《自然哲学的编程原理》基于SpringCloud框架与DDD理念的AAA架构-简要描述

最近在研究DDD架构,颇受启发,于是才有了《自然哲学的编程原理》。

不像MVC三层架构那样成熟易用,DDD并没有形成统一的分包分层规范,于是就决定结合“自然哲学”的原理,做一个我心目中“最合乎逻辑”的大型SpringCloud+DDD架构应用的层次结构出来。

该应用有若干个微服务,每个微服务都是DDD架构。

此外应用下还有common模块。

DDD分四层,分别是User Interface(用户界面层,以下简称ui),Application(应用层,以下简称app),Domain(领域层),Infrastructure(基础设施层,以下简称infra)。

一、大致分层

假设现在要研发一款新的机床。

我们把新机床视作一个java对象,可以将机床分解为对象名、属性和方法。

对象名可以理解为机床的名字,让产品去管,不是机床研发人员该操心的范畴:

1.N*方法:若干个加工功能。

2.N*属性:若干个零件。

注:N*代表正整数,即若干个

方法分解为方法名、方法体、返回类型和参数列表,其中方法名也不是开发管的:

1.N*方法体(至少一行代码):若干个加工功能的工艺流程。(不存在没有任何用处的功能。)

2.N*参数列表:若干个加工功能的输入。

3.N*返回类型:若干个加工功能的输出。

4.N*属性:若干个零件。

我们可以很轻易地从中抽取出DDD中的ui层。

ui层:

微服务需要在内部定义一个ui层。说明接口的参数列表、返回值。由该微服务开发团队进行维护:机床需要有一个说明书,随附在机床上面。说明需要接多少W的电源、按什么样的按钮,产生什么样的效果。机床新增功能后,机床产品团队修改该说明书。

1.参数列表×N*。

2.返回类型×N*。

抽取出ui层后,剩下:

1.N*方法体(至少一行代码):若干个加工功能的工艺流程。(不存在没有任何用处的功能。)

2.N*属性:若干个零件。

由于我们知道方法体至少有一行代码,所以改写成以下形式:

1.N*^2一行代码:若干个加工功能的工艺流程的若干个工艺步骤。

2.N*属性:若干个零件。

然后,试考虑一个情境,现实中,存在不需要零件就能完成的工艺步骤吗?

工艺步骤一:物料存放在机床中。

该步骤需要用于存放的零件,故不满足条件。

工艺步骤二:物料存放在机床外。

需要一个输出物料的零件,同样不满足条件。

考虑到现实中不存在不需要零件就能完成的工艺步骤,可令所有“一行代码”变为“对属性方法的调用”。

1.N*属性:若干个零件。

2.N*^2对属性方法的调用:若干个加工功能的工艺流程的若干个零件触发器。

现实世界中,一个电子产品通电后能够按照合理的顺序触发各个零件的作用,从而发挥电子产品整体的作用。

假如把电脑看成这个产品,那么直接构成电脑的就是显示器、键盘、鼠标、机箱这四个顶级零件(为了方便理解,不引入BOM这种专业术语,统称零件)。

零件下面还有零件,比如机箱里就还有CPU、风箱这种次一级的零件。

我们把移动鼠标看做一个顶层的功能,伪代码可以这样写。

public class computer{

        private Host host;

        private Monitor monitor

        public View moveMouse(Vector vector){

                host.handleMove(vector);
                monitor.refreshMousePosition(vector);

        }

}

host、monitor内也有各自的属性和对属性方法的调用,层层相套。

如果属性不仅限于顶层属性,上面的两条就可以改写成如下形式:

1.N*^N*属性:若干个零件。

2.N*^(N*+1)对属性方法的调用:若干个加工功能的工艺流程的若干个零件触发器。

属性可以进一步拆分为本微服务新定义的属性(这里简称域内属性)和从依赖引入的属性(简称域外属性):

1.N*^N*d域内属性:若干个自制零件。

2.N*^N*域外属性:若干个第三方零件。

3.N*^(N*+1)对域内属性方法的调用(中等):若干个加工功能的工艺流程的若干个自制零件的触发器。

4.N*^(N*+1)对域外属性方法的调用(复杂):若干个加工功能的工艺流程的若干个第三方零件的触发器。

成熟的域外属性(第三方组件)十分沉重,经常不能像域内属性一样如臂指使,因此将复杂的对域外属性方法的直接调用单独放置在一层中。

infra层:

微服务的开发团队完成调用第三方组件的具体代码的编写:机床的开发团队为了让机床可以连接第三方硬件并运行,专门做了独立的适配器。

1.N*^(N*+1)对域外属性方法的直接调用(复杂)。

外部依赖:

1.N*^N*域外属性。

抽取出infra层和外部依赖后:

1.N*^N*d域内属性:若干个自制零件。

2.N*^(N*+1)对域内属性方法的调用(中等):若干个加工功能的工艺流程的若干个自制零件的触发器。

3.N*^(N*+1)对域外属性方法的间接调用(简单):若干个加工功能的工艺流程的若干个第三方零件的适配器的触发器。

有些情况下,对域内属性方法的调用逻辑也会变得稍显复杂,也许没必要像infra那样单独成层,但可以考虑放在单独的包,比如domainService,和域内属性放在同一层。

但没有必要一刀切,对于复杂的我们分成间接调用和直接调用,而简单的维持原状即可。

这样一来,domain层就成型了:

domain层:

微服务的开发团队自定义类的具体代码,完成调用自定义类的具体代码的编写:机床的开发团队自制零件,让自制零件运行。

1.N*^N*域内属性。

2.N*^(N*+1)对域内属性方法的直接调用(中等)。

抽取出domain层后,剩下的自然就是app层了。

app层:

