как строить архитектуру приложения node js

О структуре и масштабировании сложных приложений для Node.JS

Структура программных проектов – это важно. От решений, принятых в самом начале работы, зависит то, какой будет эта работа в течение всего жизненного цикла продукта.

как строить архитектуру приложения node js. Смотреть фото как строить архитектуру приложения node js. Смотреть картинку как строить архитектуру приложения node js. Картинка про как строить архитектуру приложения node js. Фото как строить архитектуру приложения node js

В основу данного материала легли ответы на часто задаваемые здесь вопросы, касающиеся структурирования сложных приложений для Node.js. Он предназначен для всех, кто чувствует потребность в улучшении структуры собственных разработок.

Вот основные темы, которые мы здесь раскроем:

Обзор демонстрационного проекта

Наше приложение получает данные из Твиттера, подписавшись на обновления по определённым ключевым словам. Подходящие твиты передаются в очередь RabbitMQ. Содержимое очереди обрабатывается и сохраняется в базе данных Redis. Кроме того, в приложении имеется REST API, предоставляющее доступ к сохранённым твитам.

Структура файлов проекта выглядит так:

В проекте имеется 3 процесса:

Поддержка различных сред выполнения и конфигураций приложения

Конфигурационные данные для конкретного экземпляра приложения следует загружать из переменных окружения. Их не надо добавлять в код как константы.

Речь идёт о параметрах, которые могут не совпадать в различных вариантах развёртывания приложения и в разных средах выполнения. Например, это может быть запуск в среде разработки, на билд-сервере, в среде, максимально приближенной к рабочей, и, наконец, в продакшн-окружении. Такой подход позволяет иметь единую кодовую базу приложения, способную работать в любых условиях.

Хороший способ проверки корректности разделения конфигурационных данных и внутренних механизмов приложения заключается в следующем. Если код проекта можно, в любой момент работы над ним, выложить в открытый доступ, значит логика и настройки разделены как следует. Это автоматически означает защиту от попадания секретных данных или параметров учётных записей в систему контроля версий.

Проверка конфигурационных данных

Не включать настройки в код – решение правильное, но весьма полезно ещё и проверять переменные окружения перед их использованием. Это поможет обнаружить ошибки конфигурации в самом начале работы и избежать ситуаций, в которых приложение попытается работать с неверными или отсутствующими настройками. О плюсах раннего обнаружении ошибок в конфигурационных данных можно почитать здесь.

Разделение конфигурационных данных

Все конфигурационные данные можно держать в одном файле, но, в ходе роста и развития проекта, такой файл будет увеличиваться в размерах, работать с ним будет неудобно. Для того, чтобы этих проблем избежать, настройки имеет смысл разделить, основываясь, например, на компонентах приложения. В нашем примере это выглядит так:

После этого в основном файле config.js нужно лишь скомбинировать параметры компонентов.

Обратите внимание на то, что не следует группировать конфигурационные данные по признаку рабочего окружения, то есть, скажем, держать в файле config/production.js настройки для продакшн-версии приложения. Такой подход препятствует масштабируемости приложения, например, в ситуации, когда со временем ту же продакшн-версию надо будет развёртывать в различных средах.

Организация многопроцессного приложения

Выше мы говорили о разделении конфигурационных данных по компонентам. Этот подход оказывается очень кстати при наличии в проекте процессов различных типов. Процесс каждого типа может получить собственные настройки, запрашивая лишь необходимые ему компоненты, не ожидая наличия не использованных ранее переменных среды.

В файле config/index.js :

В корневом файле index.js запускаем нужный процесс с переменной окружения PROCESS_TYPE :

В результате оказывается, что приложение у нас одно, но разбито оно на множество независимых процессов. Каждый из них можно запустить индивидуально, при необходимости – поднять несколько параллельных процессов одного вида, которые не повлияют на другие части приложения. При этом частями кода, вроде моделей, могут совместно пользоваться разные процессы, что способствует соблюдению в ходе разработки принципа DRY.

