這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
原文在此,續前……
——–翻譯分隔線——–
在 Go 應用中使用簡明架構(2)
架構實現
首先來實現領域層。之前已經說過,應用和其用例將完全可用,但是這不是一個完整的商城。因此,定義領域的代碼應當足夠短小,這樣正好可以放在一個檔案中:
package domainimport ("errors")type CustomerRepository interface {Store(customer Customer) errorFindById(id int) Customer}type ItemRepository interface {Store(item Item) errorFindById(id int) Item}type OrderRepository interface {Store(order Order) errorFindById(id int) Order}type Customer struct {Id intName string}type Item struct {Id intName stringValue float64Available bool}type Order struct {Id intCustomer CustomerItems []Item}func (order *Order) Add(item Item) error {if !item.Available {return errors.New("Cannot add unavailable items to order")}if order.value() + item.Value > 250.00 {return errors.New(`An order may not exceed a total value of $250.00`)}order.Items = append(order.Items, item)return nil}func (order *Order) value() float64 {sum := 0.0for i := range order.Items {sum = sum + order.Items[i].Value}return sum}
顯而易見,這段代碼沒有重度依賴任何東西,除了為了某些方法返回錯誤,而引入了“errors”包。儘管這裡描述的領域模型最終將以行的形式存在於資料庫中,但這裡卻沒有任何資料庫相關的代碼。
我們定義了三個所謂用於儲存區的 Go 介面作為替代。儲存區是來自領域驅動設計的一個概念:它將領域模型的儲存和載入從某種持久化機制中抽象出來。從領域的角度來看,儲存區僅僅是一個用於擷取(FindById)或盛放(Store)領域模型的容器。
CustomerRepository,ItemRepository 和 OrderRepository 僅僅是介面。由於它們是資料庫和應用之間的介面,所以將在介面層實現。這裡說明了如何將依賴原則應用於 Go 應用:內部層定義的抽象介面不引用任何外部層的東西;其實現定義在外部層中。這樣實現就可以注入到想要使用它的層中去;在一會就能夠瞭解到,這個例子中,就是應用在用例層。
通過這種方法,用例層可以使用領域層的表達方式,來引用領域層的概念——儲存區。同時,實際執行的代碼卻是在介面層。
對於每層的每個部分來說,有三個有趣的問題:它用在哪;它的介面在哪;它的實現在哪?
就 OrderRepository 舉例來說,答案如下:它將被用在用例層,它的介面屬於領域層,它的實現屬於介面層。
從另一個方面來說,Order 實體的 Add 方法是用例層使用的,並且同樣的其介面屬於領域層。但是它的實現也在領域層,因為它自身並不需要任何領域層外部的東西。
有三個結構會實現儲存區介面的定義:Custormer、Order 和 Item。它們代表了三個領域模型。Order 實體通過兩個方法 AddItem 和 value 實現了一些額外的功能,後者僅僅是一個內部使用的協助函數。AddItem 實現了用例需要的特定領域功能。
在討論整體架構的時候,這段代碼裡還有一些額外的細節與此相關。如同你已經瞭解的,我們為 AddItem 方法添加了一些約束條件。很快就會發現,我們的應用會在若干個地方有若干個約束條件,討論哪個約束條件屬於哪裡是很有趣的。
第一個約束條件是不允許添加一個停用商品到訂單——很明顯這是一個商業約束。不允許使用者對停用商品下訂單這一約束條件,對於Web 商店和電話熱線的訂購是一樣的;這並不是(我們的)軟體特有的東西——定義這個約束條件是商業頭腦驅使的。
訂單總額不能超過 $250 的約束也是一樣的——不論商店是個網站還是個案頭遊戲,這就是總是有效商業規則。
其他約束條件在別的一些地方——某些地方,商品的值必須儲存在資料庫,那麼就必須對資料庫中 value 欄位儲存的浮點數額外小心;然而這是一個技術約束條件,而不是一個商業約束條件,那麼就不屬於領域包。
另一方面來說,資料庫介面代碼和資料庫本身在持久化總額超過 $250 的訂單時,完全無需擔心這點,它們可以很好的遵從這個商業規則。這個例子對於 Bob 大叔的理念是一個很有力的說明,比如說做一個完全相反的假設:例如將 $250 的訂單限制條件添加在資料庫的預存程序中。一旦你的應用開始增長,那就祝願你所有的商業規則都還能保持完整吧。我更樂意在任何時候,都保持它們在相同的地方。
——–翻譯分隔線——–
未完待續……