Спасибо большое за отзыв и предложенное обсуждение.
Полностью согласен с вами это идеальный вариант из книг. Но в своем проекте мы были готовы делать доменный слой правильным, для слоев выше мы разрешили себе делать отступления.
В 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-х.
Поделюсь как бы я делал. При старте разработки, когда необходимости быстро получить готовый продукт и когда не понятно на сколько два счета для разных пользователей отличаются я бы делал их одной сущность. Затем отвечал бы себе на вопрос на сколько комфортно работать с единой сущностью в зависимости от:
Разные ли действия производят над сущностями пользователи и бухгалтеры?
Разные ли представления счетов для бухгалтерии и пользователя?
У нас несколько тех/бизнесовых команд работает над этой системой?
Нужно ли нам горизонтально масштабироваться по производительности?
Чем больше, тем больше вероятность того, что их нужно делить на две.
Если под маппингом имеется в виду Домен <-> Слой приложение (Handler, Endpoint, Controller, RPC, CLI-command и другое), То слой приложение будет отдавать ответ в нужном виде и формате (json, xml и др.) или вы предлагаете что данные из Домена можно сразу в json положить и отдать пользователю?
Если я неправильно поняла о каком именно маппинге говорится, уточните пожалуйста)
Как вариант, но хочу заметит в InTx(ctx context.Context, f func(userRepo, orderRepo, otherRepos...)) будут класться все репозитории проекта. В примере не увидел, что tx сохраняется в контекст, поэтому могут возникнуть сложности при работе с вложенными сценариями.
Кладу интерфейс в домен по примеру Из синей книжки, Implementing Domain-Driven Designпример, Principles, Practices and Patterns of Domain-Driven Design, и примеру от ThreeDots. А вот почему интерфейс лежит в домене - отличный вопрос. Могу предположить интерфейс таким образом явно обозначениет, что мы можем делать с моделью не собирая общую картину изучая интерфейсы с одним методом в разных местах. Плюс в Java интерфейсы работают по другому, нам нужно указать интерфес явно при реализации класса.
Замечу, что реализация репозитория лежит на уроне пирложения, инфраструктуры или адаптера, в зависимости от вами выбранной способа разбиения на слои.
Тем не менее общий интерфейс в домене не мешает использовать небольшие интерфейсы для каждого места использования. UserRepo свободно конвертируется в интерфейс рядом с местом использования.
// В домене
type UserRepo interface {
GetByID(...)
Save(...)
}
// В месте использования
type userGetter interface {
GetByID(...)
}
По второй части вопроса. Книгу "Чистая Архитектура" читал давно и точно сопоставить слои с ней я не смогу, но слово usecase используется не только применительно к Чистой Архитектуре. У нас в приложение usecase находятся на application уровне. Можно попытаться привязать наши usecase к концепту command из CQRS или Application Service из DDD. usecase в данном случае точно, не контроллер, т.к. входными данными является узкие DTO, а не http.Request, не данные из cli и не gRPC.
Для таких случаев используем паттерн transaction outbox. Он помогает избежать ситуации когда данные сохранились, но сообщение не отправилось в очередь. Или наоборот - сообщение отправилось, а данные не сохранились.
Паттерн использует транзакцию для атомарного сохранения в БД обновляемых данных и сообщений для очереди. После этого сообщение вычитывается из БД и отправляется в очередь.
Спасибо большое за отзыв и предложенное обсуждение.
Полностью согласен с вами это идеальный вариант из книг. Но в своем проекте мы были готовы делать доменный слой правильным, для слоев выше мы разрешили себе делать отступления.
В 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 вам так зашел.
Простой ответ: если два разных контекст - значит две разные сущности.
Но согласен с @Yakamoz, нужно больше вводных.
Поделюсь как бы я делал. При старте разработки, когда необходимости быстро получить готовый продукт и когда не понятно на сколько два счета для разных пользователей отличаются я бы делал их одной сущность.
Затем отвечал бы себе на вопрос на сколько комфортно работать с единой сущностью в зависимости от:
Разные ли действия производят над сущностями пользователи и бухгалтеры?
Разные ли представления счетов для бухгалтерии и пользователя?
У нас несколько тех/бизнесовых команд работает над этой системой?
Нужно ли нам горизонтально масштабироваться по производительности?
Чем больше, тем больше вероятность того, что их нужно делить на две.
Если под маппингом имеется в виду Домен <-> Слой приложение (Handler, Endpoint, Controller, RPC, CLI-command и другое),
То слой приложение будет отдавать ответ в нужном виде и формате (json, xml и др.) или вы предлагаете что данные из Домена можно сразу в json положить и отдать пользователю?
Если я неправильно поняла о каком именно маппинге говорится, уточните пожалуйста)
Как вариант, но хочу заметит в
InTx(ctx context.Context, f func(userRepo, orderRepo, otherRepos...))
будут класться все репозитории проекта.В примере не увидел, что tx сохраняется в контекст, поэтому могут возникнуть сложности при работе с вложенными сценариями.
Кладу интерфейс в домен по примеру Из синей книжки, Implementing Domain-Driven Designпример, Principles, Practices and Patterns of Domain-Driven Design, и примеру от ThreeDots.
А вот почему интерфейс лежит в домене - отличный вопрос. Могу предположить интерфейс таким образом явно обозначениет, что мы можем делать с моделью не собирая общую картину изучая интерфейсы с одним методом в разных местах.
Плюс в Java интерфейсы работают по другому, нам нужно указать интерфес явно при реализации класса.
Замечу, что реализация репозитория лежит на уроне пирложения, инфраструктуры или адаптера, в зависимости от вами выбранной способа разбиения на слои.
Тем не менее общий интерфейс в домене не мешает использовать небольшие интерфейсы для каждого места использования.
UserRepo свободно конвертируется в интерфейс рядом с местом использования.
По второй части вопроса.
Книгу "Чистая Архитектура" читал давно и точно сопоставить слои с ней я не смогу, но слово
usecase
используется не только применительно к Чистой Архитектуре.У нас в приложение
usecase
находятся на application уровне. Можно попытаться привязать нашиusecase
к концептуcommand
из CQRS или Application Service из DDD.usecase
в данном случае точно, не контроллер, т.к. входными данными является узкие DTO, а не http.Request, не данные из cli и не gRPC.Для таких случаев используем паттерн transaction outbox. Он помогает избежать ситуации когда данные сохранились, но сообщение не отправилось в очередь. Или наоборот - сообщение отправилось, а данные не сохранились.
Паттерн использует транзакцию для атомарного сохранения в БД обновляемых данных и сообщений для очереди. После этого сообщение вычитывается из БД и отправляется в очередь.