亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

go中的存儲庫模式和聯接表

go中的存儲庫模式和聯接表

Go
素胚勾勒不出你 2022-06-06 15:47:50
我目前正在嘗試圍繞領域驅動設計、實體、服務、存儲庫構建我的應用程序...所有基本的 crud 操作都很簡單,基本上 1 個實體 => 1 個表 => 1 個存儲庫 => 1 個服務但我想不出處理兩個實體之間連接表的最干凈的方法??梢酝ㄟ^聯接內部的表進行 1 次查詢,這將是“干凈的”(可以這么說),但效率不高,因為簡單的聯接會導致一次查詢。在這種模式下,表連接在哪里?我一直在考慮現在構建可以封裝答案的實體,但這會有效地為 1 個查詢創建 1 個實體 + 存儲庫......我還認為將多個實體合并到一個界面中可能會部分解決它,但它會導致我的實體出現許多空參數(在執行連接時很少需要來自所有標簽的 ALL 字段)解決這個問題的正確方法/模式是什么,適合 DDD 或至少是干凈的?-- 編輯示例:type User struct {    ID          int       `db:"id"`    ProjectID      int    `db:"project_id"`    RoleID      int       `db:"role_id"`    Email       string    `db:"email"`    FirstName   string    `db:"first_name"`    LastName    string    `db:"last_name"`    Password    string    `db:"password"`}type UserRepository interface {    FindById(int) (*User, error)    FindByEmail(string) (*User, error)    Create(user *User) error    Update(user *User) error    Delete(int) errorr}type Project struct {    ID          int       `db:"id"``    Name   string    `db:"name"`    Description    string    `db:"description"`}在這里,我有一個簡單的用戶存儲庫。我對“項目”表有類似的東西??梢詣摻ū恚@取項目的所有信息,刪除等。如您所見,UserID 具有其所屬項目 ID 的外鍵。我的問題是當我需要從用戶那里檢索所有信息時,說“項目名稱”和描述。(我現實表/實體有更多的參數)我需要在 user.project_id 和 project.id 中做一個簡單的連接,并在一個查詢中檢索用戶 + 項目名稱 + 描述的所有信息。有時它更復雜,因為會有 3-4 個實體像這樣鏈接。(用戶、項目、項目附加信息、角色等)當然,我可以進行 N 個查詢,每個實體一個。user := userRepo.Find(user_id)project := projectRepo.FindByuser(user.deal_id)這將“工作”,但我試圖找到一種方法在一個查詢中做到這一點。因為 user.project_id 和 project.id 上的簡單 sql 連接會在查詢中為我提供所有數據。
查看完整描述

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 還是您可以部分模仿它是一個哲學問題。如果您想要靈活性,請選擇一次數據庫并使用純數據庫句柄進行操作


查看完整回答
反對 回復 2022-06-06
?
繁花如伊

TA貢獻2012條經驗 獲得超12個贊

根據您要讀取的數據,解決方案會有所不同:

如果您要連接的表形成一個單一的聚合,那么只需將它們連接到您的查詢中并始終返回并存儲完整的聚合。在這種情況下,您只有根實體的存儲庫。這可能不是您的情況,因為您說您有要加入的其他實體的存儲庫(除非您有設計問題)。

如果要加入的表屬于不同的有界上下文,則不應加入它們。更好的方法是在每個有界上下文上提交一個查詢,以便它們保持解耦。這些多個查詢將來自不同的地方,具體取決于您的架構:直接來自客戶端、來自 API 網關、來自某種應用程序服務等。

如果表屬于單個有界上下文,但來自多個聚合,那么最簡潔的方法是遵循 CQRS(命令/查詢隔離)。簡單來說,您為查詢定義了一個特定接口,其中包含您正在實現的用例所需的輸入和輸出。這種分離使您擺脫了在嘗試使用 Commands 基礎結構進行查詢時發現的限制(您擁有的 1 對 1 實體/存儲庫關系)。此查詢接口的簡單實現可能是對現有表進行連接的查詢。這快速且易于實現,但這意味著您的命令和查詢在代碼中是分開的,而不是在數據庫級別。理想情況下,您會在數據庫中創建一個(非規范化的)讀取模型表,包含該特定查詢所需的所有列,并在每次更新源表之一時更新(這通常通過域事件完成)。這允許您使用正確的列、數據格式和索引來優化您的查詢表,但作為一個缺點,它會在寫入和讀取模型之間引入一些復雜性和最終一致性。


查看完整回答
反對 回復 2022-06-06
  • 2 回答
  • 0 關注
  • 126 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號