[翻译]在 Go 应用中使用简明架构(2)

原文在此,续……

——–翻译分隔线——–

在 Go 应用中使用简明架构(2)

架构实现

首先来实现领域层。之前已经说过,应用和其用例将完全可用,但是这不是一个完整的商城。因此,定义领域的代码应当足够短小,这样正好可以放在一个文件中:

<br />
package domain</p>
<p>import (<br />
	&quot;errors&quot;<br />
)</p>
<p>type CustomerRepository interface {<br />
	Store(customer Customer) error<br />
	FindById(id int) Customer<br />
}</p>
<p>type ItemRepository interface {<br />
	Store(item Item) error<br />
	FindById(id int) Item<br />
}</p>
<p>type OrderRepository interface {<br />
	Store(order Order) error<br />
	FindById(id int) Order<br />
}</p>
<p>type Customer struct {<br />
	Id   int<br />
	Name string<br />
}</p>
<p>type Item struct {<br />
	Id        int<br />
	Name      string<br />
	Value     float64<br />
	Available bool<br />
}</p>
<p>type Order struct {<br />
	Id       int<br />
	Customer Customer<br />
	Items    []Item<br />
}</p>
<p>func (order *Order) Add(item Item) error {<br />
	if !item.Available {<br />
		return errors.New(&quot;Cannot add unavailable items to order&quot;)<br />
	}<br />
	if order.value() + item.Value &gt; 250.00 {<br />
		return errors.New(`An order may not exceed<br />
		                   a total value of $250.00`)<br />
	}<br />
	order.Items = append(order.Items, item)<br />
	return nil<br />
}</p>
<p>func (order *Order) value() float64 {<br />
	sum := 0.0<br />
	for i := range order.Items {<br />
		sum = sum + order.Items[i].Value<br />
	}<br />
	return sum<br />
}<br />

显而易见,这段代码没有重度依赖任何东西,除了为了某些方法返回错误,而引入了“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 的订单限制条件添加在数据库的存储过程中。一旦你的应用开始增长,那就祝愿你所有的商业规则都还能保持完整吧。我更乐意在任何时候,都保持它们在相同的地方。

——–翻译分隔线——–

未完待续……

One thought on “[翻译]在 Go 应用中使用简明架构(2)”

Leave a Reply

Your email address will not be published. Required fields are marked *