缘起
最近阅读 [Go微服务实战] (刘金亮, 2021.1)
本系列笔记拟采用golang练习之
聚合模式
DDD中有两个非常重要的模式:聚合(Aggregate)和聚合根(AggregateRoot)。聚合是对概念上属于同一实体(entity)或值对象(value object)的封装。而聚合根的含义是指,任何对该聚合的访问都仅到达聚合根。比如Car就是聚合根,虽然Car有轮胎、车灯,但是显然外部访问都只需要访问Car,聚合根确保了聚合的完整性。聚合的规则1. 只有聚合根可被外部访问2. 聚合之间的联系通过主键编码而不是引用3. 单个事务只能创建或更新一个聚合摘自 [Go微服务实战] 刘金亮 2021.1
目标(Day 2)
- 设计符合聚合原则的订单服务
- Day 1的设计太仓促瞎搞了, 推倒重来
设计
- IOrder: 订单接口, 定义订单的数据及操作方法
- IOrderService: 订单服务接口, 定义创建/获取订单的方法
- OrderHeaderDTO: 订单抬头数据, 纯值对象
- OrderItemDTO: 订单产品明细, 纯值对象
- iOrderRepository: 订单存储库接口, 提供订单数据的CRUD以及本地事务管理
- tOrderHeaderEntity: 订单抬头的实体类, 用于ORM
- tOrderItemEntity: 订单明细的实体类, 用于ORM
- tMockOrderRepository: 虚拟的订单存储库, 实现iOrderRepository接口
- tOrderImplement: 订单领域对象的实现, 管理具体的订单数据
- tOrderServiceImplement: 订单服务, 实现IOrderService接口
IOrder.go
订单接口, 定义订单的数据及操作方法
package ordertype IOrder interface {GetHeader() *OrderHeaderDTOSaveHeader(it *OrderHeaderDTO) errorGetItems() []*OrderItemDTOAddItem(item *OrderItemDTO) errorDelItem(item *OrderItemDTO) error}
IOrderService.go
订单服务接口, 定义创建/获取订单的方法
package ordertype IOrderService interface {Create(header *OrderHeaderDTO, items []*OrderItemDTO) IOrderGet(orderId int64) IOrder}
OrderHeaderDTO.go
订单抬头数据, 纯值对象
package ordertype OrderHeaderDTO struct {OrderID int64ConsumerID int64CreateTime int64Status intTimestamp int64}
OrderItemDTO.go
订单产品明细, 纯值对象
package ordertype OrderItemDTO struct {ItemID int64SkuID int64Qty intPrice float64Timestamp int64}
iOrderRepository.go
订单存储库接口, 提供订单数据的CRUD以及本地事务管理
package ordertype iOrderRepository interface {NewOrderID() int64NewItemID() int64LoadOrderHeader(orderID int64) (error, *tOrderHeaderEntity)SaveOrderHeader(it *tOrderHeaderEntity) (error, *tOrderHeaderEntity)LoadOrderItemsByOrderID(orderID int64) (error, []*tOrderItemEntity)LoadOrderItem(itemID int64) (error, *tOrderItemEntity)SaveOrderItem(it *tOrderItemEntity) (error, *tOrderItemEntity)RemoveOrderItem(it *tOrderItemEntity) errorTransaction(func() error) error}
tOrderHeaderEntity.go
订单抬头的实体类, 用于ORM
package ordertype tOrderHeaderEntity struct {OrderID int64ConsumerID int64CreateTime int64Status intTimestamp int64}func (me *tOrderHeaderEntity) Clone() *tOrderHeaderEntity {return &tOrderHeaderEntity{me.OrderID, me.ConsumerID, me.CreateTime, me.Status, me.Timestamp,}}func (me *tOrderHeaderEntity) ToOrderHeader() *OrderHeaderDTO {return &OrderHeaderDTO{me.OrderID, me.ConsumerID, me.CreateTime, me.Status, me.Timestamp,}}func (me *tOrderHeaderEntity) Read(it *OrderHeaderDTO) {me.OrderID = it.OrderIDme.ConsumerID = it.ConsumerIDme.CreateTime = it.CreateTimeme.Status = it.Statusme.Timestamp = it.Timestamp}
tOrderItemEntity.go
订单明细的实体类, 用于ORM
package ordertype tOrderItemEntity struct {ItemID int64OrderID int64SkuID int64Qty intPrice float64Timestamp int64}func (me *tOrderItemEntity) Clone() *tOrderItemEntity {return &tOrderItemEntity{me.ItemID, me.OrderID, me.SkuID, me.Qty, me.Price, me.Timestamp,}}func (me *tOrderItemEntity) ToOrderItemData() *OrderItemDTO {return &OrderItemDTO{me.ItemID, me.SkuID, me.Qty, me.Price, me.Timestamp,}}func (me *tOrderItemEntity) Read(it *OrderItemDTO) {me.ItemID = it.ItemIDme.SkuID = it.SkuIDme.Qty = it.Qtyme.Price = it.Priceme.Timestamp = it.Timestamp}
tMockOrderRepository.go
虚拟的订单存储库, 实现iOrderResponsity接口
package orderimport ("errors""fmt""sync""sync/atomic""time")type tMockOrderRepository struct {rwmutex *sync.RWMutexorders map[int64]*tOrderHeaderEntityitems map[int64]*tOrderItemEntity}func newMockOrderRepository() iOrderRepository {it := new(tMockOrderRepository)it.init()return it}func (me *tMockOrderRepository) init() {me.rwmutex = new(sync.RWMutex)me.orders = make(map[int64]*tOrderHeaderEntity)me.items = make(map[int64]*tOrderItemEntity)}func (me *tMockOrderRepository) LoadOrderHeader(orderID int64) (error, *tOrderHeaderEntity) {me.rwmutex.RLock()defer me.rwmutex.RUnlock()it, ok := me.orders[orderID]if ok {return nil, it.Clone()}return gErrorNotFound, nil}func (me *tMockOrderRepository) SaveOrderHeader(it *tOrderHeaderEntity) (error, *tOrderHeaderEntity) {me.rwmutex.Lock()defer me.rwmutex.Unlock()origin, ok := me.orders[it.OrderID]if ok {if origin.Status != it.Status || origin.Timestamp != it.Timestamp {return gErrorVersionChanged, nil}}it.Timestamp = time.Now().UnixNano()me.orders[it.OrderID] = it.Clone()return nil, it}func (me *tMockOrderRepository) LoadOrderItem(itemID int64) (error, *tOrderItemEntity) {me.rwmutex.RLock()defer me.rwmutex.RUnlock()it, ok := me.items[itemID]if ok {return nil, it.Clone()}return gErrorNotFound, nil}func (me *tMockOrderRepository) SaveOrderItem(it *tOrderItemEntity) (error, *tOrderItemEntity) {me.rwmutex.Lock()defer me.rwmutex.Unlock()origin, ok := me.items[it.ItemID]if ok {if origin.Timestamp != it.Timestamp {return gErrorVersionChanged, nil}}it.Timestamp = time.Now().UnixNano()me.items[it.ItemID] = it.Clone()return nil, it}func (me *tMockOrderRepository) RemoveOrderItem(it *tOrderItemEntity) error {me.rwmutex.Lock()defer me.rwmutex.Unlock()origin, ok := me.items[it.ItemID]if ok {if origin.Timestamp != it.Timestamp {return gErrorVersionChanged}}delete(me.items, it.ItemID)return nil}func (me *tMockOrderRepository) LoadOrderItemsByOrderID(orderID int64) (error, []*tOrderItemEntity) {me.rwmutex.Lock()defer me.rwmutex.Unlock()lst := []*tOrderItemEntity{}for _,v := range me.items {if v.OrderID == orderID {lst = append(lst, v)}}return nil, lst}func (me *tMockOrderRepository) NewOrderID() int64 {return atomic.AddInt64(&gOrderID, 1)}func (me *tMockOrderRepository) NewItemID() int64 {return atomic.AddInt64(&gItemID, 1)}func (me *tMockOrderRepository) Transaction(action func() error) error {fmt.Println("tMockOrderRepository.Transaction begin")e := action()if e != nil {fmt.Printf("tMockOrderRepository.Transaction rollback, e=%v\n", e)} else {fmt.Println("tMockOrderRepository.Transaction commit")}return e}var gErrorNotFound = errors.New("not found")var gErrorVersionChanged = errors.New("version changed")var MockOrderRepository = newMockOrderRepository()var gOrderID = time.Now().UnixNano()var gItemID = time.Now().UnixNano()
tOrderImplement.go
订单领域对象的实现, 管理具体的订单数据
package ordertype tOrderImplement struct {state *tOrderHeaderEntity}func newOrderImplement(order *tOrderHeaderEntity) IOrder {it := new(tOrderImplement)it.init(order)return it}func (me *tOrderImplement) init(order *tOrderHeaderEntity) {me.state = order}func (me *tOrderImplement) GetHeader() *OrderHeaderDTO {return me.state.ToOrderHeader()}func (me *tOrderImplement) SaveHeader(it *OrderHeaderDTO) error {entity := new(tOrderHeaderEntity)entity.Read(it)err, entity := MockOrderRepository.SaveOrderHeader(entity)if err != nil {return err}me.state = entityreturn nil}func (me *tOrderImplement) GetItems() []*OrderItemDTO {err, items := MockOrderRepository.LoadOrderItemsByOrderID(me.state.OrderID)if err != nil {return nil}lst := make([]*OrderItemDTO, len(items))for i,it := range items {lst[i] = it.ToOrderItemData()}return lst}func (me *tOrderImplement) AddItem(item *OrderItemDTO) error {entity := &tOrderItemEntity{}entity.Read(item)entity.ItemID = MockOrderRepository.NewItemID()entity.OrderID = me.state.OrderIDreturn MockOrderRepository.Transaction(func() error {// lock headererr, header := MockOrderRepository.SaveOrderHeader(me.state)if err != nil {return err}me.state = header// save itemerr, _ = MockOrderRepository.SaveOrderItem(entity)return err})}func (me *tOrderImplement) DelItem(item *OrderItemDTO) error {entity := &tOrderItemEntity{}entity.Read(item)entity.OrderID = me.state.OrderIDreturn MockOrderRepository.Transaction(func() error {// lock headererr, header := MockOrderRepository.SaveOrderHeader(me.state)if err != nil {return err}me.state = header// del itemreturn MockOrderRepository.RemoveOrderItem(entity)})}
tOrderServiceImplement.go
订单服务, 实现IOrderService接口
package ordertype tOrderServiceImplement struct {}func newOrderServiceImplement() IOrderService {it := new(tOrderServiceImplement)return it}func (me *tOrderServiceImplement) Create(header *OrderHeaderDTO, items []*OrderItemDTO) IOrder {ret := []IOrder{ nil }_ = MockOrderRepository.Transaction(func() error {hd := new(tOrderHeaderEntity)hd.Read(header)hd.OrderID = MockOrderRepository.NewOrderID()e, he := MockOrderRepository.SaveOrderHeader(hd)if e != nil {return e}for _,v := range items {item := new(tOrderItemEntity)item.Read(v)item.ItemID = MockOrderRepository.NewItemID()item.OrderID = he.OrderIDe, _ = MockOrderRepository.SaveOrderItem(item)if e != nil {return e}}ret[0] = newOrderImplement(he)return nil})return ret[0]}func (me *tOrderServiceImplement) Get(orderId int64) IOrder {e, hd := MockOrderRepository.LoadOrderHeader(orderId)if e != nil {return nil}return newOrderImplement(hd)}var OrderService = newOrderServiceImplement()
(end)
