一个系统架构是由一系列组件以及它们之间的边界定义的。而这些边界拥有不同的形式。
跨边界调用是指边界的一侧,调用另一侧的函数,并传递数据的行为。构造合理的跨边界调用,需要我们对源码中的依赖进行管控。
因为当一个模块发生变更时,其他的模块源码可能也会跟着变动。划分边界,就是为了防止这种事情发生。
这是最常见的一种架构,单体结构对源代码进行了解耦分组件(源代码边界),它们的运行仅在同一个进程内。但在部署的角度上来看,架构边界并不存在。在静态语言中,通常以一个可执行文件体现,即所有程序都在这一个执行文件中。
但这并不意味着这种架构就没有意义。即使这种解耦在部署过程中并不可见,但边界的划分还是对该系统的各个组件的独立开发,存在着相当大的意义。
这类架构一般都需要利用某种动态形式的多态,来管理内部依赖。如果不采用面向对象或是类似多态的实现,架构师们就只能退回到例用函数指针,来进行解耦的时代,这太过于危险。
最简单的跨边界调用,就是低层客户端,调用高层服务函数。这种依赖关系,在运行和编译时,方向保持一致。
但是当高层组件的客户端,要调用低层组件中的服务时,我们就需要用多态,来反转依赖关系了。
可以看到,控制流方向,还是和上图一样,由左至右。但是依赖关系,反转过来了。ServiceImpl
依赖左侧了,由低层组件依赖高层组件。
所以说,即使是在单体项目中,这种组件划分,仍然对开发,测试,部署,起了极大作用,它可以使得不同的团队一起协作开发,高层组件与低层组件之间也可以得到良好隔离,独立演进。
架构系统中最常见的物理边界形式:动态链接库。如.Net
的DLL
,Java
中的jar
文件,以及Unix
中的共享库等。这种类型的文件,在部署时不需要重新编译,因为他们已经是二进制的或者已经编译过后的文件。这种属于部署层次上的解耦。
这种按部署层次上的解耦组件,几乎和单体结构一样,也是运行在同一个进程,同一个地址空间的。管理组件,划分依赖的策略基本上和单体结构也一样。它们的跨边界调用也只是普通的函数调用。
单体结构和按部署层次的划分,都可以采用线程模型。当然,线程不属于架构边界,也不属于部署单元。它仅仅是一种管理并调度程序的执行方式。一个线程既可以被包含在单一组件中,也可以横跨多个组件。
系统架构还有更明显的物理边界,那就是本地进程。本地进程运行在一个处理器或者同一组处理器上,进程与进程的内存是隔离的,但它们可以通过某个独立的内存区域来实现共享。
每个本地进程,既可以是静态链接的单体结构,也可以是由动态链接的多个组件构成。本地进程的隔离策略也与单体结构,二进制组件基本相同,其源码的依赖关系和跨越边界的方向是一致的,始终指向更高层次的组件。其目的是让低层次进程成为高层进程的一个插件。
系统架构中最强的边界形式就是服务。一个服务就是一个进程。其服务不依赖于具体的运行位置,它们既可以运行在同一个服务器上,也可以位于不同的服务器上。因为服务会假设它们的所有通信都是由网络来进行。
服务之间的通信,相较于函数调用来说,速度是非常缓慢的,所以我们需要控制通信次数。此外,依然要使用低层次服务应当是高层次服务的插件这个规则。为此我们要保证,高层次服务中,不会包含任何低层次服务相关的物理信息,如URI
。
除单体结构外,多数系统都会同时采用多种边界划分策略。无论是服务还是本地进程,它们几乎肯定都是由一个或多个源码组件组成的单体结构,或者一组动态链接的可部署组件。只是形式不一样。
这也意味着一个系统中,通常会同时包含高通信量,低延迟的本地架构边界,和低通信量,高延迟的服务边界。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。