前言: 实体具有业务属性、业务逻辑和业务行为,是是实实在在的业务对象。在事件风暴中,我们可以根据命令、操作与事件将业务上紧密结合在一起的多个实体与值对象进行聚合形成聚合根。
实体是什么
虽然数据库的设计占据了主导地位(这个是没错的),但开发者也不应该只关注数据,而且要关注模型。数据+行为= 模型,实体就是含有领域概念的模型。它是一个唯一的东西,在相当长的时间里数据状态在持续地变化,并且一定有唯一键,这区别于值对象。注意的是如果非要用表结构里的一条含有主键的数据去理解实体也是可以的,但不少情况下可能是有多个表或者k/v数据来组成的一个实体。
实体、值对象与数据模型示例
实体是可变的,是变性,每个用户实体都有自己的唯一性,我们用id来进行区分。值对象是不变的,是共性,实体都有相同的值对象,例如国家等信息。我们以此区分好实体与值对象。
为什么使用实体
CRUDok
唯一标识
usernameusername
实践
entityfreedom.EntityWorkerentityIdentity() stringPODTOPOGet/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
}
目录
DDD与Go:Golang领域模型-开篇zhuanlan.zhihu.com- golang领域模型-CQRS
项目代码 https://github.com/8treenet/freedom/tree/master/example/fshop
PS:关注公众号《从菜鸟到大佬》,发送消息“加群”或“领域模型”,加入DDD交流群,一起切磋DDD与代码的艺术!