手撸golang GO与微服务 聚合模式之2

老罗话编程 · · 438 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

手撸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)


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:老罗话编程

查看原文:手撸golang GO与微服务 聚合模式之2

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

438 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传