f67348944e178077cccbe7e9075f4674.png

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

实体是什么

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

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

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

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

为什么使用实体

CRUDok
cd17d5621fccc6f252d980171c6792bc.png
实体

唯一标识

usernameusername

实践

entityfreedom.EntityWorkerentityIdentity() stringPODTOPOGet/Set
type Entity interface {
    //发布领域事件
    DomainEvent(string,interface{},...map[string]string)
    //唯一ID
    Identity() string
    //获取请求运行时对象
    GetWorker() Worker
    SetProducer(string)
    Marshal() []byte
}
4988674470c8c9489310a8490c3b4c06.png
商品的属性和行为

商品实体

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
}

目录

DDD与Go:Golang领域模型-开篇​zhuanlan.zhihu.com
e6c30df7c40b7720d1be0fe2c97631fe.png
DDD与Go:Golang领域模型-六边形架构​zhuanlan.zhihu.com
90e093b46d6bfeb5dc5ed0a17f2c65ec.png
DDD与Go:Golang领域模型-实体​zhuanlan.zhihu.com
ce569b3936c595d7fbea6efc1cf688f4.png
DDD与Go:Golang领域模型-资源库​zhuanlan.zhihu.com
e9baf0b34558cfc628564f11f2895dcc.png
DDD与Go:Golang领域模型-依赖倒置​zhuanlan.zhihu.com
7e310e7538df5fd6c8e04f38be74805e.png
DDD与Go:Golang领域模型-聚合根​zhuanlan.zhihu.com
b1b96ceb3e37ce1fb15019c27bb5f824.png
  • golang领域模型-CQRS
DDD与Go:Golang领域模型-领域事件​zhuanlan.zhihu.com
6e336ff06327005e0624b6dc2e42b133.png

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

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