Организация файлов с тестами

В отдельной папке /test имеет смыл хранить все дополнительные тесты и утилиты, которые не используются самим приложением.

Источник

Лучшие практики Node.js — советы по структуре проектов

1. Советы по структуре проектов

1.1 Структурируйте ваш проект по компонентам

Худшая ошибка больших приложений — это архитектура монолита в виде огромной кодовой базы с большим количеством зависимостей (спагетти-кодом), такая структура сильно замедляет разработку особенно внедрение новых функций. Совет — разделяйте ваш код на отдельные компоненты, для каждого компонента выделяйте собственную папку для модулей компонента. Важно чтобы каждый модуль остался маленьким и простым. В разделе «Подробнее» можно посмотреть примеры правильной структуры проектов.

В противном случае: разработчикам будет сложно развивать продукт — добавление нового функционала и внесение изменений в код будут производиться медленно и иметь высокий шанс поломки других зависимые компонент. Считается, что если бизнес-юниты не разделены, то могут возникнуть проблемы с масштабирование приложения.

Объяснение одним абзацем

Для приложений среднего размера и выше монолиты действительно плохи — одна большая программа с множеством зависимостей просто сложна для понимания, да еще и часто приводит к спагетти-коду. Даже опытные программисты, которые умеют правильно «готовить модули», тратят много усилий на проектирование архитектуры и стараются тщательно оценить последствия каждого изменения в связях между объектами. Наилучшим вариантом является архитектура, базирующаяся на наборе небольших программ-компонент: разделите программу на отдельные компоненты, которые не делятся ни с кем своими файлами, каждая компонента должна состоять из небольшого количества модулей (например, модулей: API, сервиса, доступа к БД, тестирования и т.п.), так чтобы структура и состав компоненты были очевидны. Некоторые могут назвать эту архитектуру «микросервисной», но, важно понимать, что микросервисы — это не спецификация, которой вы должны следовать, а скорее набор некоторых принципов. По вашему желанию, вы можете принять на вооружение как отдельные из этих принципов, так и все принципы архитектуры микросервисов. Оба способа хороши если вы сохраняете сложность кода на низком уровне.

Самое меньшее, что вы должны сделать, это определить границы между компонентами: назначить папку в корне вашего проекта для каждого из них и сделать их автономным. Доступ к функционалу компоненты должен быть реализован только через публичный интерфейс или API. Это основа для того, чтобы сохранить простоту ваших компонентов, избежать «ада зависимостей» и дать вашему приложению дорасти до к полноценных микросервисов.

Цитата блога: «Масштабирование требует масштабирования всего приложения»
Из блога MartinFowler.com

Монолитные приложения могут быть успешными, но люди все чаще испытывают разочарование в связи с ними, особенно когда задумываются о развертывании в облаке. Любые, даже небольшие, изменения в приложении требуют сборки и перевыкладки всего монолита. Часто трудно постоянно сохранять хорошую модульную структуру, при которой изменения в одном модуле не затрагивают другие. Масштабирование требует масштабирования всего приложения, а не только отдельных его частей, конечно, для такого подхода требуется больше усилий.

… если вы бывали в библиотеке, то вы представляете ее архитектуру: парадный вход, стойки регистрации, читальные залы, конференц-залы и множество залов с книжными полками. Сама архитектура будет говорить: это здание — библиотека.

Так о чем же говорит архитектура вашего приложения? Когда вы смотрите на структуру каталогов верхнего уровня и файлы-модули в них они говорят: я — интернет-магазин, я — бухгалтерия, я — система управления производством? Или они кричат: я — Rails, я — Spring/Hibernate, я — ASP?
(Примечание переводчика, Rails, Spring/Hibernate, ASP — это фреймворки и веб-технологии).

Правильная структура проекта с автономными компонентами

как строить архитектуру приложения node js. Смотреть фото как строить архитектуру приложения node js. Смотреть картинку как строить архитектуру приложения node js. Картинка про как строить архитектуру приложения node js. Фото как строить архитектуру приложения node js

