Структура кода DDD

Java

прогулочная доска с полостью отходов

Это поздний пост. На самом деле это моя четвертая статья о DDD. Примерно в ноябре прошлого года я написал три статьи о DDD на своем личном веб-сайте, и все они были более стратегическими. В то время я все еще работал над проектом, в котором использовался DDD, и это был первый раз, когда я действительно начал использовать DDD в полной мере.

DDD的前3篇文章
первые 3 статьи DDD

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

Эта статья подходит для читателей, которые имеют определенное представление о тактических схемах DDD.

от сложности

Фактически, чтобы говорить о проблемах архитектуры, мы должны сначала понять, какие проблемы архитектура хочет решить. Я думаю, что архитектура кода в основном решает две проблемы: сложность программы и сложность программирования.

Путаница с зависимостями приводит к сложности программы

Сложность программы относится к нечеткой многослойности или абстракции в программе, что приводит к хаотическим зависимостям. «Зависимость» здесь на самом деле может быть понята относительно просто, поскольку А использует В. Если B удалить, A не может продолжать работать должным образом. AB может относиться к таким понятиям, как классы, модули, слои и т. д.

После длительного периода разработки индустрия программного обеспечения смогла обобщить некоторые лучшие шаблоны проектирования, многоуровневые архитектуры и даже модульность, субподряд кода и даже разделение микросервисов на основе инструментов компонентов, таких как maven/gradle. Суть архитектуры заключается в том, чтобы попытайтесь достичь «высокой сплоченности, низкой связанности».

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

DDD в сочетании с чистой архитектурой может прояснить зависимости и имеет лучшие характеристики «высокая связность, низкая связанность», чем трехуровневая архитектура.

Взаимодействие технологий и бизнеса приводит к сложности программирования

Почему программирование такое сложное? Почему это могут сделать только программисты, даже младшие программисты не могут сделать это хорошо? Разве это не просто CRUD (добавление, удаление, изменение, поиск) плюс если-иначе?

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

Бизнес-логика — это настоящий CRUD плюс if-else."Когда разработчики пишут бизнес-логику, они подобны инструментам, которые переводят бизнес-язык в язык кода.".我们很多bug,可能并不是技术层面的bug,而是由于开发和测试没有足够了解业务造成的,遗漏了业务上的某些点,产生了业务上的bug。

В трехуровневой архитектуре технологии и бизнес часто связаны и склеены, по крайней мере, на уровне кода их трудно разделить, а «бизнес»-код сложно отделить отдельно.

Но DDD может, DDD может когерировать весь бизнес на доменном уровне, а доменный уровень не зависит ни от чего другого, только чистейший бизнес. Тогда можем ли мы даже написать пользовательский пример, модульный тест и даже код реализации этого уровня для студентов-бизнесменов (или экспертов в предметной области)? Ведь это связано только с бизнесом, и все просто если-иначе.

чистая архитектура

Взгляните на временную шкалу, предложенную для DDD и различных архитектур.

软件架构编年史
Хроника архитектуры программного обеспечения

В 2003 году Эрик Эванс опубликовал книгу под названием «Дизайн, управляемый предметной областью: как справиться со сложностью программного обеспечения». В 2005 году вышла гексагональная архитектура (также называемая архитектурой портов и адаптеров), в 2006 году CQRS и Event Sourcing представили ее в выступлении Грега Янга, а в 2008 году — луковая архитектура.

В 2012 году дядя Боб прислал текст, в котором говорилось: я подытожил предыдущую архитектуру, такую ​​как шестисторонняя архитектура, луковая архитектура, кричащая архитектура (его собственный 2011 год) и т. Одно дело, я их абстрактно выразил, выдвинул такую ​​"аккуратную архитектуру".

Ссылка на чистую архитектуру: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

整洁架构图
Диаграмма чистой архитектуры

Правило внутренней зависимости

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

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

Entities

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

Use Cases

В переводе это «уровень вариантов использования», который используется для координации потока данных, входящего и исходящего из уровня сущностей, и реализации вариантов использования путем вызова и оркестровки модели предметной области. В DDD этим уровнем обычно является уровень Application Service, который является очень тонким уровнем и используется только для выполнения некоторых относительно простых задач.

Interface Adapters

Этот слой называется «слой адаптации интерфейса», который на самом деле в основном используется для адаптации к внешнему миру. Например, контроллеры (запись) и презентаторы (чтение) для входящих веб-запросов. Этот слой преобразует структуру данных, требуемую слоем User Cases или Entities, со структурой данных внешнего слоя. Например, работа с базой данных, вызов сторонних интерфейсов и т. д.

Frameworks and Drivers

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

Пересечение границ и инверсия зависимостей

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

依赖反转
Отмена зависимости

Проще говоря, мы определяем два абстрактных класса или интерфейса на уровне вариантов использования: порт ввода варианта использования или порт вывода варианта использования. Use Case Interactor реализует Use Case Input Port, а Presenter реализует Use Case Output Port.

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

код

No Code, No BB, Show me the code!

Я нашел чистую реализацию кода архитектуры версии языка Go для всех, и давайте вместе проанализируем ее структуру.

Адрес Github: https://github.com/manakuro/golang-clean-architecture.

Адрес статьи: https://medium.com/@manakuro/clean-architecture-with-go-bce409427d31

Сначала посмотрите на общую структуру:

