Golang领域模型-实体

前言: 实体具有业务属性、业务逻辑和业务行为,是是实实在在的业务对象。在事件风暴中,我们可以根据命令、操作与事件将业务上紧密结合在一起的多个实体与值对象进行聚合形成聚合根。

实体是什么

虽然数据库的设计占据了主导地位(这个是没错的),但开发者也不应该只关注数据,而且要关注模型。数据+行为= 模型,实体就是含有领域概念的模型。它是一个唯一的东西,在相当长的时间里数据状态在持续地变化,并且一定有唯一键,这区别于值对象。注意的是如果非要用表结构里的一条含有主键的数据去理解实体也是可以的,但不少情况下可能是有多个表或者k/v数据来组成的一个实体。

实体、值对象与数据模型示例

实体、值对象与数据模型示例

实体是可变的,是变性,每个用户实体都有自己的唯一性,我们用id来进行区分。值对象是不变的,是共性,实体都有相同的值对象,例如国家等信息。我们以此区分好实体与值对象。

为什么使用实体

如果开发者设计系统时,并没有建模而是直接开始设计表结构和它的CRUD,其实这也算是建模的一种,但这种方式仅仅能应对简单的模型。这样的操作当更复杂的业务和更复杂的模型出现后是驾驭不了的。假设我们做10平米的卧室设计,那么简单的量一下桌椅床就ok。但如果我们设计一个200平米的化学实验室的时候,这个简单的摆放可能搞不好会导致爆炸吧~

实体

唯一标识

在实体的设计早期,我们将关注点都放在了实体的身份唯一性、属性、行为上。同时还应该关注对实体的查询和创建,我们首先要考虑实体的本质特征,特别是实体的唯一标识符,它是一个关系节点。比如用户这个实体username是不是唯一标识,如果不是唯一,是不是可以通过username去查找。

实践

https://github.com/8treenet/freedom/tree/master/example/fshop/domain/entity

  1. 所有的entity都必须继承freedom.Entity 接口, 这里为实体注入了领域事件和运行时的Worker对象。
  2. 所有的entity都必须重写Identity() string 方法。
  3. 实体可以选择的继承PO或者DTOPO是通过脚手架生成的表模型属性和Get/Set方法。
type Entity interface {
    //发布领域事件
    DomainEvent(string,interface{},...map[string]string)
    //唯一ID
    Identity() string
    //获取请求运行时对象
    GetWorker() Worker
    SetProducer(string)
    Marshal() []byte
}
商品的属性和行为

商品实体

package entity

import (
    "errors"
    "strconv"

    "github.com/8treenet/freedom"
    "github.com/8treenet/freedom/example/fshop/domain/po"
)

const (
    //热销
    GoodsHotTag = "HOT"
    //新品
    GoodsNewTag  = "NEW"
    GoodsNoneTag = "NONE"
)

// 商品实体
type Goods struct {
    freedom.Entity //继承实体基类接口
    po.Goods //继承持久化的商品模型,包含了商品的列和属性方法
}

// Identity 唯一
func (g *Goods) Identity() string {
    return strconv.Itoa(g.Id)
}

// CutStock 扣库存
func (g *Goods) CutStock(num int) error {
    if num > g.Stock {
        return errors.New("库存不足")
    }
    g.AddStock(-num) //po对象的方法,增加库存
    return nil
}

// MarkedTag 为商品打tag
func (g *Goods) MarkedTag(tag string) error {
    if tag != GoodsHotTag && tag != GoodsNewTag && tag != GoodsNoneTag {
        return errors.New("Tag doesn't exist")
    }
    g.SetTag(tag) //po对象的方法,设置tag
    return nil
}

用户实体

package entity

import (
    "errors"
    "strconv"

    "github.com/8treenet/freedom"
    "github.com/8treenet/freedom/example/fshop/domain/po"
)

// 用户实体
type User struct {
    freedom.Entity //继承实体基类接口
    po.User  //继承持久化的用户模型,包含了用户的列和属性方法
}

// Identity 唯一
func (u *User) Identity() string {
    return strconv.Itoa(u.Id)
}

// ChangePassword 修改密码
func (u *User) ChangePassword(newPassword, oldPassword string) error {
       //判断旧密码是否正确
    if u.Password != oldPassword {
        return errors.New("Password error")
    }
    u.SetPassword(newPassword) //po对象的方法,可以设置密码
    return nil
}

订单实体

package entity

import (
    "github.com/8treenet/freedom"
    "github.com/8treenet/freedom/example/fshop/domain/po"
)

const (
    OrderStatusPAID       = "PAID" //付款
    OrderStatusNonPayment = "NON_PAYMENT" //未付款
    OrderStatusShipment   = "SHIPMENT" //发货
)

// 订单实体
type Order struct {
    freedom.Entity //继承实体基类接口
    po.Order  //继承持久化的订单模型,包含了订单的列和属性方法
    Details []*po.OrderDetail  //定义订单商品详情成员变量,一个订单包含多个商品
}

// Identity 唯一
func (o *Order) Identity() string {
    return o.OrderNo
}

// AddOrderDetal 增加订单详情
func (o *Order) AddOrderDetal(detal *po.OrderDetail) {
    o.Details = append(o.Details, detal) //增加订单详情,repository会做持久化处理
}

// Pay 付款
func (o *Order) Pay() {
    o.SetStatus(OrderStatusPAID) //po对象的方法,设置状态
}

// Shipment 发货
func (o *Order) Shipment() {
    o.SetStatus(OrderStatusShipment) //po对象的方法,设置状态
}

// IsPay 是否支付
func (o *Order) IsPay() bool {
        //判断是否付款
    if o.Status != OrderStatusPAID {
        return false
    }
    return true
}

目录

  • golang领域模型-开篇
  • golang领域模型-六边形架构
  • golang领域模型-实体
  • golang领域模型-资源库
  • golang领域模型-依赖倒置
  • golang领域模型-聚合根
  • golang领域模型-CQRS
  • golang领域模型-领域事件

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

PS:关注公众号《从菜鸟到大佬》,发送消息“加群”或“领域模型”,加入DDD交流群,一起切磋DDD与代码的艺术!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343