2 回答

TA貢獻1752條經驗 獲得超4個贊
至于加入部分,您的問題很容易回答,但是對于 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 {
if err == sql.ErrNoRows {
return nil, ErrNoCityEntity
}
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 來說看起來很可怕并且值得提出自己的問題(這個想法是工作單元的存儲庫版本在引擎蓋下用 start 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)
所以在這里你到達了決賽,在大多數情況下,人們開始說你編寫的代碼似乎不習慣引用類似的東西。關鍵是概念寫得太抽象了,物化 DDD 是否適用于 Golang 還是您可以部分模仿它是一個哲學問題。如果您想要靈活性,請選擇一次數據庫并使用純數據庫句柄進行操作

TA貢獻2012條經驗 獲得超12個贊
根據您要讀取的數據,解決方案會有所不同:
如果您要連接的表形成一個單一的聚合,那么只需將它們連接到您的查詢中并始終返回并存儲完整的聚合。在這種情況下,您只有根實體的存儲庫。這可能不是您的情況,因為您說您有要加入的其他實體的存儲庫(除非您有設計問題)。
如果要加入的表屬于不同的有界上下文,則不應加入它們。更好的方法是在每個有界上下文上提交一個查詢,以便它們保持解耦。這些多個查詢將來自不同的地方,具體取決于您的架構:直接來自客戶端、來自 API 網關、來自某種應用程序服務等。
如果表屬于單個有界上下文,但來自多個聚合,那么最簡潔的方法是遵循 CQRS(命令/查詢隔離)。簡單來說,您為查詢定義了一個特定接口,其中包含您正在實現的用例所需的輸入和輸出。這種分離使您擺脫了在嘗試使用 Commands 基礎結構進行查詢時發現的限制(您擁有的 1 對 1 實體/存儲庫關系)。此查詢接口的簡單實現可能是對現有表進行連接的查詢。這快速且易于實現,但這意味著您的命令和查詢在代碼中是分開的,而不是在數據庫級別。理想情況下,您會在數據庫中創建一個(非規范化的)讀取模型表,包含該特定查詢所需的所有列,并在每次更新源表之一時更新(這通常通過域事件完成)。這允許您使用正確的列、數據格式和索引來優化您的查詢表,但作為一個缺點,它會在寫入和讀取模型之間引入一些復雜性和最終一致性。
- 2 回答
- 0 關注
- 126 瀏覽
添加回答
舉報