Неправильная структура проекта с группировкой файлов по их назначению

как строить архитектуру приложения node js. Смотреть фото как строить архитектуру приложения node js. Смотреть картинку как строить архитектуру приложения node js. Картинка про как строить архитектуру приложения node js. Фото как строить архитектуру приложения node js

1.2 Разделяйте слои ваших компонентов и не смешивайте их со структурой данных Express

Каждый ваш компонент должен иметь «слои», к примеру, для работы с вебом, бизнес-логикой, доступом к БД, эти слои должны иметь свой собственный формат данных не смешанный с форматом данных сторонних библиотек. Это не только четко разделяет проблемы, но и значительно облегчает проверку и тестирование системы. Часто разработчики API смешивают слои, передавая объекты веб-слоя Express (к примеру, req, res) в бизнес-логику и в слой данных — это делает ваше приложение зависимым и сильно связанным с Express.

В противном случае: для приложения, в котором объекты слоев перемешаны, сложнее обеспечить тестирования кода, организацию CRON-тасков и других «неExpress» вызовов.

Разделите код компонента на слои: веб, сервисы и DAL

как строить архитектуру приложения node js. Смотреть фото как строить архитектуру приложения node js. Смотреть картинку как строить архитектуру приложения node js. Картинка про как строить архитектуру приложения node js. Фото как строить архитектуру приложения node js

Обратная сторона смешение слоев в одной gif-анимации

как строить архитектуру приложения node js. Смотреть фото как строить архитектуру приложения node js. Смотреть картинку как строить архитектуру приложения node js. Картинка про как строить архитектуру приложения node js. Фото как строить архитектуру приложения node js

1.3 Оберните ваши базовые утилиты в пакеты npm

В большом приложении, состоящем из различных сервисов со своими репозиториями, такие универсальные утилиты, как логгер, шифрование и т.п., должны быть обернуты вашим собственным кодом и представлены как приватные npm-пакеты. Это позволяет делиться ими между несколькими кодовыми базами и проектами.

В противном случае: вам придется изобрести собственный велосипед для расшаривания этого кода между отдельными кодовыми базами.

Объяснение одним абзацем

Как только проект начнет расти и у вас на разных серверах будут разные компоненты, использующие одни и те же утилиты, вы должны начать управлять зависимостями. Как можно без дублирования кода вашей утилиты между репозиториями позволить нескольким компонентам использовать ее? Для этого есть специальный инструмент, и называется он — npm…. Начните с обертывания сторонних пакетов утилит вашим собственным кодом, чтобы в будущем его можно было легко заменить, и опубликуйте этот код как частный пакет npm. Теперь вся ваша кодовая база может импортировать код утилит и использовать все возможности управления зависимостями npm. Помните, что есть следующие способы публиковать пакеты npm для личного использования, не открывая их для публичного доступа: частные модули, частный реестр или локальные пакеты npm.

Совместное использование собственных общих утилит в разном окружении
как строить архитектуру приложения node js. Смотреть фото как строить архитектуру приложения node js. Смотреть картинку как строить архитектуру приложения node js. Картинка про как строить архитектуру приложения node js. Фото как строить архитектуру приложения node js

1.4 Разделяйте Express на «приложение» и «сервер»

Избегайте неприятной привычки определять все приложение Express в одном огромном файле, разделите ваш ‘Express’-код по крайней мере на два файла: объявление API (app.js) и код www-сервера. Для еще лучшей структуры размещайте объявление API в модулях компонент.

В противном случае: ваш API будет доступен для тестирования только через HTTP-вызовы (что медленнее и намного сложнее для создания отчетов о покрытии). Еще, предполагаю, не слишком большое удовольствие работать с сотнями строк кода в одном файле.

Объяснение одним абзацем

