Pull to refresh

Comments 7

Почему вы отправляете в очередь информацию до того как транзакции зафиксировалась?

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

Для таких случаев используем паттерн transaction outbox. Он помогает избежать ситуации когда данные сохранились, но сообщение не отправилось в очередь. Или наоборот - сообщение отправилось, а данные не сохранились.

Паттерн использует транзакцию для атомарного сохранения в БД обновляемых данных и сообщений для очереди. После этого сообщение вычитывается из БД и отправляется в очередь.

А почему у вас интерфейсы репозиториев лежат в domain слое, ведь в go принято объявлять интерфейсы в месте использования? Плюс, судя по названиям хандлеров "useсase", это у вас чистая архитектура и в домене должны быть только модели и бизнес правила, а не информация о хранилище. Юзкейсы у вас больше похожи на контроллеры и довольно странно, что fast_purchase зависит от других контроллеров, может было бы удобнее вынести логику из них в еще один слой и уже из него код переиспользовать в юзкейсах. Не придраться ради, понятно, что проект демонстрационный, просто интересуюсь структура проектов на go.

Кладу интерфейс в домен по примеру Из синей книжки, 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.

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

код
type UserRepo interface {
    GetByID(...)
    Save(...)
}


func (t *TX) InTx(ctx context.Context, f func(repo UserRepo)) error {
	pool, ok := t.fb.(*DB)
	tx, err := pool.Begin(ctx)
	defer tx.Rollback(ctx)
	f(ctx, NewUserRepo(tx))
	tx.Commit(ctx)
}

есть свои минусы, например то, что в замыкании можно случайно вызвать репозиторий у которого коннект не в транзакции

Как вариант, но хочу заметит в InTx(ctx context.Context, f func(userRepo, orderRepo, otherRepos...)) будут класться все репозитории проекта.
В примере не увидел, что tx сохраняется в контекст, поэтому могут возникнуть сложности при работе с вложенными сценариями.

Sign up to leave a comment.