前言: 作为领域模型中最重要的环节之一的Repository,其通过对外暴露接口屏蔽了内部的复杂性,又有其隐式写时复制的巧妙代码设计,完美的将DDD中的Repository的概念与代码相结合!

Repository

资源库通常标识一个存储的区域,提供读写功能。通常我们将实体存放在资源库中,之后通过该资源库来获取相同的实体,每一个实体都搭配一个资源库。

如果你修改了某个实体,也需要通过资源库去持久化。当然你也可以通过资源库去删除某一个实体。

资源库对外部是屏蔽了存储细节的,资源库内部去处理 cache、es、db。

数据操作流程

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

隐式写时复制

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

注意商品实体的changes,商品被修改某个属性,对应的Repository就持久化相应的修改。这么写有什么好处呢?如果不这么做,那只能在service里调用orm指定更新列,但是这样做的话,Repository的价值就完全被舍弃了!

可以说写时复制是Repository和领域模型的桥梁!//商品实体type Goods struct {changes map[string]interface{} //被修改的属性Name string//商品名称Price int// 价格Stock int// 库存}// SetPrice .func (obj *Goods) SetPrice(price int) {obj.Price = priceobj.changes["price"] = price //写时复制}// SetStock .func (obj *Goods) SetStock(stock int) {obj.Stock = stockobj.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.Entitytype Entity interface {DomainEvent(string, interface{},...map[string]string)Identity() stringGetWorker() WorkerSetProducer(string)Marshal() []byte}// infra.EntityCachetype 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 repositoryimport ("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 //资源库必须继承,这样是为了约束 db、redis、http等的访问Cache store.EntityCache //依赖注入实体缓存组件}// BeginRequestfunc (repo *Goods) BeginRequest(worker freedom.Worker) {repo.Repository.BeginRequest(worker)//设置缓存的持久化数据源,旁路缓存模型,如果缓存未有数据,将回调该函数。repo.Cache.SetSource(func(result freedom.Entity) error {return findGoods(repo, result)})//缓存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 包含运行时和领域事件的producerrepo.InjectBaseEntity(goodsEntity)//读取缓存, Identity() 会返回 id,缓存会使用它当keyreturn 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 包含运行时和领域事件的producerrepo.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 domainimport ("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 商品领域服务.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)}

项目代码 https://github.com/8treenet/freedom/tree/master/example/fshop

商品领域ddd_为 Gopher 打造 DDD 系列:领域模型-资源库相关推荐

  1. 商品领域ddd_DDD第1篇 - 为什么使用DDD?

    认真写文章,用心做分享. 个人网站:yasinshaw.com 公众号:xy的技术圈 先说声抱歉,最近两三个星期都没有产出新的文章了.一方面是忙(懒):另一方面是在想能写什么题材,毕竟日常开发中遇到能 ...

  2. 【转】阿里技术专家详解DDD系列 第二讲 - 应用架构

    填坑.谢谢大家对这个系列的期待,持续更新,欢迎关注此账号. 第一篇内容附地址: 阿里巴巴淘系技术:阿里技术专家详解 DDD 系列 第一讲- Domain Primitive​zhuanlan.zhih ...

  3. 【转】阿里技术专家详解 DDD 系列 第一讲- Domain Primitive

    导读 对于一个架构师来说,在软件开发中如何降低系统复杂度是一个永恒的挑战,无论是 94 年 GoF 的 Design Patterns , 99 年的 Martin Fowler 的 Refactor ...

  4. DDD系列第五讲:聊聊如何避免写流水账代码

    向读者们道歉,由于工作太忙,又对文章质量有追求,所以这篇文章产出速度较慢,但可以向大家保证:文章中的内容都经过了反复实践和踩坑.DDD系列的前几篇文章可以点击文字下方阅读~ DDD系列第一讲 DDD系 ...

  5. DDD系列 实战一 应用设计案例 (golang)

    DDD系列 实战一 应用设计案例 (golang) 基于 ddd 的设计思想, 核心领域需要由纯内存对象+基础设施的抽象的接口组成 独立于外部框架: 比如 web 框架可以是 gin, 也可以是 be ...

  6. 云宏与Rancher达成合作伙伴关系,结合金融领域客户特点联合打造WinGarden 2.0容器云平台...

    11月13日,由Rancher Labs和CNCF联合举办的云原生服务网格(Istio)企业峰会在上海拉开帷幕.大会汇集了国内外诸多著名企业科技负责人和微服务架构师到会,共同分享新一代微服务架构设计. ...

  7. 商品领域ddd_DDD 领域驱动设计-商品建模之路

    1. 业务流程 业务场景:发布商品 业务场景就上面四个字,看起来很简单,但其实具体分析起来,所包含的东西还是蛮多的,整个发布商品过程,就像一个商品诞生的生命周期一样,需要经历各个阶段和过程,直到商品正 ...

  8. 计算机视觉领域稍微容易中的期刊系列(二)1

    计算机视觉领域稍微容易中的期刊系列 (1)适合于图像处理方向的SCI期刊杂志列表 (2)模式识别.计算机视觉领域期刊 表1. 适合于图像处理方向的SCI期刊杂志列表 ISSN 期刊名 出版周期 105 ...

  9. 【转载】阿里技术专家详解DDD系列 第二讲 - 应用架构

    目录 1. 案例分析 1.1 问题1-可维护性能差 1.2 问题2-可拓展性差 1.3 问题3-可测试性能差 1.4 总结分析 2.重构方案 2.1 抽象数据存储层 2.1.1 Repository和 ...

最新文章

  1. 如何用 Parse 和 Swift 搭建一个像 Instagram 那样的应用?(3)
  2. java sleep方法_百战程序员:java线程的休眠和回复
  3. 【图像处理】图像内插“最近邻插值 最近邻内插法(Nearest Neighbour Interpolate)”代码演示(调整图像大小、放大、缩小)
  4. Linux——CentOS安装桌面
  5. SVN:冲突解决 合并别人的修改
  6. 工作177:表单重置项目处理
  7. new/delete与malloc/free
  8. Gulp简介、gulp基本使用步骤、gulp-cli工具、gulpfile.js文件、gulp插件
  9. org.hibernate.HibernateException: 'hibernate.dialect' must be set when no Connection avalable
  10. Com 方法默认参数值设置
  11. package.json 入门
  12. python怎么用for循环找出最大值_从“for in”循环中获取最小值和最大值
  13. 完美解决IE11和补丁安装不上方案
  14. 大文件传输的三种方式
  15. scrapy中代理设置
  16. 贪心法LeetCode算法例子【总】
  17. 关于一些数据集的下载链接
  18. 国产cms java_国产java类cms v3.0.161109
  19. maven项目查看依赖树
  20. Google Instant Apps

热门文章

  1. c++局部对象是什么_程序员每日一题-GCROOT对象
  2. python 散点图 分类_Python | 分类图
  3. 为什么wait/notify必须要和synchronized一起使用?
  4. 第六章 计算机性能测试
  5. zoj 1088 System Overload
  6. 如何选择c语言学习书籍
  7. mysql数据库关联练习_mysql数据库建立数据表的练习(附代码)
  8. ExecutorService源码解读
  9. 动环监控系统接线图_机房动环监控系统报价
  10. js获取ip地址_(原创)Node.JS实战31:大名鼎鼎的Express!