——–翻译分隔线——–
在 Go 应用中使用简明架构(2)
架构实现
首先来实现领域层。之前已经说过,应用和其用例将完全可用,但是这不是一个完整的商城。因此,定义领域的代码应当足够短小,这样正好可以放在一个文件中:
package domain import ( "errors" ) type CustomerRepository interface { Store(customer Customer) error FindById(id int) Customer } type ItemRepository interface { Store(item Item) error FindById(id int) Item } type OrderRepository interface { Store(order Order) error FindById(id int) Order } type Customer struct { Id int Name string } type Item struct { Id int Name string Value float64 Available bool } type Order struct { Id int Customer Customer Items []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.0 for 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 的订单限制条件添加在数据库的存储过程中。一旦你的应用开始增长,那就祝愿你所有的商业规则都还能保持完整吧。我更乐意在任何时候,都保持它们在相同的地方。
——–翻译分隔线——–
未完待续……
Leave a Reply