译见|构建用户管理微服务(二):实现领域模型

394 阅读6分钟

译见系列  道客船长「译见」系列,关注国外云计算领域的技术和前沿趋势,每周为开发者提供精选译文。


上期的《译见|构建用户管理微服务(一):定义领域模型和 REST API》,作者定义了用户管理微服务的要求,并设计了它的初始领域模型。在得到许多来自社区和 Reddit 上许多有价值的正能量的评论,这个项目值得继续推进。在第二部分,作者将详细介绍如何实现领域模型,在代码之外做了哪些决定。

使用领域驱动设计

在第一部分中,作者提到了将使用领域驱动设计原则,这意味着,该模型可以不依赖于任何框架或基础设施类。在多次应用实现过程中,作者把领域模型和框架的具体注释(如 JPA 或 Hibernate )混在一起,就如同和 Java POJO 一起工作(贫血模型)。在设计领域模型中,唯一使用的库是Lombok,用于减少定义的 getter 和 setter 方法以避免冗余。

当设计 DDD 的模型,第一步是对类进行分类。在埃里克·埃文斯书中的第二部分专注于模型驱动设计的构建模块。考虑到这一点,我们的模型分为以下几类。

实体类

实体有明确的标识和生命周期需要被管理。从这个角度来看,用户肯定是一个实体。

ConfirmationToken 就是一个边缘的例子,因为在没有用户上下文的情况下,逻辑上它就不存在,而另一方面,它可以通过令牌的值来标识并且它有自 己的生命周期。

同样的方法也适用于 Session ,这也可能是一个值对象,由于其不可改变的性质,但它仍然有一个 ID 和一个生命周期(会话过期)。

值对象

相对于实体类,值对象没有一个明确的 ID ,那就是,他们只是将一系列属性组合,并且,如果这些属性和另外一个相同类型的值对象的属性相同,那么我们就可以认为这两个值对象是相同的。

当设计领域模型,值对象提供了一种方便的方式来描述携带有一定的信息片段属性的集合。 AddressData,AuditData,ContactData 和 Password 因此可以认为是值对象。

虽然将所有这些属性实现为不可改变的是不切实际的,他们的某些属性可以单独被修改, Password 是一个很好的例子。当我们创建 Password 的实例,它的盐和哈希创建只有一次。在改变密码时,一个全新的实例与新的盐和散列将会被创建。

聚合

聚合代表一组结合在一起,并通过访问所谓的聚合根的对象。

这儿有两个聚合对象:用户和会话。前者包含了所有与用户相关的实体和值对象,而后者只包含一个单一的实体 Session 。

显然,用户聚合根是用户实体。通过一个实例用户实体,我们可以管理确认令牌,用户事件和用户的密码。

聚合 Session 成为一个独立的实体——尽管被捆绑到一个用户的上下文——部分原因是由于其一次性性质,部分是因为当我们查找一个会话时我们不知道用户是谁。 Session 被创建之后,要么过期,要么按需删除。

领域事件

当需要由系统的另外组件处理的事件发生时,领域事件就会被触发。

用户管理应用程序有一个领域事件,这是 UserEvent ,它有以下类型:

  • DELETED

  • EMAIL_CHANGED

  • EMAIL_CHANGE_REQUESTED

  • EMAIL_CONFIRMED

  • PASSWORD_CHANGED

  • PASSWORD_RESET_CONFIRMED

  • PASSWORD_RESET_REQUESTED

  • SCREEN_NAME_CHANGED

  • SIGNIN_SUCCEEDED

  • SIGNIN_FAILED

  • SIGNUP_REQUESTED

服务

服务包含了能够操作一组领域模型的类的业务逻辑。在本应用中, UserService 管理用户的生命周期,并发出合适的 UserEvent 。SessionService 是用于创建和销毁用户会话。

存储库

存储库旨在代表一个实体对象的概念集合,但是有时他们只是作为数据访问对象。有两种实现方法,一种方法是列出所有的抽象存储库类或超接口可能的数据访问方法,例如 Spring Data ,或者创建专门存储库接口。

对于用户管理应用程序,作者选择了第二种方法。UserRepository  和 SessionRepository  只列出那些绝对必要的处理他们实体的方法。

项目结构

你可能已经注意到,这里有一个 GitHub 上的库: springuni ,它包含用户管理应用程序的一部分,但它不包含应用程序本身的可执行版本。

究其原因,我为什么不提供单一只包含 Spring Boot 少量 @Enable* 注解的库,是为了可重用性。大多数我碰到的项目第一眼看起来是可以模块化的,但实际上他们只是没有良好分解职责的巨大单体应用。当你试图重用这样一个项目的模块,你很快意识到,它依赖于许多其他模块和/或过多的外部库。

 springuni-particles (它可能已被也称为 springuni 模块)提供了多个模块的可重复使用的只为某些明确定义的功能。用户和会话管理是很好的例子。

模块

springuni-auth-model 包含了所有的领域模型类和用于管理用户生命周期的业务逻辑,它是完全与框架无关的。它的存储库,并且可以使用任何数据存储机制,对于手头的实际任务最符合。还有,PasswordChecker 和 PasswordEncryptor 可基于任何强大的密码散列技术实现。

springuni-commons 包含了通用的工具库。有很多著名的第三方库(如 Apache Commons Lang,Guava 等),这外延了 JDK 的标准库。在另一方面,我发现自己很多时候仅仅只用这些非常可扩展库的少量类。我特别喜欢的 Apache Commons Lang 中的 StringUtils 的和 Apache 共同集合的 CollectionUtils 类,但是,我宁愿为当前项目提供一个高度定制化的 StringUtils 和 CollectionUtils,这样就不需要添加外部依赖。

sprinuni-crm-model 定义了通用的值对象,用于处理联系人数据,如地址,国家等。虽然微服务架构的倡导者将投票反对使用共享库,但我认为这个特定点可能需要不时修订手头的任务。我最近参与了一些 CRM 集成项目,不得不重新实现了几乎同样的领域模型在不同的限界上下文(即用户,客户,联系人),这样一遍又一遍的操作是乏味的。也就是说,我认为使用联系人数据领域模型的小型通用库是值得尝试的。

下期预告: 构建用户管理微服务(三):存储库的测试与实现

原文链接: https://springuni.com/user-management-microservice-part-2

上期回顾


构建用户管理微服务(一):定义领域模型和 REST API

在《构建用户管理微服务》的第一部分中,我们会定义应用的需求,初始的领域模型和供前端使用 的 REST API。 我们首先定义用户注册管理用户的故事。


☟点击查看  上期文章