Проектирование микрослужбы, ориентированной на DDD

Совет

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

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

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

Иногда технические правила и шаблоны DDD считаются препятствием, поскольку для реализации подхода DDD требуется высокий уровень подготовки. Однако важны не шаблоны сами по себе, а организация кода, таким образом, чтобы он соответствовал бизнес-проблемам и использовал те же бизнес-термины (единый язык). Кроме того, подход DDD должен применяться только в том случае, когда вы реализуете сложные микрослужбы со сложными бизнес-правилами. Простыми задачами, такими как службы CRUD, можно управлять с помощью более простых подходов.

И именно вопрос о том, где провести границу, является ключевым при разработке и определении микрослужбы. DDD шаблоны помогают понять сложную структуру предметной области. В модели предметной области для каждого ограниченного контекста вы задаете и определяете сущности, объекты значений и агрегаты, которые моделируют вашу предметную область. Вы строите и детализируете модель предметной области, которая содержится в границах, задающих контекст. Это подход хорошо проявляется при использовании микрослужб. Компоненты в пределах этих границ становятся микрослужбами, хотя в некоторых случаях ограниченный контекст или бизнес-микрослужба могут состоять из нескольких физических служб. DDD описывает границы, а, следовательно, и микрослужбы.

Сохранение границы контекста микрослужбы в относительно небольших пределах

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

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

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

Уровни в микрослужбах DDD

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

Например, сущность может загружаться из базы данных. Затем часть этих сведений, или объединенные данные, включающие данные из других сущностей, могут отправляться в пользовательский интерфейс клиента через веб-API REST. Суть в том, что объект содержится в уровне модели предметной области и не должен передаваться в другие области, к которым он не принадлежит, такие как уровень представления данных.

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

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

На рис. 7-5 показана реализация многоуровневой архитектуры в приложении eShopOnContainers.

Diagram showing the layers in a domain-driven design microservice.

Рис. 7-5. Уровни DDD в микрослужбе заказов в eShopOnContainers

Три уровня в микрослужбе DDD, например для заказа. Каждый слой является проектом VS: прикладной уровень — Ordering.API, уровень предметной области — Ordering.Domain, а уровень инфраструктуры — Ordering.Infrastructure. Необходимо разработать систему таким образом, чтобы каждый уровень взаимодействовал только с некоторыми из других уровней. Простым решением будет принудительно реализовать уровни в виде отдельных библиотек классов, так как в этом случае вы сможете легко определить, какие зависимости устанавливаются между библиотеками. Например, уровень модели домена не должен зависеть от любого другого уровня (классы модели домена должны быть обычными старыми объектами класса или POCO, классами). Как показано на рис. 7-6, библиотека уровня Ordering.Domain имеет зависимости только от библиотек .NET и пакетов NuGet, но не от других пользовательских библиотек, таких как библиотека данных или библиотека сохраняемости.

Screenshot of Ordering.Domain dependencies.

Рис. 7-6. Уровни, реализованные в виде библиотек, позволяют лучше управлять зависимостями между уровнями

Уровень модели предметной области

В великолепной книге Эрика Эванса Проблемно-ориентированное проектирование об уровне модели предметной области говорится следующее.

Уровень модели предметной области отвечает за представления концепции бизнес-процессов, бизнес-правил и сведений о бизнес-ситуации. Здесь контролируется и используется состояние, которое отражает бизнес-ситуацию, несмотря на то, что технические детали его хранения делегируются инфраструктуре. Этот уровень является сердцем программного обеспечения для бизнеса.

Уровень модели предметной области — это уровень, где содержится суть бизнеса. При реализации этого уровня в микрослужбе в среде .NET он создается как библиотека классов с сущностями предметной области, которые получают данные плюс взаимодействие (методы с логикой).

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

Сущности предметной области не должны иметь никаких прямых зависимостей (например, производных от базового класса) ни с какой платформой инфраструктуры доступа к данным, такими как Entity Framework и NHibernate. В идеальном случае сущности предметной области не должны наследоваться от или являться никаким типом, определенном в каком-либо инфраструктурном фреймворке.

Большинство современных ORM платформ, таких как Entity Framework Core, допускают такой подход, при котором классы модели предметной области не привязаны к инфраструктуре. Однако наличие сущностей POCO не всегда возможно при использовании определенных баз данных NoSQL и платформ, например субъектов и надежных коллекций в Azure Service Fabric.

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

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

Уровень приложения

Переходя к уровню приложения, мы снова процитируем книгу Эрика Эванса Проблемно-ориентированное проектирование:

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

Часто уровень приложения микрослужбы в .NET реализуется как проект веб-API ASP.NET Core. Этот проект реализует взаимодействие микрослужбы, удаленный доступ к сети и внешние веб-API, используемые из пользовательского интерфейса или клиентских приложений. При использовании подхода CQRS он включает запросы, принимаемые микрослужбой команды и даже управляемое событиями взаимодействие между микрослужбами (события интеграции). Веб-API ASP.NET Core, представляющий уровень приложения, не должен содержать бизнес-правила или наборы знаний предметной области (особенно правила предметной области для транзакции или обновлений), которые должны принадлежать библиотеке классов модели предметной области. Уровень приложения должен только координировать задачи и не должен содержать или задавать состояние предметной области (модель предметной области). Он делегирует выполнение бизнес-правил непосредственно классам модели предметной области (корням агрегации и сущностям предметной области), что в конечном итоге приводит к обновлению данных в этих сущностях предметной области.

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

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

Уровень инфраструктуры

Уровень инфраструктуры определяет способ сохранения данных, изначально содержащихся в сущности предметной области (в памяти), в базах данных или других постоянных хранилищах данных. В примере используется код Entity Framework Core для реализации классов шаблона репозитория, которые используют класс DBContext для сохранения данных в реляционной базе данных.

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

Таким образом, уровни, или библиотеки классов, и проекты должны в конечном счете зависеть от уровня модели предметной области (библиотеки), а не наоборот, как показано на рис. 7-7.

Diagram showing dependencies that exist between DDD service layers.

Рис. 7-7. Зависимости между уровнями в DDD

Зависимости в службе DDD: прикладной уровень зависит от предметной области и инфраструктуры, а инфраструктура зависит от предметной области, но предметная область не зависит ни от какого уровня. Проектирование уровней должно выполняться отдельно для каждой микрослужбы. Как отмечалось ранее, вы можете реализовать наиболее сложные микрослужбы по шаблонам DDD, тогда как при реализации простых микрослужб,управляемых данными (простой CRUD в одном уровне), использовать более простые подходы.

Дополнительные ресурсы