Рекомендуем использовать генератор приложений Express и его подход к формированию базы приложения: объявление API отделено от конфигурации сервера (данных о порте, протоколе и т.п.). Это позволяет тестировать API без выполнения сетевых вызовов, что ускоряет выполнение тестирования и упрощает получение метрик покрытия кода. Это также позволяет гибко развертывать один и тот же API для разных сетевых настроек сервера. Бонусом вы так же получаете лучшее разделение ответственности и более чистый код.

Пример кода: объявление API, должно находиться в app.js

Пример кода: сетевых параметров сервера, должно находиться в /bin/www

Пример: протестируем свой API используя supertest (популярный пакет тестирования)

1.5 Используйте безопасную иерархическую конфигурацию с учетом переменных окружения

Идеальная настройка конфигурации должна обеспечивать:

(1) считывание ключей как из конфигурационного файла, так и из переменных среды,
(2) хранение секретов вне кода репозитория,
(3) иерархическую (а не плоскую) структуру данных конфигурационного файла для облегчения работы с настройками.

Есть несколько пакетов, которые могут помочь в реализации этих пунктов, такие как: rc, nconf и config.

В противном случае: несоблюдение этих требований к конфигурации приведет к срыву работы как отдельного разработчика, так и всей команды.

Объяснение одним абзацем

Когда вы имеете дело с настройками конфигурации многие вещи могут раздражать и тормозить работу:

1. Задание всех параметров с использованием переменных окружения становится очень утомительным если требуется ввести 100+ ключей (вместо того, чтобы просто зафиксировать их в файле конфигурации), однако если конфигурация будет задаваться только в файлах настроек, то это может быть неудобно для DevOps. Надежное конфигурационное решение должно объединять оба способа: и файлы конфигураций, и переопределения параметров из переменных окружения.

2. Если конфигурационный файл является «плоском» JSON (т.е. все ключи записаны в виде единого списка), то при увеличении количества настроек с ним будет сложно работать. Решить эту проблему можно с помощью формирования вложенных структур содержащих группы ключей по разделам настроек, т.е. организовать иерархического JSON-структуру данных (см. пример ниже). Есть библиотеки, которые позволяют хранить такую конфигурацию в нескольких файлах и объединять данные из них во время выполнения.

3. Не рекомендуется хранить в конфигурационных файлах конфиденциальную информацию (такую как пароль БД), но однозначного удобного решения где и как хранить такую информацию — нет. Некоторые библиотеки конфигураций позволяют шифровать конфигурационные файлы, другие шифруют эти записи во время git-коммитов, а можно вообще не сохранять секретные параметры в файлах и задавать их значения во время развертывания через переменные среды.

4. Некоторые расширенные сценарии конфигураций требуют ввода ключей через командную строку (vargs) или синхронизируют конфигурационные данные через централизованный кэш, такой как Redis, чтобы несколько серверов использовали одни и те же данные.

Есть npm-библиотеки, которые помогут вам с реализацией большинства этих рекомендаций, советуем взглянуть на следующие библиотеки: rc, nconf и config.

Пример кода: иерархическая структура помогает находить записи и работать с объемными файлами конфигураций

(Примечание переводчика, в классическом JSON-файле нельзя использовать комментарии. Вышеприведенный пример взят из документации библиотеки config, в которой добавлен функционал предварительной очистки JSON-файлов от комментариев. Поэтому пример вполне рабочий, однако линтеры, такие как ESLint, с настройками по умолчанию могут «ругаться» на подобный формат).

Послесловие от переводчика:

Источник

Реализация SOLID и слоистой архитектуры в Node.js с TypeScript и InversifyJS

Привет, Хабр! Предлагаю вашему вниманию перевод статьи Implementing SOLID and the onion architecture in Node.js with TypeScript and InversifyJS автора Remo H. Jansen

В этой статье мы рассмотрим архитектуру, известную как слоистая (onion). Слоистая архитектура — подход к построению архитектуры приложения, придерживающийся принципов SOLID. Он создан под влиянием DDD и некоторых принципов функционального программирования, а также, активно применяет принцип инъекции зависимостей.

