<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          為 Gopher 打造 DDD 系列:領(lǐng)域模型-資源庫(kù)

          共 6517字,需瀏覽 14分鐘

           ·

          2020-07-31 18:22

          前言: 作為領(lǐng)域模型中最重要的環(huán)節(jié)之一的Repository,其通過對(duì)外暴露接口屏蔽了內(nèi)部的復(fù)雜性,又有其隱式寫時(shí)復(fù)制的巧妙代碼設(shè)計(jì),完美的將DDD中的Repository的概念與代碼相結(jié)合!

          Repository

          資源庫(kù)通常標(biāo)識(shí)一個(gè)存儲(chǔ)的區(qū)域,提供讀寫功能。通常我們將實(shí)體存放在資源庫(kù)中,之后通過該資源庫(kù)來獲取相同的實(shí)體,每一個(gè)實(shí)體都搭配一個(gè)資源庫(kù)。

          如果你修改了某個(gè)實(shí)體,也需要通過資源庫(kù)去持久化。當(dāng)然你也可以通過資源庫(kù)去刪除某一個(gè)實(shí)體。

          資源庫(kù)對(duì)外部是屏蔽了存儲(chǔ)細(xì)節(jié)的,資源庫(kù)內(nèi)部去處理 cache、esdb。數(shù)據(jù)操作流程

          Repository解除了client的巨大負(fù)擔(dān),使client只需與一個(gè)簡(jiǎn)單的、易于理解的接口進(jìn)行對(duì)話,并根據(jù)模型向這個(gè)接口提出它的請(qǐng)求。要實(shí)現(xiàn)所有這些功能需要大量復(fù)雜的技術(shù)基礎(chǔ)設(shè)施,但接口卻很簡(jiǎn)單,而且在概念層次上與領(lǐng)域模型緊密聯(lián)系在一起。

          隱式寫時(shí)復(fù)制

          通常我們通過資源庫(kù)讀取一個(gè)實(shí)體后,再對(duì)這個(gè)實(shí)體進(jìn)行修改。那么這個(gè)修改后的持久化是需要知道實(shí)體的哪些屬性被修改,然后再對(duì)應(yīng)的去持久化被修改的屬性。

          注意商品實(shí)體的changes,商品被修改某個(gè)屬性,對(duì)應(yīng)的Repository就持久化相應(yīng)的修改。這么寫有什么好處呢?如果不這么做,那只能在service里調(diào)用orm指定更新列,但是這樣做的話,Repository的價(jià)值就完全被舍棄了!

          可以說寫時(shí)復(fù)制是Repository和領(lǐng)域模型的橋梁!

          //商品實(shí)體type Goods struct {    changes map[string]interface{}        //被修改的屬性    Name    string//商品名稱  Price   int// 價(jià)格  Stock   int// 庫(kù)存}// SetPrice .func (obj *Goods) SetPrice(price int) {  obj.Price = price  obj.changes["price"] = price //寫時(shí)復(fù)制}
          // SetStock .func (obj *Goods) SetStock(stock int) { obj.Stock = stock obj.changes["stock"] = stock //寫時(shí)復(fù)制}
          //示例func main() { goodsEntity := GoodsRepository.Get(1) goodsEntity.SetPrice(1000) GoodsRepositorySave(goodsEntity) //GoodsRepository 會(huì)內(nèi)部處理商品實(shí)體的changes}

          工廠和創(chuàng)建

          創(chuàng)建商品實(shí)體需要唯一ID和已知的屬性名稱等,可以使用實(shí)體工廠去生成唯一ID和創(chuàng)建,在交給資源庫(kù)去持久化,這也是<<實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)>>的作者推薦的方式,但這種方式更適合文檔型數(shù)據(jù)庫(kù),唯一IDKey和實(shí)體序列化是值。

          “底層技術(shù)可能會(huì)限制我們的建模選擇。例如,關(guān)系數(shù)據(jù)庫(kù)可能對(duì)復(fù)合對(duì)象結(jié)構(gòu)的深度有實(shí)際的限制"(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì):軟件核心復(fù)雜性應(yīng)對(duì)之道 Eric Evans)

          但我們更多的使用的是關(guān)系型數(shù)據(jù)庫(kù),這樣資源庫(kù)就需要?jiǎng)?chuàng)建的行為。實(shí)體的唯一ID就是聚簇主鍵。一個(gè)實(shí)體或許是多張表組成,畢竟我們還要考慮垂直分表。我認(rèn)為DDD的范式和關(guān)系型數(shù)據(jù)庫(kù)范式,后者更重要。有時(shí)候我們還要為Repository 實(shí)現(xiàn)一些統(tǒng)計(jì)select count(*)的功能。

          根據(jù)所使用的持久化技術(shù)和基礎(chǔ)設(shè)施不同,Repository的實(shí)現(xiàn)也將有很大的變化。理想的實(shí)現(xiàn)是向客戶隱藏所有內(nèi)部工作細(xì)節(jié)(盡管不向客戶的開發(fā)人員隱藏這些細(xì)節(jié)),這樣不管數(shù)據(jù)是存儲(chǔ)在對(duì)象數(shù)據(jù)庫(kù)中,還是存儲(chǔ)在關(guān)系數(shù)據(jù)庫(kù)中,或是簡(jiǎn)單地保持在內(nèi)存中,客戶代碼都相同。Repository將會(huì)委托相應(yīng)的基礎(chǔ)設(shè)施服務(wù)來完成工作。將存儲(chǔ)、檢索和查詢機(jī)制封裝起來是Repository實(shí)現(xiàn)的最基本的特性。

          實(shí)踐

          https://github.com/8treenet/freedom/tree/master/example/fshop/adapter/repository

          實(shí)體的緩存

          這個(gè)是緩存組件的接口,可以讀寫實(shí)體,實(shí)體的key 使用必須實(shí)現(xiàn)的Identity 方法。

          • 一級(jí)緩存是基于請(qǐng)求的,首先會(huì)從一級(jí)緩存查找實(shí)體,生命周期是一個(gè)請(qǐng)求的開始和結(jié)束。
          • 二級(jí)緩存是基于redis。
          • 組件已經(jīng)做了冪等的防擊穿處理
          • SetSource設(shè)置持久化的回調(diào)函數(shù),當(dāng)一、二級(jí)緩存未命中,會(huì)讀取回調(diào)函數(shù),并反寫一、二級(jí)緩存。
          // freedom.Entitytype Entity interface {  DomainEvent(string, interface{},...map[string]string)  Identity() string  GetWorker() Worker  SetProducer(string)  Marshal() []byte}
          // infra.EntityCachetype EntityCache interface { //獲取實(shí)體 GetEntity(freedom.Entity) error //刪除實(shí)體緩存 Delete(result freedom.Entity, async ...bool) error //設(shè)置數(shù)據(jù)源 SetSource(func(freedom.Entity) error) EntityCache //設(shè)置前綴 SetPrefix(string) EntityCache //設(shè)置緩存時(shí)間,默認(rèn)5分鐘 SetExpiration(time.Duration) EntityCache //設(shè)置異步反寫緩存。默認(rèn)關(guān)閉,緩存未命中讀取數(shù)據(jù)源后的異步反寫緩存 SetAsyncWrite(bool) EntityCache //設(shè)置防擊穿,默認(rèn)開啟 SetSingleFlight(bool) EntityCache //關(guān)閉二級(jí)緩存. 關(guān)閉后只有一級(jí)緩存生效 CloseRedis() EntityCache}

          以下實(shí)現(xiàn)了一個(gè)商品的資源庫(kù)
          package repository
          import ( "time"
          "github.com/8treenet/freedom/infra/store"
          "github.com/8treenet/freedom/example/fshop/domain/po" "github.com/8treenet/freedom/example/fshop/domain/entity"
          "github.com/8treenet/freedom")
          func init() { freedom.Prepare(func(initiator freedom.Initiator) { initiator.BindRepository(func() *Goods { return &Goods{} }) })}
          // Goods .type Goods struct { freedom.Repository //資源庫(kù)必須繼承,這樣是為了約束 db、redis、http等的訪問 Cache store.EntityCache //依賴注入實(shí)體緩存組件}
          // BeginRequestfunc (repo *Goods) BeginRequest(worker freedom.Worker) { repo.Repository.BeginRequest(worker)
          //設(shè)置緩存的持久化數(shù)據(jù)源,旁路緩存模型,如果緩存未有數(shù)據(jù),將回調(diào)該函數(shù)。 repo.Cache.SetSource(func(result freedom.Entity) error { return findGoods(repo, result) }) //緩存30秒, 不設(shè)置默認(rèn)5分鐘 repo.Cache.SetExpiration(30 * time.Second) //設(shè)置緩存前綴 repo.Cache.SetPrefix("freedom")}
          // Get 通過id 獲取商品實(shí)體.func (repo *Goods) Get(id int) (goodsEntity *entity.Goods, e error) { goodsEntity = &entity.Goods{} goodsEntity.Id = id //注入基礎(chǔ)Entity 包含運(yùn)行時(shí)和領(lǐng)域事件的producer repo.InjectBaseEntity(goodsEntity)
          //讀取緩存, Identity() 會(huì)返回 id,緩存會(huì)使用它當(dāng)key return goodsEntity, repo.Cache.GetEntity(goodsEntity)}
          // Save 持久化實(shí)體.func (repo *Goods) Save(entity *entity.Goods) error { _, e := saveGoods(repo, entity) //寫庫(kù),saveGoods是腳手架生成的函數(shù),會(huì)做寫時(shí)復(fù)制的處理。 //清空緩存 repo.Cache.Delete(entity) return e}
          func (repo *Goods) FindsByPage(page, pageSize int, tag string) (entitys []*entity.Goods, e error) { build := repo.NewORMDescBuilder("id").NewPageBuilder(page, pageSize) //創(chuàng)建分頁(yè)器 e = findGoodsList(repo, po.Goods{Tag: tag}, &entitys, build) if e != nil { return } //注入基礎(chǔ)Entity 包含運(yùn)行時(shí)和領(lǐng)域事件的producer repo.InjectBaseEntitys(entitys) return}
          func (repo *Goods) New(name, tag string, price, stock int) (entityGoods *entity.Goods, e error) { goods := po.Goods{Name: name, Price: price, Stock: stock, Tag: tag, Created: time.Now(), Updated: time.Now()}
          _, e = createGoods(repo, &goods) //寫庫(kù),createGoods是腳手架生成的函數(shù)。 if e != nil { return } entityGoods = &entity.Goods{Goods: goods} repo.InjectBaseEntity(entityGoods) return}

          領(lǐng)域服務(wù)使用倉(cāng)庫(kù)
          package domain
          import ( "github.com/8treenet/freedom/example/fshop/domain/dto" "github.com/8treenet/freedom/example/fshop/adapter/repository" "github.com/8treenet/freedom/example/fshop/domain/aggregate" "github.com/8treenet/freedom/example/fshop/domain/entity" "github.com/8treenet/freedom/infra/transaction"
          "github.com/8treenet/freedom")
          func init() { freedom.Prepare(func(initiator freedom.Initiator) { initiator.BindService(func() *Goods { return &Goods{} }) initiator.InjectController(func(ctx freedom.Context) (service *Goods) { initiator.GetService(ctx, &service) return }) })}
          // Goods 商品領(lǐng)域服務(wù).type Goods struct { Worker freedom.Worker //依賴注入請(qǐng)求運(yùn)行時(shí)對(duì)象。 GoodsRepo repository.Goods //依賴注入商品倉(cāng)庫(kù)}
          // New 創(chuàng)建商品func (g *Goods) New(name string, price int) (e error) { g.Worker.Logger().Info("創(chuàng)建商品") _, e = g.GoodsRepo.New(name, entity.GoodsNoneTag, price, 100) return}
          // Items 分頁(yè)商品列表func (g *Goods) Items(page, pagesize int, tag string) (items []dto.GoodsItemRes, e error) { entitys, e := g.GoodsRepo.FindsByPage(page, pagesize, tag) if e != nil { return }
          for i := 0; i < len(entitys); i++ { items = append(items, dto.GoodsItemRes{ Id: entitys[i].Id, Name: entitys[i].Name, Price: entitys[i].Price, Stock: entitys[i].Stock, Tag: entitys[i].Tag, }) } return}
          // AddStock 增加商品庫(kù)存func (g *Goods) AddStock(goodsId, num int) (e error) { entity, e := g.GoodsRepo.Get(goodsId) if e != nil { return }
          entity.AddStock(num) //增加庫(kù)存 entity.DomainEvent("Goods.Stock", entity) //發(fā)布增加商品庫(kù)存的領(lǐng)域事件 return g.GoodsRepo.Save(entity)}

          項(xiàng)目代碼 https://github.com/8treenet/freedom/tree/master/example/fshop




          推薦閱讀



          學(xué)習(xí)交流 Go 語(yǔ)言,掃碼回復(fù)「進(jìn)群」即可


          站長(zhǎng) polarisxu

          自己的原創(chuàng)文章

          不限于 Go 技術(shù)

          職場(chǎng)和創(chuàng)業(yè)經(jīng)驗(yàn)


          Go語(yǔ)言中文網(wǎng)

          每天為你

          分享 Go 知識(shí)

          Go愛好者值得關(guān)注


          瀏覽 60
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  淫色天天 | 国产无卡无码在线观看视频 | 久久播播 | 韩导航激情亚洲丁香幼导航 | 亚洲午夜在线 |