应用架构的本质
架构这个词,我们经常会聊起,那什么是架构呢,这个问题,我相信十个人会有二十种答案,我想翻一翻Neal Ford的《软件架构》找一找定义,但又一想,貌似他的定义不如知乎来的更篇幅少,于是我得到了这样的回答:软件架构这项工作的实质就是:如何将系统切分成组件,并安排好组件之间的排列关系,以及组件之间的相互通信的方式。还不错的定义,这句话基本可以描述成下面这样
架构 = 组件 + 相互关系 + 指导原则
对于应用架构而言,同样适用,代码就是我们“组件”,整体工作就是要如何处理模块(Module)、组件(Component)、包(Package)和类(Class)之间的关系。简而言之,应用架构就是要解决代码要如何被组织的问题。
好的应用架构,也遵循一些共同模式,不管是六边形架构、洋葱圈架构、整洁架构,都提倡以业务为核心,解耦外部依赖,分离业务复杂度和技术复杂度。
业务为核心
我们很多的老兵,看到这个词的时候,会很不理解,为什么在应用架构层,还要讲业务为核心呢,我写的代码不就是在解决业务问题吗,怎么不是以业务为核心呢,同时会给我丢过来这样一个代码。
我看了下,这种代码很优秀,我们可以清晰的看出,这段代码 应该是用户根据档位进行提现,执行了下面几个步骤
校验提现的档位是否正确
判断是否是新手档位,
校验用户绑定信息
扣减金币
给用户打钱
典型的过程式代码。他可以解决非常多的场景,像我们熟悉的spring,最复杂的refresh 方法,基本上也就是这样的写法。
但是这类的写法会有两个问题,第一个是,会丢失领域知识,第二个是,代码的业务语义表达能力比较弱。我们可以选择另外一种方式替换这种过程式代码。
最佳实践
分层结构
所有的复杂系统都会呈现出层级结构,如果网络协议不是四层,而是一层,意味着,你要在应用层去处理链路层的bit数据流会是怎样的情景吗?同样,应用系统处理复杂业务逻辑也应该是分层的,下层对上层屏蔽处理细节,每一层各司其职,分离关注点,而不是一个ServiceImpl解决所有问题。
对于一个典型的业务应用系统来说,我们会做如下层次定义,每一层都有明确的职责定义:
应用层(Application Layer):主要负责获取输入,组装上下文,调用领域层做业务处理, 发送消息通知等。
领域层(Domain Layer):主要是封装了核心业务逻辑,并通过领域服务(Domain Service)和领域对象(Domain Entity)的方法对App层提供业务实体和业务逻辑计算。领域是应用的核心,不依赖任何其他层次;
基础实施层(Infrastructure Layer):主要负责技术细节问题的处理,比如数据库的CRUD、搜索引擎、文件系统、分布式服务的RPC等。此外,领域防腐的重任也落在这里,外部依赖需要通过转义处理,才能被上面的App层和Domain层使用。
包结构
包结构应该有如下表现:
常见的包形态
层次 | 包名 | 功能 | 必选 |
infra | repo | 处理数据库等存储的实现 | 否 |
infra | config | 配置信息 | 否 |
infra | sender | 埋点等消息发送的实现 | 否 |
infra | controller | 处理请求的实现 | 是 |
application | service | 组装上下文,调用领域层做业务处理 | 是 |
application | sender | 消息发送的定义,由infra层实现 | 否 |
domain | entity | 领域模型的定义 | 是 |
domain | factory | 模型的创建 | 否 |
domain | service | 领域能力 | 否 |
依赖倒置
在我们上面的层次里,我们可以很明显的发现,我们的依赖关系完全是一种内外层次的关系。如果用图形来表达出来,就是下面这样,所有的关系都是在由外向里依赖。
这样做有什么好处呢,主要体现在两个方面。
首先,在我们的理解里,最核心的是业务,我们所做的一切都在围绕着业务场景,技术的实现完全要服务于业务场景。举个例子,存在着一个给用户发送验证码的业务场景,因此我们需要调用一个三方的服务,把验证码发出去。这里发送验证码这个业务场景,是上游,这个场景不存在了,我们调用三方发验证码的实现,也就没有意义了,而如果场景一直存在,可能会发生的变化是,我们会根据三方服务的价钱变动,更换不同的厂商,厂商变了,调用服务的具体方式也会发生变化,因此从变化频率的角度来看,我们将技术的实现放在最外层,让他向内依赖更稳定的业务场景。
另外,我们从副作用的角度来理解,在整个层次里,infra 里定义了所有的基础实现,而我们最有可能发生难以预料的异常场景,就是在infra 里,比如说,数据库突然连接不上了,消息中间件突然挂掉了,这是我们难以预料,但却可能随时发生,是典型的副作用,因此我们将可能存在副作用的模块放在最外层,保证里面的domain层,可以进行能力上的单测,同时让他向内依赖无副作用的业务核心逻辑。
模型定义的方法
事件分析
通过事件风暴的方法,进行业务问题的拆解。最后得到领域模型。
能力下沉
建模一定不是可以一蹴而就的,我们的模型具备哪些能力同样不是一簇而就的。所谓的能力,简单说就是我们的实体他应该具备的方法有哪些, 或者说我们的domainservice 应该有哪些发放。我们可以采用能力下沉的方法,反复迭代,所谓能力下沉就是,经过我们一定的拆解,发现在不同的case 里,我们会发现存在着step ,有着很大的共性。这个时候,我们可以考虑做个抽象,去思考它是否是归属某个领域的某个能力,去做个能力的下沉。这个时候可以沉淀到 domain service 做个能力,也可以沉掉到domain 里做个能力,这里取决于粒度。如果他横跨多个模型,那只能是domainservice ,如果他很内聚,同时他很自洽,那就可以放进模型里。