Предпосылки

Данный раздел описывает некоторые подходы и принципы разработки программного обеспечения, необходимые для понимания слоистой архитектуры.

Принцип разделения ответственности

Под ответственностью подразумеваются различные аспекты функционала программного обеспечения. Например «бизнес-логика» и интерфейс, через который она используется — это разные ответственности.

Разделение ответственности позволяет изолировать код, реализующий каждую ответственность.Например, изменение интерфейса не должно требовать изменения кода бизнес-логики, и так далее.

Принципы SOLID

SOLID является акронимом от следующих пяти принципов:
как строить архитектуру приложения node js. Смотреть фото как строить архитектуру приложения node js. Смотреть картинку как строить архитектуру приложения node js. Картинка про как строить архитектуру приложения node js. Фото как строить архитектуру приложения node js

Принцип единственной ответственности

Класс должен иметь только одну ответственность. (прим. переводчика: более точная формулировка, на мой взгляд, звучит так: «Класс должен иметь одну и только одну причину для изменений»)

Самый эффективный способ «сломать» приложение — создание божественного класса.

Божественный класс — класс, знающий и делающий слишком много. Этот подход является хорошим примером анти-паттерна.

Божественный класс отслеживает большое количество информации и имеет несколько ответственностей. Одна правка кода, с большой вероятностью, может повлиять на другие части класса и опосредованно повлиять на остальные классы, которые используют его. Это приводит к проблемам развития и обслуживания кода, поскольку никто не осмеливается вносить изменения, кроме добавления нового функционала.

Следующий пример представляет класс TypeScript описывающий персону. Этот класс не должен включать валидацию email, так как она не относится к поведению персоны.

Мы можем улучшить этот класс путем вынесения ответственности за валидацию email в новый класс Email :

Реализация классов с единственной ответственностью, по умолчанию, упрощает его понимание, а также расширение/улучшение.

Принцип открытости/закрытости

Программные сущности должны быть открыты для расширения и закрыты для модификации.

Следующий пример кода является примером кода, написанного без соблюдения принципа открытости/закрытости:

Для решения данной проблемы, мы можем использовать преимущества полиморфизма в ООП, например так:

Данное решение позволяет нам добавить поддержку новой фигуры (открыто для расширения) без необходимости изменения существующего кода (закрыто для модификации).

Принцип подстановки Барбары Лисков

Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения программы.

Данный принцип, также, призывает нас использовать полиморфизм. В предыдущем примере кода:

Принцип разделения интерфейса

Много интерфейсов, специально предназначенных для клиентов, лучше, чем один интерфейс общего назначения.

Наш доменный слой нуждается в методе вычисления площади, но ему нет необходимости ничего знать про сериализацию:

И, напротив, наш инфраструктурный слой нуждается в методе сериализации, но ничего не знает про вычисление площади:

Проблема в том, что добавление метода serialize в интерфейс Shape нарушает принципы разделения ответственности и единственной ответственности. Фигура является бизнес-концепцией, а его сериализация — инфраструктурной концепцией. Мы не должны смешивать эти концепции в одном интерфейсе.

Принцип разделения интерфейсов говорит нам, что много клиенто-ориентированных интерфейсов лучше, чем один интерфейс общего назначения, таким образом, мы должны разделить наши интерфейсы:

При помощи новых интерфейсов, мы полностью изолируем доменный слой от инфраструктурных концепций.

Теперь в инфраструктурном слое мы можем использовать новый набор сущностей, имеющих функционал сериализации.

Использование нескольких интерфейсов вместо одного интерфейса общего назначения, позволяет избежать нарушения принципов Разделения ответственности (бизнес слой ничего не знает про сериализацию) и Единой ответственности (мы не имеем божественного класса, который знает и о вычислении площади фигур и об их сериализации).

