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

原文在此,续……

——–翻译分隔线——–

在 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 的订单限制条件添加在数据库的存储过程中。一旦你的应用开始增长,那就祝愿你所有的商业规则都还能保持完整吧。我更乐意在任何时候,都保持它们在相同的地方。

——–翻译分隔线——–

未完待续……

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

Leave a Reply

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