Implement Domain Object in Golang

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

序言

筆者在《軟體設計的演變過程》一文中,將通訊系統軟體的DDD分層模型最終演化為五層模型,即調度層(Schedule)、事務層(Transaction DSL)、環境層(Context)、領域層(Domain)和基礎設施層(Infrastructure),我們簡單回顧一下:


ddd-layer-with-dci-dsl.png
  1. 調度層:維護UE的狀態模型,只包括業務的本質狀態,將接收到的訊息派發給事務層。
  2. 事務層:對應一個商務程序,比如UE Attach,將各個同步訊息或非同步訊息的處理組合成一個事務,當事務失敗時,進行復原。當事務層收到調度層的訊息後,委託環境層的Action進行處理。
  3. 環境層:以Action為單位,處理一條同步訊息或非同步訊息,將Domain層的領域對象cast成合適的role,讓role互動起來完成商務邏輯。
  4. 領域層:不僅包括領域對象及其之間關係的建模,還包括對象的角色role的顯式建模。
  5. 基礎實施層:為其他層提供通用的技術能力,比如訊息通訊機制、對象持久化機制和通用的演算法等。

對於業務來說,事務層和領域層都非常重要。筆者在《Golang事務模型》一文中重點討論了事務層,本文主要闡述領域層的實現技術,將通過一個案例逐步展開。

本文使用的案例源自MagicBowen的一篇熱文《DCI in C++》,並做了一些修改,目的是將Golang版領域對象的主要實現技術儘可能流暢的呈現給讀者。

領域對象的實現

假設有這樣一種情境:類比人和機器人製造產品。人製造產品會消耗吃飯得到的能量,缺乏能量後需要再吃飯補充;而機器人製造產品會消耗電能,缺乏能量後需要再充電。這裡人和機器人在工作時都是一名工人,工作的流程是一樣的,但是區別在於依賴的能量消耗和擷取方式不同。

領域模型

通過對情境進行分析,我們根據組合式設計的基本思想得到一個領域模型:


human-robot.png

實體設計

從領域模型中可以看出,角色Worker既可以組合在領域對象Human中,又可以組合在領域對象Robot中,可見領域對象和角色是兩個不同的變化方向,於是domain的子目錄結構為:


object-role-dir.png

role的實現

Energy

Energy是一個抽象role,在Golang中是一個interface。它包含兩個方法:一個是消耗能量Consume,另一個是能量是否耗盡IsExhausted。

Energy的代碼比較簡單,如下所示:

package roletype Energy interface {    Consume()    IsExhausted() bool}

HumanEnergy

HumanEnergy是一個具體role,在Golang中是一個struct。它既有擷取能量的吃飯方法Eat,又實現了介面Energy的所有方法。對於HumanEnergy來說,Eat一次擷取的所有能量在Consume 10次後就完全耗盡。

HumanEnergy的代碼如下所示:

package roletype HumanEnergy struct {    isHungry bool    consumeTimes int}const MAX_CONSUME_TIMES = 10func (h *HumanEnergy) Eat() {    h.consumeTimes = 0    h.isHungry = false}func (h *HumanEnergy) Consume() {    h.consumeTimes++    if h.consumeTimes >= MAX_CONSUME_TIMES {        h.isHungry = true    }}func (h *HumanEnergy) IsExhausted() bool {    return h.isHungry}

RobotEnergy

RobotEnergy是一個具體role,在Golang中是一個struct。它既有擷取能量的充電方法Charge,又實現了介面Energy的所有方法。對於RobotEnergy來說,Charge一次擷取的所有能量在Consume 100次後就完全耗盡。

RobotEnergy的代碼如下所示:

package roletype RobotEnergy struct {    percent int}const (    FULL_PERCENT = 100    CONSUME_PERCENT = 1)func (r *RobotEnergy) Charge() {    r.percent = FULL_PERCENT}func (r *RobotEnergy) Consume() {    if r.percent > 0 {        r.percent -= CONSUME_PERCENT    }}func (r *RobotEnergy) IsExhausted() bool {    return r.percent == 0}

Worker

Worker是一名工人,人和機器人在工作時都是一名Worker,工作的流程是一樣的,但是區別在於依賴的能量消耗和擷取方式不同。對於代碼實現來說Worker僅依賴於另一個角色Energy,只有在Worker的執行個體化階段才需要考慮注入Energy的依賴。
Worker是一個具體role,在Golang中是一個struct。它既有生產產品的方法Produce,又有擷取已生產的產品數的方法GetProduceNum。

Worker的代碼如下所示:

package roletype Worker struct {    produceNum int    Energy Energy}func (w *Worker) Produce() {    if w.Energy.IsExhausted() {        return    }    w.produceNum++    w.Energy.Consume()}func (w *Worker) GetProduceNum() int {    return w.produceNum}

