Комментарии 10
Все круто. Только еще мапить в промежуточный формат типа json или xdto и уже их отправлять в зеленый слой и получать оттуда. А то как-то связанность большая между элементом кода struct и внешним миром.
Если под маппингом имеется в виду Домен <-> Слой приложение (Handler, Endpoint, Controller, RPC, CLI-command и другое),
То слой приложение будет отдавать ответ в нужном виде и формате (json, xml и др.) или вы предлагаете что данные из Домена можно сразу в json положить и отдать пользователю?
Если я неправильно поняла о каком именно маппинге говорится, уточните пожалуйста)
Для отображения данных лучше не использовать агрегаты совсем. Даже если в текущий момент данных достаточно, то не факт, что в будущем не появятся новые поля.
Очень сильно упрощает работу разделение моделей получения данных и агрегата изменения данных.
Я не очень понимаю как организовать взаимодействие разных контекстов. Например такая ситуация:
У пользователя есть личный кабинет, он может в личном кабинете запросить счет.
У бухгалтерии тоже есть личный кабинет, в котором она видит запросы счетов от пользователей и как-то их обрабатывает.
В данном случае, "запрос счета" у пользователя и "запрошенный счет" у бухгалтерии - это разные сущности? И каким образом "запрос счета" превращается в "запрошенный счет"? Должен ли пользователь знать о бухгалтерии и дергать в ней какой-то метод, чтобы запросить счет?
Мне кажется, что тут недостаточно исходных данных для точного ответа, потому что возможны разные сценарии в зависимости от процессов и архитектуры.
>> "запрос счета" у пользователя и "запрошенный счет" у бухгалтерии - это разные сущности?
Скорее да, у вас может может быть целый ворох действий по постановке и выполнению формирования счета для пользователя через какой-нибудь битрикс. А для пользователя важно видеть только счетное кол-во статусов (принято, в работе, выполнено).
>> каким образом "запрос счета" превращается в "запрошенный счет"? Должен ли пользователь знать о бухгалтерии и дергать в ней какой-то метод, чтобы запросить счет?
А какая архитектура? Синхронная/асинхронная? Можно дергать метод апи, а можно в брокер события кидать.
Простой ответ: если два разных контекст - значит две разные сущности.
Но согласен с @Yakamoz, нужно больше вводных.
Поделюсь как бы я делал. При старте разработки, когда необходимости быстро получить готовый продукт и когда не понятно на сколько два счета для разных пользователей отличаются я бы делал их одной сущность.
Затем отвечал бы себе на вопрос на сколько комфортно работать с единой сущностью в зависимости от:
Разные ли действия производят над сущностями пользователи и бухгалтеры?
Разные ли представления счетов для бухгалтерии и пользователя?
У нас несколько тех/бизнесовых команд работает над этой системой?
Нужно ли нам горизонтально масштабироваться по производительности?
Чем больше, тем больше вероятность того, что их нужно делить на две.
можно использовать альтернативу — go-arch-lint
как автор тулзы, заодно порекламлю статью на хабре, как им пользоватся:
https://habr.com/ru/articles/751174/
Спасибо за статью, на личном опыте знаю как сложно сделать статью полезной и понятной большому числу людей.
В предложенном варианте есть несколько проблем, которые хотелось бы обсудить:
Публичные свойства агрегата дают не только возможность изменять, но и читать данные, что дает возможность разработчикам обойти правило чтения данных через соответствующие модели. В идеале не должно быть возможности в сервисном слое получить в память экземпляр агрегата на чтение.
Метод репозитория должен представлять транзакционную границу. Если разработчик, работая в сервисном слое, задумывается о транзакциях, значит утекла абстракция и мы уже не можем менять хранилище. Если необходимо транзакционно изменить два агрегата, то либо накосячили при проектировании, либо можно обойтись событийной согласованностью. Менеджер транзакций в ДДД говорит о проблеме.
В идеале лучше иметь отдельный репозиторий на каждый агрегат и отдельно для моделей чтения данных, не смешивая команды и запросы. Паттерн CQRS хорошо подходит для подобных вещей.
Нет ничего о доменных событиях, что практически является основой для общения агрегатов. Именно на событийной согласованности, в противовес транзакционной, строится независимость агрегатов. Сохранение агрегата всегда должно сопровождаться сохранением события. Паттерн outbox идеально вписываться в подобных случаях.
Мне бы еще хотелось добавить, что важно понимать как определить агрегат, сейчас очень популярно использование event storming. На личном опыте могу сказать, что только после знакомства с ним начал понимать суть ДДД и как должен вести себя агрегат с репозиторием.
Спасибо большое за отзыв и предложенное обсуждение.
Полностью согласен с вами это идеальный вариант из книг. Но в своем проекте мы были готовы делать доменный слой правильным, для слоев выше мы разрешили себе делать отступления.
В DDD комьюнити использование транзакций на уровне приложение применимо. Подробное описание можно найти в Implementing Domain-driven Design, Глава 12. Repositories, Managing Transactions или в Patterns, Principles, and Practices of Domain-Driven Design, Глава 20. Repositories, Transaction Management and Units of Work.
Согласен, но каждый паттерн стоит применять по надобности, CQS, а тем более CQRS мне было бы очень тяжело продать бизнесу в рамках нашего проекта в виде стартапа.
Доменные события очень объемная тема, но без них проект может существовать достаточно долго, поэтому я не включил их в статью и доклад. В книги 2002 года Эрика Эванса доменные события не упоминаются и появились только в Domain-Driven Design Reference начале 10-х.
Рад, что event storming вам так зашел.
Domain Driven Design в Go – это почти не больно