Мы можем спорить о том, что RectangleDTO и Rectangle почти идентичны и был нарушен принцип Не повторяйся (DRY). Я думаю, что это другой случай. Потому что, эти классы выглядят похожими, но являются выражением разных концепций. Далеко не всегда, похожий код является дублированием.

Даже в случае нарушения принципа DRY, мы будем выбирать между нарушением DRY или SOLID. Я считаю, что принцип DRY менее важен, нежели принципы SOLID и я предпочту «повторить себя» в таком случае.

Принцип инверсии зависимостей

Зависимость на Абстракциях. Нет зависимости на что-то конкретное.

Принцип инверсии зависимостей велит нам всегда стараться использовать в качестве зависимостей интерфейсы а не конкретные их реализации. Важно понимать, что Инверсия зависимостей и Инъекция зависимостей являются разными понятиями.

К сожалению, принцип инверсии зависимостей представлен в аббревиатуре SOLID буквой D. И всегда к объяснению этого принципа переходят в последнюю очередь, несмотря на то, что он является самым важным в SOLID. Без применения этого принципа, большинство других принципов SOLID применять невозможно. Если мы оглянемся назад и вспомним все принципы, которые мы затронули выше, мы придем к выводу, что использование интерфейсов является ключевым элементом в каждом принципе:

Реализация принципов SOLID в языках программирования. которые не поддерживают интерфейсы или в программных парадигмах, не поддерживающих полиморфизм, является очень неестественным. Например, в JavaScript ES5 или даже ES6, реализация SOLID может быть крайне неестественной. Тем не менее, в TypeScript это может быть реализовано вполне естественно.

Шаблон проектирования модель-представление-контроллер MVC

Данный подход предполагает разделение кода приложения на три блока: модель, представление и контроллер.

как строить архитектуру приложения node js. Смотреть фото как строить архитектуру приложения node js. Смотреть картинку как строить архитектуру приложения node js. Картинка про как строить архитектуру приложения node js. Фото как строить архитектуру приложения node js

Модель

Код этой части приложения должен имплементировать логику данных домена. Обычно, этот код вытягивает и сохраняет состояние модели в базу данных. Например, объект Product может получать информацию из базы данных, обрабатывать её и записывать обновленную информацию обратно в таблицу продуктов в SQL Server.

В простых приложениям, модель может не быть выражена в коде явно. Например, если приложение только читает данные из БД и передаёт их в представление, приложение не имеет слоя модели, как такового. В таком случае, код работы с БД выполняет роль модели.

Представление

Представления отвечают за отображение пользовательского интерфейса приложения. Обычно этот интерфейс создаётся на основе данных модели. В качестве примера можно привести представление формы редактирования товара, которое отображает поля для ввода, выпадающие списки и чекбоксы, основываясь на текущем состоянии объекта товара.

Контроллер

Контроллеры отвечают за обработку пользовательских действий, работают с моделями и определяют представление для отображения пользовательского интерфейса. В приложении MVC, представление только отображает информацию; контроллер обрабатывает запрос пользователя и возвращает ответ на действие пользователя. Например, контроллер обрабатывает параметры строки запроса и передает их в модель, которая может использовать эти параметры для выполнения запроса к базе данных.

Подход MVC позволяет создавать приложения с разделением различных аспектов (логика ввода, бизнес логика и логика отображения) и обеспечивая низкую связность этих частей. Данных подход определяет, где должна быть расположена каждая часть логики приложения. Логика пользовательского интерфейса располагается в представлении. Логика ввода — в контроллере. Бизнес-логика — в модели. Это разделение позволяет управлять сложностью в разработке приложения, так как заставляет фокусироваться на одном аспекте и не думать об остальных. Например, вы можете сфокусироваться только на представлении, без зависимости от бизнес-логики.

Слабая связность между тремя основными компонентами MVC приложения, также, способствует параллельной разработке. Например, один разработчик может работать над представлением, второй разработчик может работать над логикой контроллера и третий может сфокусироваться на бизнес-логике. Паттерн проектирования MVC — отличный пример разделения кода для улучшения поддерживаемости кода.

