go语言过渡到DDD(四)

224 阅读3分钟

这是我参与2022首次更文挑战的第22天,活动详情查看:2022首次更文挑战

本文为译文,原文链接:www.calhoun.io/moving-towa…

接上文,接下来聊聊DDD

领域驱动设计

到目前为止,我一直试图避免使用任何令人困惑的术语,因为我发现它们往往会使问题复杂化,而不是简化问题。如果你不相信我,可以试着去阅读任何DDD相关的文章,书籍,或者其他资源。它们几乎总是会给你留下更多的问题,让你不太清楚如何在代码中实际实现这些想法。

我不是在说DDD没用,也不是在说你永远不应该阅读那些书籍。我想说的是我的大多数读者在这里是为了寻找更实际的建议说怎样提高他们的代码,不是为了讨论软件开发的理论。

从实践的角度来看,领域驱动设计的主要好处是编写可以随着时间发展和变化的软件。我发现在Go中实现这一点的最好方法是明确定义你的domain领域类型,然后编写依赖于这些类型的实现。这仍然会产生耦合的代码,但是由于您的domain领域与正在解决的问题是如此紧密地联系在一起,所以这种耦合很少会出现问题。事实上,我经常发现需要一个明确的领域模型定义是有启发性的,而不是麻烦的。

注意:定义具体的域类型并将代码耦合到它们的想法并不是唯一的或新的。本·约翰逊(Ben Johnson)在2016年写了一篇关于它的文章,对于任何一个新地鼠来说,这仍然是一篇非常有价值的文章。

回到之前的例子,我们看到外面的domain已经定义在domain包下:

package domain

type User struct {
  ID    string
  Email string
  OrgIDs []string
}

更进一步,我们甚至可以开始定义应用程序其余部分可以(a)实现的基本构建块,或者(b)在不耦合实现细节的情况下利用这些基本构建块。举个例子就是我们的UserService:

package domain

type User struct { ... }

type UserService interface {
  User(token string) (User, error)
}

这是由github和gitlab包实现的,并被mw包依赖。我们代码中的其他包也可以依赖它,而不用担心它是如何实现的。因为它是在domain域上定义的,所以我们不需要担心每个实现在返回类型上都有轻微的改变——它们都有一个共同的定义来构建。

随着应用程序的发展和变化,定义用于构建的公共接口的想法变得更加强大。例如,假设我们有一个稍微复杂一点的UserService;它可能处理创建用户、验证用户、通过令牌查找用户、重置密码令牌、更改密码等等。

package domain

type UserStore interface {
	Create(NewUser) (*User, RememberToken, error)
	Authenticate(email, pw string) (*User, RememberToken, error)
	ByToken(RememberToken) (*User, error)
	ResetToken(email string) (ResetToken, error)
	UpdatePw(pw string, tok ResetToken) (RememberToken, error)
}

我们可以先用纯SQL代码和本地数据库来实现:

package sql

type UserStore struct {
  DB *sql.DB
}

func (us *UserStore) Create(newUser domain.NewUser) (*domain.User, domain.RememberToken, error) {
  // ...
}

// ... and more methods

当我们有一个单独的应用程序时,这是完全有意义的,但也许我们开始发展到另一个谷歌,并决定我们需要一个集中的用户管理系统,所有单独的应用程序都可以使用。

那么需要怎么做呢?

好了今天就到这里为止,下一节我们会做一个解答,并给这一系列做一个收尾。