Что касается части соединения, на ваш вопрос довольно тривиально ответить, однако для DDD существует множество препятствий из-за текущих языковых возможностей. Но я попробую ..
Хорошо, давайте представим, что мы разрабатываем бэкэнд образовательных курсов с многоязыковой поддержкой, где нам нужно соединить две таблицы и впоследствии сопоставить их с объектом. У нас есть две таблицы (первая содержит данные, не зависящие от языка, а вторая - данные, зависящие от языка). Если вы сторонник репозитория, то у вас будет что-то вроде этого:
// Course represents e.g. calculus, combinatorics, etc.
type Course struct {
ID uint `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Poster string `json:"poster" db:"poster"`
}
type CourseRepository interface {
List(ctx context.Context, localeID uint) ([]Course, error)
}
, а затем реализовать его для sql db у нас будет что-то вроде этого:
type courseRepository struct {
db *sqlx.DB
}
func NewCourseRepository(db *sqlx.DB) (CourseRepository, error) {
if db == nil {
return nil, errors.New("provided db handle to course repository is nil")
}
return &courseRepository{db:db}, nil
}
func (r *courseRepository) List(ctx context.Context, localeID uint) ([]Course, error) {
const query = `SELECT c.id, c.poster, ct.name FROM courses AS c JOIN courses_t AS ct ON c.id = ct.id WHERE ct.locale = $1`
var courses []Course
if err := r.db.SelectContext(ctx, &courses, query, localeID); err != nil {
return nil, fmt.Errorf("courses repostory/problem while trying to retrieve courses from database: %w", err)
}
return courses, nil
}
То же самое относится и к разным связанным объектам. Вам просто нужно терпеливо смоделировать отображение вашего объекта с базовыми данными. Приведу еще один пример.
type City struct {
ID uint `db:"id"`
Country Country `db:"country"`
}
type Country struct {
ID uint `db:"id"`
Name string `db:"name"`
}
// CityRepository provides access to city store.
type CityRepository interface {
Get(ctx context.Context, cityID uint) (*City, error)
}
// Get retrieve city from database by specified id
func (r *cityRepository) Get(ctx context.Context, cityID uint) (*City, error) {
const query = `SELECT
city.id, country.id AS 'country.id', country.name AS 'country.name',
FROM city JOIN country ON city.country_id = country.id WHERE city.id = ?`
city := City{}
if err := r.db.GetContext(ctx, &city, query, cityID); err != nil {
return nil, fmt.Errorf("city repository / problem occurred while trying to retrieve city from database: %w", err)
}
return &city, nil
}
Теперь все выглядит чистым, пока вы не поймете, что Go на самом деле (на данный момент) не поддерживает дженерики, и, кроме того, люди в большинстве ситуаций отговаривают использовать функцию отражения, потому что это замедляет вашу программу. Чтобы окончательно расстроить вас, представьте, что с этого момента вам нужна транзакционная функциональность ....
Если вы пришли с других языков, вы можете попробовать добиться этого с помощью чего-то вроде этого:
// UnitOfWork is the interface that any UnitOfWork has to follow
// the only methods it as are to return Repositories that work
// together to achieve a common purpose/work.
type UnitOfWork interface {
Entities() EntityRepository
OtherEntities() OtherEntityRepository
}
// StartUnitOfWork it's the way to initialize a typed UoW, it has a uowFn
// which is the callback where all the work should be done, it also has the
// repositories, which are all the Repositories that belong to this UoW
type StartUnitOfWork func(ctx context.Context, t Type, uowFn UnitOfWorkFn, repositories ...interface{}) error
// UnitOfWorkFn is the signature of the function
// that is the callback of the StartUnitOfWork
type UnitOfWorkFn func(ctx context.Context, uw UnitOfWork) error
Я намеренно пропустил реализацию, потому что она выглядит чудовищно для sql и заслуживает отдельного вопроса (идея состоит в том, что у единицы работы есть свои версии репозиториев, украшенные запущенным tx под капотом), и после того, как вы преодолеете эту проблему, у вас будет больше или меньше
err = svc.startUnitOfWork(ctx, uow.Write, func(ctx context.Context, uw uow.UnitOfWork) error {
// _ = uw.Entities().Store(entity)
// _ = uw.OtherEntities().Store(otherEntity)
return nil
}, svc.entityRepository, svc.otherEntityRepository)
, так что здесь вы дошли до финала, и в большинстве случаев люди начали говорить, что вы пишете код, который кажется не идиоматическим c, имея в виду что-то вроде , что . Дело в том, что концепции написаны слишком абстрактно, и это философский вопрос, применимо ли материализованное DDD в Golang или вы можете просто частично имитировать c. Если вам нужна гибкость, выберите базу данных один раз и используйте чистый дескриптор db