手撸golang GO与微服务 聚合模式之2
缘起
最近阅读 [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 order
type IOrder interface {
GetHeader() *OrderHeaderDTO
SaveHeader(it *OrderHeaderDTO) error
GetItems() []*OrderItemDTO
AddItem(item *OrderItemDTO) error
DelItem(item *OrderItemDTO) error
}
IOrderService.go
订单服务接口, 定义创建/获取订单的方法
package order
type IOrderService interface {
Create(header *OrderHeaderDTO, items []*OrderItemDTO) IOrder
Get(orderId int64) IOrder
}
OrderHeaderDTO.go
订单抬头数据, 纯值对象
package order
type OrderHeaderDTO struct {
OrderID int64
ConsumerID int64
CreateTime int64
Status int
Timestamp int64
}
OrderItemDTO.go
订单产品明细, 纯值对象
package order
type OrderItemDTO struct {
ItemID int64
SkuID int64
Qty int
Price float64
Timestamp int64
}
iOrderRepository.go
订单存储库接口, 提供订单数据的CRUD以及本地事务管理
package order
type iOrderRepository interface {
NewOrderID() int64
NewItemID() int64
LoadOrderHeader(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) error
Transaction(func() error) error
}
tOrderHeaderEntity.go
订单抬头的实体类, 用于ORM
package order
type tOrderHeaderEntity struct {
OrderID int64
ConsumerID int64
CreateTime int64
Status int
Timestamp 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.OrderID
me.ConsumerID = it.ConsumerID
me.CreateTime = it.CreateTime
me.Status = it.Status
me.Timestamp = it.Timestamp
}
tOrderItemEntity.go
订单明细的实体类, 用于ORM
package order
type tOrderItemEntity struct {
ItemID int64
OrderID int64
SkuID int64
Qty int
Price float64
Timestamp 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.ItemID
me.SkuID = it.SkuID
me.Qty = it.Qty
me.Price = it.Price
me.Timestamp = it.Timestamp
}
tMockOrderRepository.go
虚拟的订单存储库, 实现iOrderResponsity接口
package order
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"time"
)
type tMockOrderRepository struct {
rwmutex *sync.RWMutex
orders map[int64]*tOrderHeaderEntity
items 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 order
type 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 = entity
return 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.OrderID
return MockOrderRepository.Transaction(func() error {
// lock header
err, header := MockOrderRepository.SaveOrderHeader(me.state)
if err != nil {
return err
}
me.state = header
// save item
err, _ = MockOrderRepository.SaveOrderItem(entity)
return err
})
}
func (me *tOrderImplement) DelItem(item *OrderItemDTO) error {
entity := &tOrderItemEntity{}
entity.Read(item)
entity.OrderID = me.state.OrderID
return MockOrderRepository.Transaction(func() error {
// lock header
err, header := MockOrderRepository.SaveOrderHeader(me.state)
if err != nil {
return err
}
me.state = header
// del item
return MockOrderRepository.RemoveOrderItem(entity)
})
}
tOrderServiceImplement.go
订单服务, 实现IOrderService接口
package order
type 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.OrderID
e, _ = 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)