EF几乎是按照领域的概念诞生,它可以和Clean结合。在 微服务架构 中服务的通用开发方式。当有了统一开发方式后,协作将更上一层楼。

最近没工作,就写了这章。是为了团队协作的,如何能被更多知道同样知识的人快速了解业务系统?

通用名词:DDD中定义了通用名词,意为某件事情的专业术语,软件中也可拥有。Clean和DDD的战术设计近乎一致。

先看看Clean中的概念,简略意思是低耦合高内聚。每一个方法都需要遵循SOLID,命名为特殊的含义。从方法名中能看到它的功能,即使它的内部很复杂。都能通过几句简短的代码,飞快的看到过程。过程中隐藏着复杂细节,不需要及时的都去了解,就能理解这个方法都做了什么事情。

低耦合是代表不同的模块之间更改没有冲突。

高内聚是代表上层不同的模块调用下层相同的模块,这个下层模块更改,多个上层模块也被更改。

EF中DbContext,则符合领域的概念代表一个上下文,它被设计的很灵活,DbContext是一个仓储管理者(意为DDD中的仓储模式,多个仓储的管理者)。用DbContext可以直接调用出多个不同仓储,用linq简单的方式操作仓储的API。从操作仓储API中可以操作多个实体(意为DDD中的聚合根模式)。

如图所示(剪头方向代表正被它使用的):

EntityCleanFramework-LMLPHP

实体

Entity 实体是与数据库接触的模型体,它可以是存储在内存数据库中的缓存,也可以是关系型数据库中的表。

每一个实体有自己的行为方法。不过有些特殊的实体,却能管理其他实体的行为,它是实体管理者也叫聚合根。实体管理者它被任命管理它所认知范围内所有的实体。实体管理者不能处理过程业务,过程业务只能是管理者实现。

不可变的值

实体中很重要的值对象,在实体中通常会有要改一起改的属性,它们不能只改一个,是一个整体。这种一般会把它做成结构体,在实体中做成结构属性,以防止对它进行破坏。

管理者

Manager 是遵循SOLID的好管理者。它指的是只做有意义的事情,在代码层面是独一无二的,它可以方便上层调用最少量代码。一直在上层编写可能会有很多重复功能,更改其一偶尔忽略其二的事情常常发生,它的出现将改变这一切。

它是范围生命周期的服务,它会使用工厂,使用缓存,使用外部服务,DbContext,日志等项。它将暴露只属于它的仓储,而不是DbContext(DbContext 可以切换不同的关系型数据库,使得仓储不用改变),还将暴露对上层有意义的方法和属性。它的方法不能隐藏业务过程,只能隐藏代码和业务细节。它的方法命名必须简明要义,必须让外部来看瞬间理解其意思,而不是添加更多的注释。

模型

模型和实体一样重要,它可以是分页带很多查询参数的请求模型、外部服务的模型、工厂的模型等等。它不是数据库交互的模型体,它也可以包含行为。

外部服务

外部服务应该是瞬态生命周期可以有服务注册选择项,最常包含的有链路追踪,单例HttpClient。只能被管理者调用,这是因为在上层不需要处理细节。

实体规则

了解实体的行为方法后,还需要了解实体上有些行为是不允许的会报错。通常是有属性不能为空,字符串长度太长等项。这种应该在领域中呈现,作者自己命名为 实体规则。实体规则能被实体的行为调用,还可以被数据传输对象的规则调用。实体规则是被注入为单例的服务,实体规则可以有服务注册选择项。实体规则的方法要简明要义,请求参数必须有是否抛出异常的选项,返回值必须是bool类型。

数据传输对象

是Rpc的过程传输对象,一般有请求参数和返回参数。数据传输对象不能直接是实体,因为不易于前端的使用,可能会有前端不认识的属性出现。请求参数,每个API都必须不一样,返回参数,则相反尽量使用同样的返回参数,请求和返回都应该用最少的属性满足,这样设计有易于前端的使用。一般命名规则为,请求参数动词在前名词在中间最后加上Dto。返回参数则是名词加上Dto,嵌套返回参数亦是如此。

映射

几乎映射规则是实体和数据传输对象的映射,一般只服务于Rpc。在管理者中应该尽量减少映射的出现,因为管理者的请求应该只接受实体和工厂模型的传输。

数据传输对象规则

是最终要返回给用户的错误信息,是单例声明周期的服务可以有服务注册选择项。不可包含DbContext,它应该是无状态的规则,可以调用专有的实体规则来满足高内聚。

一般有状态的规则应该在业务中体现,若是出现问题应该抛出异常返回400 BadRequest,并且不提示任何信息,这很有可能是一个破坏请求,生产环境正常业务下是不会出现400的。

有状态的规则在业务中体现,必须有专门的API去给前端判断,比如昵称是否重复API,而不是主流程API出现判断。

Rpc

远程过程调用API,应该尽可能的用过滤器保护内部业务和数据,这包含请求缓存,限流,授权等项。

它的业务过程应该简单清晰,包含使用日志,使用数据传输对象规则、映射规则、DbContext和管理者、还有实体等项。

集成事件

只有上层才会有集成事件和外部系统发送事件交互,从而解耦项目之间的依赖。不可否认的是集成事件和Rpc如出一辙,都可以使用相同的战术,需要保护内部业务和数据。(领域事件是项目内部发生,依赖注入的方式做成的)

单元测试

由于项目是高内聚,低耦合,这使得其中的每个方法,都可以进行单元测试。

单元测试最终的目的是:将这个方法过去错误进行保留,当需要修改这个方法时,可以运行过去的错误,用来兼容新的的业务规范。

其他配置项

应该与业务需要适配,尽量去做出灵活的配置。不应该出现不认识或者无用的配置属性。知己知彼百战百胜,配置项不是一定要记住,也不是一定要把注释写在配置文件中。正确的做法是,给每一项属性加上完整的注释。

规模大成分布式

当知道如何完成一个独立项目后,后面应该知道如何分布式项目,完成上下文解耦。

举个例子,有个用户项目,包含登录,注册。还包含,管理员的用户创建不需要登录。一方流量大,一方根本没流量,在同一个项目中。此时是需要完成上下文解耦,一个上下文变成两个项目。身份项目服务于普通用户,账号管理服务于管理员。

我们知道既然慢慢的分布成了多个项目变成了分布式,就要设计很多东西:日志收集、指标监控、链路追踪、分布式缓存、缓存淘汰策略、领导选举、哨兵模式、索引文档、服务发现、健康检查、负载均衡、网关、消息队列、分布式设计模式、Sagas等项。这里每一项在关键时刻就需要上,不上就很难做成,切忌不可操之过急,根据团队成员分配,微服务是把双刃剑。

当项目和工具太多又需要承受每个服务的管理,就需要容器化服务。容器越来越多,就需要上管理容器的工具K8s。当数据库承受不住还要上集群数据库,都知道集群数据库不是一般的贵,要是上不起只能跑到消息队列。

这只是表象,实际中许多还需要处理存储图片,文件,静态资源,还需要上了对象存储。资源太卡,还需要CDN。域名,DNS等等等很多。每一个知识点的用处都有讲究,知识层出不穷很多很多,学不完根本学不完。

最后

借鉴了微软微服务文档(https://learn.microsoft.com/zh-cn/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/)、微软EF文档(https://learn.microsoft.com/zh-cn/ef/core/)、《Clean架构》、《领域驱动设计》、《微服务设计模式》。如有异议欢迎探讨。

07-29 08:20