架构简洁之道-5.整洁架构
同样的,这一层的代码也会负责将数据从对业务实体与用例而言最方便操作的格式,转化为对所采用的持久性框架(譬如数据库〉最方便的格式。例如,在Java这一类的语言中,可以通过调整代码中的接口和继承关系,利用源码中的依赖关系来限制控制流只能在正确的地方跨越架构边界。最内层的圆中包含的是最通用、最高层的策略,最外层的圆包含的是最具体的实现细节。换句话说, 就是任何属于内层圆中的代码都不应该牵涉外层圆中的代码
图中的同心圆分别代表了软件系统中的不同层次,通常越靠近中心,其所在的软件层次就越高。基本上,外层圆代表的是机制,内层圆代表的是策略。当然这其中有一条贯穿整个架构设计的规则。
即它的依赖关系规则:源码中的依赖关系必须只指向同心圆的内层,由低层机制指向高层策略。换句话说, 就是任何属于内层圆中的代码都不应该牵涉外层圆中的代码,尤其是内层圆中的代码不应该引用外层圆中代码所声明的名字,包括函数、类、变量以及一切其他有命名的软件实体。
同样的道理,外层圆中使用的数据格式也不应该被内层圆中的代码所使用,尤其是当数据格式是由外层圆的框架所生成时。总之,我们不应该让外层圆中发生的任何变更影响到内层圆的代码。
业务实体
业务实体这一层中封装的是整个系统的关键业务逻辑, 一个业务实体既可以是一个带有方法的对象,也可以是一组数据结构和函数的集合。无论如何,只要它能被系统中的其他不同应用复用就可以。如果我们在写的不是一个大型系统,而是一个单一应用的话,那么我们的业务实体就是该应用的业务对象。这些对象封装了该应用中最通用、最高层的业务逻辑,它们应该属于系统中最不容易受外界影响而变动的部分。例如,一个针对页面导航方式或者安全问题的修改不应该触及这些对象, 一个针对应用在运行时的行为所做的变更也不应该影响业务实体。
用例
软件的用例层中通常包含的是特定应用场景下的业务逻辑,这里面封装并实现了整个系统的所有用例。这些用例引导了数据在业务实体之间的流入/流出,并指挥着业务实体利用其中的关键业务逻辑来实现用例的设计目标。我们既不希望在这一层所发生的变更影响业务实体,同时也不希望这一层受外部因素(譬如数据库、UI 、常见框架)的影响。用例层应该与它们都保持隔离。然而,我们知道应用行为的变化会影响用例本身,因此一定会影响用例层的码。因为如果一个用例的细节发生了变化,这一层中的某些代码自然要受到影响。
接口适配器
软件的接口适配器层中通常是一组数据转换器,它们负责将数据从对用例和业务实体而言最方便操作的格式,转化成外部系统(譬如数据库以及Web )最方便操作的格式。例如,这一层中应该包含整个GUI MVC 框架。展示器、视图、控制器都应该属于接口适配器层。而模型部分则应该由控制器传递给用例,再由用例传回展示器和视图。同样的,这一层的代码也会负责将数据从对业务实体与用例而言最方便操作的格式,转化为对所采用的持久性框架(譬如数据库〉最方便的格式。总之,在从该层再往内的同心圆中, 其代码就不应该依赖任何数据库了。譬如说,如果我们采用的是SQL 数据库,那么所有的SQL 语句都应该被限制在这一层的代码中一一而且是仅限于那些需要操作数据库的代码。当然,这一层的代码也会负责将来自外部服务的数据转换成系统内用例与业务实体所需的格式。
框架与驱动程序
图中最外层的模型层一般是由工具、数据库、Web 框架等组成的。在这一层中,我们通常只需要编写一些与内层沟通的勤合性代码。
框架与驱动程序层中包含了所有的实现细节。Web 是一个实现细节,数据库也是一个实现细节。我们将这些细节放在最外层,这样它们就很难影响到其他层了。只有四层吗图中所显示的同心圆只是为了说明架构的结构,真正的架构很可能会超过四层。并没有某个规则约定一个系统的架构有且只能有四层。然而,这其中的依赖关系原则是不变的。也就是说,源码层面的依赖关系一定要指向同心圆的内侧。层次越往内,其抽象和策略的层次越高, 同时软件的抽象程度就越高其包含的高层策略就越多。最内层的圆中包含的是最通用、最高层的策略,最外层的圆包含的是最具体的实现细节。
跨越边界
在图的右下侧,我们示范的是在架构中跨边界的情况。具体来说就是控器、展示器与下一层的用例之间的通信过程。请注意这里控制流的方向:它从控制器开始,穿过用例,最后执行展示器的代码。但同时我们也该注意到,源码中的依赖方向却都是向内指向用例的。
这里,我们通常采用依赖反转原则( DI 归来解决这种相反性。例如,在Java这一类的语言中,可以通过调整代码中的接口和继承关系,利用源码中的依赖关系来限制控制流只能在正确的地方跨越架构边界。假设某些用例代码需要调用展示器,这里一定不能直接调用,因为这样做会违反依赖关系原则: 内层圆中的代码不能引用其外层的声明。我们需要让业务逻辑代码调用一个内层接口(图中的“用例输出端”〉,并让展示器来负责实现这个接口。我们可以采用这种方式跨越系统中所有的架构边界。利用动态多态技术,我们将源码中的依赖关系与控制流的方向进行反转。不管控制流原本的方向如何,我们
都可以让它遵守架构的依赖关系规则。
哪些数据会跨越边界
一般来说,会跨越边界的数据在数据结构上都是很简单的。如果可以的话,我们会尽量采用一些基本的结构体或简单的可传输数据对象。或者直接通过函数调用的参数来传递数据。另外, 我们也可以将数据放入哈希表,或整合成某种对象。这里最重要的是这个跨边界传输的对象应该有一个独立、简单的数据结构。总之,不要投机取巧地直接传递业务实体或数据库记录对象。同时,这些传递的数据结构中
也不应该存在违反依赖规则的依赖关系。例如,很多数据库框架会返回一个便于查询的结果对象,我们称之为“行结构体”。这个结构体不应该跨边界向架构的内层传递。因为这等于让内层的代码引用
外层代码,违反依赖规则。因此,当我们进行跨边界传输时, 一定要采用内层最方便使用的形式。
更多推荐
所有评论(0)