构建微服务要求整体思维。理解微服务架构及其适用规则很重要。这是什么意思呢?思考的一种方式是理解给定应用程序里的所有域,以及服务边界的细分。
微服务必须完全和表示给定域的关联上下文匹配——应该是松耦合高内聚的。
在第一部分,我们讨论了示例项目中使用的DDD,CQRS以及event sourcing模式。希望你已经查看了GitHub代码库并且复制了代码。这一部分会详细讨论实现细节——我也推荐大家超越本文的范畴,学习更多的相关概念。
这里将项目的微服务架构分解为流程图:
注意:通过拷贝给定 .env.example 文件在根目录创建名为 .env 的新文件。
选择Node.js技术是因为它遵循模块化的方案构建应用程序——它完美契合微服务以及Unix的设计哲学:
小而美。让每个程序只干一件事情。( 来源 )
模块化仅仅是构建长期运行的应用程序的一个方面。选择最佳实践以及设计模式也很重要——从开发人员和编译器的角度看,这些方面需要仔细思考。
选择Nest.js作为框架是因为Nest.js框架库遵循最佳实践,并且提倡最佳的设计模式,比如依赖注入。这些模式改进代码质量和可维护性——并允许进一步改进。阅读Nest的 文档 ,在继续之前了解它的基础特性。
接下来让我们看看代码!
这是 AppModule ,引入了 UsersModule :
注意:AppModule用来注册微服务使用的所有模块。在示例项目中,涉及的模块包括 UsersModule 和 EventStoreModule 。
很简单对吧?这是UsersModule:
enter link description here
注意:UsersModule用来bootstrap所有Users域特定的逻辑。
在写这篇文章的时候, Fastify 处理大约76835请求/秒, Express 处理大约50933请求/秒。我的项目实现了Fastify,因为我们希望得到尽可能大的吞吐量。可以很容易得从Fastify改成其他的路由框架。
文档是 UsersModule 里控制器实现的核心!这很有用,通过流化创建及维护微服务API文档的流程可以节省大量时间。
本项目最重要的是创建用户的流程:
通过用户接口触发command。当UserCreatedEvent被捕获后,userCreated saga继续欢迎用户的事务。
聚合的所有变化都必须通过 command 来处理。当某个command被调度后,相关的 command处理器 验证并恢复给定请求——如果成功了,会尝试将新事件持久化到event store里。
客户端应用程序创建command,随后发送到域层。Command是通知某个特定实体执行特定操作的消息。( 来源 )
CreateUserCommand实现——src/users/commands/impl/create-user.command.ts
注意:command实现用来构建command。实现应该易于理解——即使对非开发人员来说。
CreateUserCommand处理器 — src/users/commands/handlers/create-user.command.ts
注意:command处理器用来管理某个命令请求。在调度事件时需要按顺序commit。
域模型是域规则定义的地方。用聚合,而不是域模型来描述User——在DDD里,聚合是域对象的集合,可以当作单个单元来处理。记住,域模型对象是不可变的,因此需要聚合。
聚合形成对象关系的树形结构或图。聚合根是“最上层“的那个对象,它代表整体,也可能委托给其他对象。它很重要因为外部是通过它来通信的。( 来源 )
User — src/users/models/user.model.ts
注意:User模型仅仅触发相关的事件。
UserRepository — src/users/repository/user.repository.ts
注意:Users repository使用User模型来持久化并且处理事件。
event sourcing领域的并发是很重要的。使用分布式command处理器时,聚合的并发访问或者修改一般不是个问题——可以做一定程度的冲突管理。
示例项目选择了乐观锁——了解event sourcing里乐观vs.悲观锁机制也很重要。
Event Store使用如下端口:
http://localhost:1113 (tcp) http://localhost:2113 (http)
Event Store的所有HTTP请求都是幂等的。
发布事件到event store很简单,代码如下:
Event Bus 发布 — src/event-store/event-store.ts
在项目的 event-store.ts
文件里,包含Event Store bridge的逻辑,它订阅了给定域分类流里的所有事件。在这里分类流是Users域——它使用了名为 $ce-users
的类projection。
Event Store自带接口。当事件发生时,接口会通知用户,并且实时处理事件。
用Docker运行在本地的Event Store实例— http://localhost:2113
event store的设计保证event持久化和流化的原子性。可以试试,发送请求创建新的User,然后监控Event Store看看发生了什么。
Event由处理器实现——和command类似。
UserCreatedEvent实现 — src/users/events/impl/user-created.event.ts
注意:事件实现用来构造事件。
UserCreatedEvent处理器 — src/users/events/handlers/user-created.handler.ts
注意:事件处理器用来管理事件。
事件处理器负责写入物化状态或者缓存来更快的读取。
最后,User saga继续Users创建事务
UsersSagas — src/users/sagas/users.saga.ts
UserCreatedEvent 事件存储之后,系统发出由userCreated saga触发的UserWelcomedEvent——之后用户创建事务完成。
===========================
译者介绍
崔婧雯,现就职于IBM,高级软件工程师,负责IBM WebSphere业务流程管理软件的系统测试工作。曾就职于VMware从事桌面虚拟化产品的质量保证工作。对虚拟化,中间件技术,业务流程管理有浓厚的兴趣。