Репозиторий и дата-маппер

Паттерн MVC помогает нам разделить логику ввода, бизнес-логику и логику пользовательского интерфейса. Тем не менее, в зоне ответственности модели остаётся слишком много аспектов. Мы можем использовать паттерн Репозиторий для отделения логики получения и маппинга данных на сущности модели от бизнес-логики, которая непосредственно работает с моделью. Бизнес-логика должна быть независимой по отношению к типам данных, которые хранятся на уровне источника данных. Например, источником данных может быть база данных, файловая система или веб-сервис.

Репозиторий является посредником между слоем данных и слоем бизнес-логики приложения. Он запрашивает нужные данные и маппит их на бизнес-сущность, а также, сохраняет изменения бизнес-сущности в слой данных. Репозиторий отделяет бизнес-логику от взаимодействия с нижележащими слоями данных. Это разделение между слоями данных и бизнес-логики имеет три преимущества:

Репозиторий осуществляет запросы от имени клиента. Репозиторий возвращает набор сущностей, соответствующий запросу. Репозиторий, также, сохраняет новые или измененные сущности. Следующая диаграмма показывает взаимодействие репозитория с клиентом и источником данных.

как строить архитектуру приложения node js. Смотреть фото как строить архитектуру приложения node js. Смотреть картинку как строить архитектуру приложения node js. Картинка про как строить архитектуру приложения node js. Фото как строить архитектуру приложения node js

Репозитории являются мостами между данными и операциями из различных доменов. В общем случае, это маппинг из доменов со слабо типизированными данными, такими как базы данных, в домены со строго типизированными объектами, такие как сущности доменной модели.

Репозиторий осуществляет запросы к базе данных и маппит результаты на внешние бизнес-сущности. Репозитории, зачастую, используют паттерн Преобразователь Данных (Data Mapper) для преобразования данных между их представлениями в разных слоях.

Паттерн Репозиторий помогает избавить своих клиентов от зависимости на конкретные технологии хранения данных. Например, если клиент вызывает репозиторий catalog для получения данных определенного товара, ему необходимо знать только интерфейс этого репозитория. В этом случае, клиенту не нужно ничего знать о способе получения данных репозиторием, будь то SQL-запрос к базе данных или запрос к Sharepoint с использованием Языка разметки совместных приложений (CAML). Подобная изоляция зависимостей обеспечивает гибкость в развитии конкретных реализаций.

Слоистая архитектура

Данный подход делит приложение на слои (подобно луковице):

как строить архитектуру приложения node js. Смотреть фото как строить архитектуру приложения node js. Смотреть картинку как строить архитектуру приложения node js. Картинка про как строить архитектуру приложения node js. Фото как строить архитектуру приложения node js

Центральный слой — доменная модель. По мере движения от центрального слоя к внешним, мы можем увидеть доменные сервисы, сервисы уровня приложения и, наконец, слои тестов, инфраструктуры и пользовательского интерфейса.

В DDD, в центре всего находится «Домен». Домен состоит из двух компонентов:

В функциональном программировании, одним из главных архитектурных принципов, является вынесение всех сайд-эффектов к границам приложения. Слоистая архитектура также следует этому принципу. Ядро приложения (доменные сервисы и доменная модель) должны быть быть свободны от сайд-эффектов и деталей реализации. Это означает, что в нём не должно быть ссылок на такие вещи, как детали реализации хранения информации (например SQL) или передачи данных (например HTTP).

Доменная модель и доменные сервисы ничего не знают о базах данных, протоколах, кэшах и любых других аспектах, связанных к конкретной реализацией. Ядро приложения заботится только о бизнес-правилах. Внешние слои (инфраструктура, тест и пользовательский интерфейс) непосредственно взаимодействуют с системными ресурсами (сеть, хранилище данных и так далее) и, тем самым, изолируют сайд-эффекты от ядра приложения.