1.N*^(N*+1)对域内属性方法的简单调用(简单)。

2.N*^(N*+1)对域内属性方法的间接调用(简单)。

3.N*^(N*+1)对域外属性方法的间接调用(简单)。

至此,不重复且无遗漏的大致的分层完成。

二、依赖分析

接下来试分析研发新机床从设计到完成的过程,以推导合宜的依赖关系。

我们先列出上文总结出的层次结构。

springcloud-ddd-application:一系列产品

        common:公司通用资源

        ddd-application:新机床

                application:实现新机床功能的控制主板

                        N*^(N*+1)对域内属性方法的简单调用:简单自制零件的触发器

                        N*^(N*+1)对域内属性方法的间接调用:复杂自制零件适配器的触发器

                        N*^(N*+1)对域外属性方法的间接调用第三方零件适配器的触发器

                domain自制零件及配套适配器

                        N*^N*域内属性:自制零件

                        N*^(N*+1)对域内属性方法的直接调用:复杂自制零件的适配器

                infrastructure:第三方零件的适配器

                        N*^(N*+1)对域外属性方法的直接调用:第三方零件的适配器

                user-interface:用户说明书

                        参数列表×N*:操作步骤说明

                        返回类型×N*:预期结果说明

                外部依赖:第三方零件

                        N*^N*域外属性:第三方零件1

基于完成顺序分析,易得层次结构如下:

infra层依赖于外部依赖:先有了第三方零件,才有对应的适配器。

domain层依赖于infra:既有的第三方零件不可能包含自制零件,但自制零件却可能包含第三方零件,若是,则必须先有第三方零件的适配器,后设计具体的自制零件。

app层依赖于domain层:先有自制零件的适配器,后设计具体的控制主板。

app层依赖于infra层:先有第三方零件的适配器,后设计具体的控制主板。

ui层依赖于其他全部层:先完成具体的产品,才能完成用户说明书。

这基本就是传统的DDD架构,从底层一层层向上搭建起整个应用,看起来非常的科学。

试重新思考。

1.用户是否为新机床买账,取决于该机床有没有实现机床开发商声称的功能,还是机床所用到的零件,还是用户说明书?

显然,取决于前者。

所以,应围绕新机床的功能下开发,而不是围绕自制零件、第三方零件的适配器或用户说明书开发。

domain层和infra层都应当依赖于app层,而不是让ui层依赖app层,再让app层去依赖domain层和infra层。

这可以借助接口实现。

我们不再让app层只对应一个具体的控制主板,而是加入对应产品的设计文档,包括新机床功能的设计文档、自制零件的设计文档、复杂自制零件适配器的设计文档、第三方零件适配器的设计文档。

然后,domain和infra共同依赖于这些设计文档,通过设计文档约定的接口实现交互。
可以看出来,domain和infra其实并没有本质区别,因为它们都是零件,唯一的区别就是domain完全是开发者自定义的,infra是结合第三方做的。

2.新机床买来放在那里后并不会自己动起来,为什么?

是因为还缺少了一个关键“零件”——机床操作员。

如果将机床操作员看做“零件”,ui层看作针对机床操作员做的适配器,新机床就真正形成了一个完整的系统。

可以看到,ui层除了是向app输入而非输出外,同样和domin、infra层没有本质区别,只要便于用户使用,那么其实用户也不关心具体的实现。

得出结论

1.domain层、infra层、ui层均依赖于app层

2.app层负责定义应用的功能,定义需要实现的接口,由其他三层实现

3.domain层、infra层、ui层彼此之间互不依赖

4.domain层、infra层、ui层需要用到另外两层的实例时,通过依赖注入获取。

最终,实现高度的解耦,并完全面向服务开发。

springcloud-ddd-application:一系列产品

        common:公司通用资源

        ddd-application:新机床

                application:新机床的设计文档

                        service:新机床功能的设计文档

                                impl:实现新机床功能的控制主板(可以触发符合设计文档标准的零件)

                        entity:自制零件的设计文档

                        domain-service:复杂自制零件适配器的设计文档

                        repository:第三方零件(以数据库为例)适配器的设计文档

                domain自制零件及配套适配器

                        entity:自制零件

                        domain-service:复杂自制零件的适配器

                infrastructure:第三方零件的适配器

                        repository:第三方零件(以数据库为例)的适配器

                user-interface:用户说明书

                        dto:操作步骤说明、预期结果说明

                外部依赖:第三方零件

                        pom.xml:第三方零件

1.ui层加入一个controller包,以适配操作员。

2.加入一个adapter模块,同时依赖于domain层、infra层和ui层,负责完成各种数据的转化。

adapter的assembler包负责完成domain的entityImpl和infra中组件用到的pojo对象(比如XxxPO)之间的转化,converter包负责完成domain的entityImpl和ui中用到的pojo对象(比如XxxDTO)之间的转化。

3.加入一个starter模块,依赖于adapter,负责完成应用的启动。

最终架构如图所示(个人项目原因,和上面描述有些许出入,但90%都是一样的)。

注意到,这种模式虽然仍将业务逻辑写在entity中,但依赖关系相比传统的DDD或是六边形架构都是有所改动的。

不再是app依赖于domain,而是domain、infra和ui一起依赖于app。

而我们都知道,一个应用真正产生价值的部分在于其功能,只要功能是完全等价的,用户就并不关心你是用木头还是石头造出来的机床。

之后,我希望能够继续完善这个架构,并且将与此结合的微服务方面的架构设计也讲述一遍。

ps:为区别于DDD,我希望把这叫做AAA(Application Actuated Architecture,应用激活架构)

其践行的理念为POP(Philosophy-Oriented Programming,面向哲学编程)

还有,这个架构写出来的应用可以跑通。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值