手擼golang GO與微服務 聚合模式之2

手擼golang GO與微服務 聚合模式之2golang

緣起

最近閱讀 [Go微服務實戰] (劉金亮, 2021.1)
本系列筆記擬採用golang練習之app

聚合模式

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

訂單擡頭數據, 純值對象atom

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以及本地事務管理code

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)

相關文章
相關標籤/搜索