Изоляция слоёв достигается использованием интерфейсов и применением принципа инверсии зависимостей: Компоненты должны зависеть на абстракции (интерфейсы), а не на конкретные реализации (классы). В качестве примера, рассмотрим один из инфраструктурных слоёв — HTTP слой, в основном, построенный из набора контроллеров. Контроллер AircraftController может иметь зависимость от интерфейса AircraftRepository :

как строить архитектуру приложения node js. Смотреть фото как строить архитектуру приложения node js. Смотреть картинку как строить архитектуру приложения node js. Картинка про как строить архитектуру приложения node js. Фото как строить архитектуру приложения node js

как строить архитектуру приложения node js. Смотреть фото как строить архитектуру приложения node js. Смотреть картинку как строить архитектуру приложения node js. Картинка про как строить архитектуру приложения node js. Фото как строить архитектуру приложения node js

Это означает, что вы имеем зависимость одного из внешних слоёв (инфраструктура) на один из внутренних слоёв (доменные сервисы). В слоистой архитектуре разрешены зависимости только в одном направлении, от внешних слоёв к внутренним, а никак не наоборот.

как строить архитектуру приложения node js. Смотреть фото как строить архитектуру приложения node js. Смотреть картинку как строить архитектуру приложения node js. Картинка про как строить архитектуру приложения node js. Фото как строить архитектуру приложения node js

На этапе работы приложения, InversifyJS использует свою конфигурацию для инъекции конкретной реализации:

Сейчас наш граф зависимостей выглядит таким образом:

как строить архитектуру приложения node js. Смотреть фото как строить архитектуру приложения node js. Смотреть картинку как строить архитектуру приложения node js. Картинка про как строить архитектуру приложения node js. Фото как строить архитектуру приложения node js

Рассмотрим реализацию интерфейсов Repository и AircraftRepository :

Начнём с реализации Repository

Эта реализация ожидает, что EntityDataMapper и TypeOrmRepository будут внедрены через конструктор. Эти зависимости будут использованы для получения данных из БД и маппинга их в доменные сущности.

Также, нам нужен интерфейс EntityDataMapper :

И реализация EntityDataMapper :

Мы используем EntityDataMapper для маппинга сущностей, полученных от TypeOrmRepository в доменные сущности.Теперь наша диаграмма зависимостей выглядит следующим образом:

как строить архитектуру приложения node js. Смотреть фото как строить архитектуру приложения node js. Смотреть картинку как строить архитектуру приложения node js. Картинка про как строить архитектуру приложения node js. Фото как строить архитектуру приложения node js

И, наконец, реализуем AircraftRepository :

Теперь, когда мы закочили, наш граф зависимостей выглядит так:

как строить архитектуру приложения node js. Смотреть фото как строить архитектуру приложения node js. Смотреть картинку как строить архитектуру приложения node js. Картинка про как строить архитектуру приложения node js. Фото как строить архитектуру приложения node js

Диаграмма выше использует цвета для определения типа объекта: реализаций (классы, голубой) и абстракций (интерфейсы, оранжевый).

Следующая диаграмма использует цвета для определения принадлежности компонентов к слоям, зеленый — доменный слой и инфраструктурный слой — голубой.

как строить архитектуру приложения node js. Смотреть фото как строить архитектуру приложения node js. Смотреть картинку как строить архитектуру приложения node js. Картинка про как строить архитектуру приложения node js. Фото как строить архитектуру приложения node js

Такой архитектурный подход работает для меня в больших энтерпрайзных проектах на протяжении десяти последних лет. Также, я закончил разделение огромного слоистого монолита на микросервисы, которые следуют той же архитектуре. Мне нравится говорить, что в случае с микросервисами на слоистой архитектуре, мы имеем «мешок луковиц».

Я надеюсь, Вам понравилась эта статья! Пожалуйста, делитесь своими мыслями в комментариях или напрямую автору @RemoHJansen.

Бонус для тех, кто дочитал до конца — репозиторий с рабочим примером кода.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *