Продолжаю читать книгу "Go with domains" и учиться строить DDD-приложения.
Ранее я уже писал о двух правилах построения DDD: вся логика должна быть упрощена до типов, которые имеют поведение и всегда держите валидное состояние домена в одном месте.
Дальше автор пишет про третье правило - домен не должен работать с базой данных (будь то реляционная, in-memory или любая другая БД) напрямую.
У нас есть как минимум три причины чтобы следовать этому:
• Домены по определению описывают сущности, связанные с бизнес-логикой, а не с DB-слоем, и их нужно различать.
• Вероятно, мы захотим хранить данные в базе в более оптимальном виде. Например, если в домене у нас есть тип
[]string, то в DB-слое мы, вероятно, захотим использовать pq.StringArray• В Go нет ORM-решений и магии в виде аннотаций из коробки, что сильно усложняет интеграцию домена с DB-слоем
И пример хорошего кода на основе всех трех правил:
func (g Server) MakeHourAvailable(ctx context.Context, request *trainer.UpdateHourRequest) (*trainer.EmptyResponse, error) {
trainingTime, err := protoTimestampToTime(request.Time)
if err != nil {
return nil, status.Error(codes.InvalidArgument, "unable to parse time")
}
if err := g.hourRepository.UpdateHour(ctx, trainingTime, func(h *hour.Hour) (*hour.Hour, error) {
if err := h.MakeAvailable(); err != nil {
return nil, err
}
return h, nil
}); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &trainer.EmptyResponse{}, nil
}
В 18 строк тут уместилась логика без изменения домена - она вся инкапсулирована в сам домен. У типа, которым мы оперируем -
Hour, есть поведение - MakeAvailable(), которое мы используем. И вся логика работы со слоем базы данных скрыта и так же инкапсулирована с помощью паттерна репозиторий - о котором я расскажу в следующем посте.