golang领域模型-资源库

八叉树 · · 502 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

Repository

资源库通常标识一个存储的区域,提供读写功能。
通常我们将实体存放在资源库中,之后通过该资源库来获取相同的实体,每一个实体都搭配一个资源库。
如果你修改了某个实体,也需要通过资源库去持久化。当然你也可以通过资源库去删除某一个实体。
资源库对外部是屏蔽了存储细节的,资源库内部去处理 cache、es、db。



Repository解除了客户的巨大负担,使客户只需与一个简单的、易于理解的接口进行对话,并根据模型向这个接口提出它的请求。要实现所有这些功能需要大量复杂的技术基础设施,但接口很简单,而且在概念层次上与领域模型紧密联系在一起。

隐式写时复制

通常我们通过资源库读取一个实体后,在对这个实体进行修改。那么这个修改后的持久化是需要知道实体的某些属性被修改,在对应的去持久化被修改的属性。

//商品实体
type Goods struct {
    changes map[string]interface{}        //被修改的属性
    Name    string  //商品名称
    Price   int      // 价格
    Stock   int     // 库存
}
// SetPrice .
func (obj *Goods) SetPrice(price int) {
    obj.Price = price
    obj.changes["price"] = price //写时复制
}

// SetStock .
func (obj *Goods) SetStock(stock int) {
    obj.Stock = stock
    obj.changes["stock"] = stock //写时复制
}

//示例
func main() {
    goodsEntity := GoodsRepository.Get(1) 
    goodsEntity.SetPrice(1000)
    GoodsRepositorySave(goodsEntity) //GoodsRepository 会内部处理商品实体的changes
}

工厂和创建

创建商品实体需要唯一ID和已知的属性名称等,可以使用实体工厂去生成唯一ID和创建,在交给资源库去持久化,这也是<<实现领域驱动设计>>的作者推荐的方式,但这种方式更适合文档型数据库,唯一ID是Key和实体序列化是值。

“底层技术可能会限制我们的建模选择。例如,关系数据库可能对复合对象结构的深度有实际的限制"(领域驱动设计:软件核心复杂性应对之道 Eric Evans)

但我们更多的使用的是关系型数据库,这样资源库就需要创建的行为。实体的唯一ID就是聚簇主键。一个实体或许是多张表组成,毕竟我们还要考虑垂直分表。我认为DDD的范式和关系型数据库范式,后者更重要。有时候我们还要为Repository 实现一些统计select count(*)的功能。

根据所使用的持久化技术和基础设施不同,Repository的实现也将有很大的变化。理想的实现是向客户隐藏所有内部工作细节(尽管不向客户的开发人员隐藏这些细节),这样不管数据是存储在对象数据库中,还是存储在关系数据库中,或是简单地保持在内存中,客户代码都相同。Repository将会委托相应的基础设施服务来完成工作。将存储、检索和查询机制封装起来是Repository实现的最基本的特性。

实践

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

实体的缓存

这个是缓存组件的接口,可以读写实体,实体的key 使用必须实现的Identity 方法。

  • 一级缓存是基于请求的,首先会从一级缓存查找实体,生命周期是一个请求的开始和结束。
  • 二级缓存是基于redis。
  • 组件已经做了幂等的防击穿处理。
  • SetSource设置持久化的回调函数,当一、二级缓存未命中,会读取回调函数,并反写一、二级缓存。
// freedom.Entity
type Entity interface {
    DomainEvent(string, interface{},...map[string]string)
    Identity() string
    GetWorker() Worker
    SetProducer(string)
    Marshal() []byte
}

// infra.EntityCache
type EntityCache interface {
    //获取实体
    GetEntity(freedom.Entity) error
    //删除实体缓存
    Delete(result freedom.Entity, async ...bool) error
    //设置数据源
    SetSource(func(freedom.Entity) error) EntityCache
    //设置前缀
    SetPrefix(string) EntityCache
    //设置缓存时间,默认5分钟
    SetExpiration(time.Duration) EntityCache
    //设置异步反写缓存。默认关闭,缓存未命中读取数据源后的异步反写缓存
    SetAsyncWrite(bool) EntityCache
    //设置防击穿,默认开启
    SetSingleFlight(bool) EntityCache
    //关闭二级缓存. 关闭后只有一级缓存生效
    CloseRedis() EntityCache
}

以下实现了一个商品的资源库

package repository

import (
    "time"

    "github.com/8treenet/freedom/infra/store"

    "github.com/8treenet/freedom/example/fshop/adapter/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 //资源库必须继承,这样是为了约束 db、redis、http等的访问
    Cache store.EntityCache //依赖注入实体缓存组件
}

// BeginRequest
func (repo *Goods) BeginRequest(worker freedom.Worker) {
    repo.Repository.BeginRequest(worker)

    //设置缓存的持久化数据源,旁路缓存模型,如果缓存未有数据,将回调该函数。
    repo.Cache.SetSource(func(result freedom.Entity) error {
        return findGoods(repo, result) //读库,findGoods是脚手架生成的函数
    })
    //缓存30秒, 不设置默认5分钟
    repo.Cache.SetExpiration(30 * time.Second)
    //设置缓存前缀
    repo.Cache.SetPrefix("freedom")
}

// Get 通过id 获取商品实体.
func (repo *Goods) Get(id int) (goodsEntity *entity.Goods, e error) {
    goodsEntity = &entity.Goods{}
    goodsEntity.Id = id
    //注入基础Entity 包含运行时和领域事件的producer
    repo.InjectBaseEntity(goodsEntity)

    //读取缓存, Identity() 会返回 id,缓存会使用它当key
    return goodsEntity, repo.Cache.GetEntity(goodsEntity)
}

// Save 持久化实体.
func (repo *Goods) Save(entity *entity.Goods) error {
    _, e := saveGoods(repo, entity) //写库,saveGoods是脚手架生成的函数,会做写时复制的处理。
    //清空缓存
    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) //创建分页器
    e = findGoodsList(repo, po.Goods{Tag: tag}, &entitys, build)
    if e != nil {
        return
    }
    //注入基础Entity 包含运行时和领域事件的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)  //写库,createGoods是脚手架生成的函数。
    if e != nil {
        return
    }
    entityGoods = &entity.Goods{Goods: goods}
    repo.InjectBaseEntity(entityGoods)
    return
}

领域服务使用仓库

package domain

import (
    "github.com/8treenet/freedom/example/fshop/adapter/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 商品领域服务.
type Goods struct {
    Worker    freedom.Worker   //依赖注入请求运行时对象。
    GoodsRepo repository.Goods //依赖注入商品仓库
}

// New 创建商品
func (g *Goods) New(name string, price int) (e error) {
    g.Worker.Logger().Info("创建商品")
    _, e = g.GoodsRepo.New(name, entity.GoodsNoneTag, price, 100)
    return
}

// Items 分页商品列表
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 增加商品库存
func (g *Goods) AddStock(goodsId, num int) (e error) {
    entity, e := g.GoodsRepo.Get(goodsId)
    if e != nil {
        return
    }

    entity.AddStock(num) //增加库存
    entity.DomainEvent("Goods.Stock", entity) //发布增加商品库存的领域事件
    return g.GoodsRepo.Save(entity)
}

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:八叉树

查看原文:golang领域模型-资源库

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

502 次点击  ∙  1 赞  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传