.
├── domain(对应Entities层)
│   └── model
│       └── user.go
├── infrastructure(对应FrameWorks & Divers层)
│   ├── datastore
│   │   └── db.go
│   └── router
│       └── router.go
├── interface(对应Interface Adapters层)
│   ├── controller
│   │   ├── app_controller.go
│   │   ├── context.go
│   │   └── user_controller.go
│   ├── presenter
│   │   └── user_presenter.go
│   └── repository
│       └── user_repository.go
├── main.go
├── registry
│   ├── registry.go
│   └── user_registry.go
├── usecase(对应Use Cases层)
│   ├── presenter
│   │   └── user_presenter.go
│   ├── repository
│   │   └── user_repository.go
│   └── interactor
│       └── user_interactor.go

Слою сущностей не о чем говорить, просто поместите модель предметной области. Уровень вариантов использования определяет интерфейс для презентатора и репозитория. А в интеракторе используются эти два интерфейса.

// user_interactor.go中的Get方法
func (us *userInteractor) Get(u []*model.User) ([]*model.User, error) {
 u, err := us.UserRepository.FindAll(u)
 if err != nil {
  return nil, err
 }

 return us.UserPresenter.ResponseUsers(u), nil
}

Затем на уровне интерфейсных адаптеров реализуются два интерфейса, определенные выше. Внутри user_controller.go вызовите user_interactor.

DDD и чистая архитектура

Глядя на приведенную выше аккуратную структуру, я обнаружил, что она все еще не может полностью соответствовать тактическому режиму DDD?

Да, DDD используется для решения проблемы сложности программного обеспечения, а реальное программное обеспечение намного сложнее, чем приведенный выше демонстрационный код. Две наиболее важные архитектурные концепции, CQRS, Event Sourcing, Domain Service и т. д., не отражены на приведенной выше аккуратной архитектурной диаграмме.

Я тоже долго гадал, наконец вhgracaизблогнашел ответ здесь. На этой картинке все:

DDD与整洁架构
DDD и чистая архитектура

Исходное сообщение в блоге: https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/

Это не просто аккуратная архитектура, в ней есть несколько очевидных шестиугольных архитектур. Но внутри это образец чистой архитектуры, и он хорошо интегрируется с DDD. Я немного увеличил среднюю часть.

放大版
Увеличенная версия

Как видите, посередине находится модель домена, а затем сервис домена. Эти два кольца вместе составляют доменный уровень. Внешний уровень — это прикладной уровень, а прикладной уровень также включает в себя два кольца, внутренний — службы приложений, а внешний — процессор C/Q, прослушиватель событий и т. д.

Затем выходите, это ядро ​​​​приложения, обведенное большим красным контуром. Этот уровень определяет множество интерфейсов (или портов), таких как постоянство, сторонние сервисы, поиск, шина CQ, шина событий и т. д. Разумеется, также принимаются команды обработки и запросы.

Подумайте о проблемах, с которыми я столкнулся

DDD на предыдущих проектах, я столкнулся с некоторыми проблемами в тактическом режиме, вернитесь и подумайте об этом сейчас.

Что мне нужно для обслуживания территорий?

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

Очень распространенный сценарий — создание модели с бизнес-логикой. Хотя модель создания обычно размещается на Фабрике, Фабрика не подходит для размещения бизнес-логики. На данный момент модель домена не создана, и естественно ее можно разместить только в сервисе домена.

Конечно, наилучшей практикой DDD является максимально возможное устранение уровня службы предметной области, все связанное с уровнем модели, но достичь такой идеальной ситуации сложно.

Что делать, если мне нужно запросить другие данные на прикладном уровне?

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

Если вам нужно получить эти другие данные, как это сделать? Особенно в поле не может быть связано с текущими данными модели, такими как «Время последних комментариев».

Решение нашей команды состоит в том, чтобы использовать запрос. В обязанности запроса входит переход к запросу базы данных, который может запрашивать некоторые поля. Однако, чтобы предотвратить злоупотребление им, команда оговаривает, что его можно использовать только в resenter или read ".

Оглядываясь назад на чистую архитектуру, Query здесь на самом деле является портом вывода пользовательского случая. Однако в то время репозиторий и запрос нашей команды не были абстрагированы, то есть не было инверсии зависимостей, поэтому от них зависел слой ApplicationService, что несовместимо с «односторонней зависимостью» чистой архитектуры. Переведи лучше.

Когда именно следует отправлять и получать события домена?

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

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

Лучше отправлять события на прикладном уровне. Предыдущее решение нашей команды было довольно странным, и оно было отправлено в реализацию репозитория. Я как бы забыл, зачем я это делал в то время, но сейчас кажется, что правильнее делать это на прикладном уровне.

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

Проблемы с транзакциями в корнях агрегатов?

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

Как убедиться, что архитектура не повреждена?

Согласно предыдущему практическому опыту, структура кода со временем может разъедать. Например, Query, о котором я упоминал ранее, является примером, и необходимо гарантировать, что им не будут злоупотреблять из-за некоторого «консенсуса» между командами. Мы не можем гарантировать, что код вообще не будет поврежден, но есть некоторые способы гарантировать, что уровень зависимости не будет поврежден.

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

Вечная головоломка: определение подходящего совокупного корня

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

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

попросить поддержки

Я Ясин, блогер, который настаивает на технической оригинальности. Мой публичный аккаунт в WeChat:"запрограммировал программу"

Я видел здесь, если я думаю, что моя статья все еще написана, я могу ее поддержать.

Каждая ретвист, следуйте, как и комментарий вашей - самая большая поддержка для меня!

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

Есть также учебные ресурсы и внутреннее продвижение интернет-компаний первой линии.

В этой статье используетсяmdniceнабор текста