領域對象的實現

該案例中有兩個領域對象,一個是Human,另一個是Robot。我們知道,在C++中通過多重繼承來完成領域對象和其支援的role之間的關係綁定,同時在多重繼承樹內通過關係交織來完成role之間的依賴關係描述。這種方式在C++中比採用傳統的依賴注入的方式更加簡單高效,所以在Golang中我們盡量通過類比C++中的多重繼承來實現領域對象,而不是僅僅靠簡陋的委託。

在Golang中可以通過匿名組合來類比C++中的多重繼承,role之間的依賴注入不再是注入具體role,而是將領域對象直接注入,可以避免產生很多小對象。
在我們的案例中,角色Worker依賴於抽象角色Energy,所以在執行個體化Worker時,要麼注入HumanEnergy,要麼注入RobotEnergy,這就需要產生具體角色的對象(小對象)。領域對象Human在工作時是一名Worker,消耗的是通過吃飯擷取的能量,所以Human通過HumanEnergy和Worker匿名組合而成。Golang通過了匿名組合實現了繼承,那麼就相當於Human多重繼承了HumanEnergy和Worker,即Human也實現了Energy介面,那麼給Energy注入Human就等同於注入了HumanEnergy,同時避免了小對象HumanEnergy的建立。同理,Robot通過RobotEnergy和Worker匿名組合而成,Worker中的Energy注入的是Robot。

Human的實現

Human對象中有一個方法inject用於role的依賴注入,Human對象的建立通過工廠函數CreateHuman實現。

Human的代碼如下所示:

package objectimport(    "domain/role")type Human struct {    role.HumanEnergy    role.Worker}func (h *Human) inject() {    h.Energy = h}func CreateHuman() *Human {    h := &Human{}    h.inject()    return h}

Robot的實現

同理,Robot對象中有一個方法inject用於role的依賴注入,Robot對象的建立通過工廠函數CreateRobot實現。

Robot的代碼如下所示:

package objectimport(    "domain/role")type Robot struct {    role.RobotEnergy    role.Worker}func (r *Robot) inject() {    r.Energy = r}func CreateRobot() *Robot {    r := &Robot{}    r.inject()    return r}

領域對象的使用

在Context層中,對於任一個Action,都有明確的情境使得領域對象cast成該情境的role,並通過role的互動完成Action的行為。在Golang中對於匿名組合的struct,預設的變數名就是該struct的名字。當我們訪問該struct的方法時,既可以直接存取(略去預設的變數名),又可以通過預設的變數名訪問。我們推薦通過預設的變數名訪問,從而將role顯式化表達出來。由此可見,在Golang中領域對象cast成role的方法非常簡單,我們僅僅藉助這個預設變數的特性就可直接存取role。

HumanProduceInOneCycleAction

對於Human來說,一個生產周期就是HumanEnergy角色Eat一次擷取的能量被角色Worker生產產品消耗的過程。HumanProduceInOneCycleAction是針對這個過程的一個Action,代碼實現簡單類比如下:

package contextimport (    "fmt"    "domain/object")func HumanProduceInOneCycleAction() {    human := object.CreateHuman()    human.HumanEnergy.Eat()    for {        human.Worker.Produce()        if human.HumanEnergy.IsExhausted() {            break        }    }    fmt.Printf("human produce %v products in one cycle\n", human.Worker.GetProduceNum())}

列印如下:

human produce 10 products in one cycle

符合預期!

RobotProduceInOneCycleAction

對於Robot來說,一個生產周期就是RobotEnergy角色Charge一次擷取的能量被角色Worker生產產品消耗的過程。RobotProduceInOneCycleAction是針對這個過程的一個Action,代碼實現簡單類比如下:

package contextimport (    "fmt"    "domain/object")func RobotProduceInOneCycleAction() {    robot := object.CreateRobot()    robot.RobotEnergy.Charge()    for {        robot.Worker.Produce()        if robot.RobotEnergy.IsExhausted() {            break        }    }    fmt.Printf("robot produce %v products in one cycle\n", robot.Worker.GetProduceNum())}

列印如下:

robot produce 100 products in one cycle

符合預期!

小結

本文通過一個案例闡述了Golang中領域對象的實現要點,我們歸納如下:

  1. 類是一種模組化的手段,遵循高內聚低耦合,讓軟體易於應對變化,對應role;對象作為一種領域對象的直接映射,解決了過多的類帶來的可理解性問題,領域對象由role組合而成。
  2. 領域對象和角色是兩個不同的變化方向,我們在做實體設計時應該是兩個並列的目錄。
  3. 通過匿名組合實現多重繼承。
  4. role的依賴注入單位是領域對象,而不是具體role。
  5. 使用領域對象時,不要直接存取role的方法,而是先cast成role再存取方法。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.