最近有一位同事在周会上做了有关SOA的分享,在开始时提出了一个问题,在我们开始一个项目时,是按照功能层面进行架构分拆(例如Customer项目,Order项目),还是按照结构层面进行架构分拆(例如Model层,View层)。恰巧在前几天的一个后台项目重构过程中,一个同事也问了我类似的问题,所以想在这里简单记录下。
分层架构
1 我们先看传统的分层架构(Layer Pattern),它是以结构层面对软件项目进行分层划分,将应用程序划分为多个子任务,让每个任务处于一个单独的架构层,完成独立的工作,常见的例子包括我们在项目中常常涉及的软件三层架构以及我们一直接触到的OSI五层/七层模型。分层架构听上去非常简单,但是我在这里要提一些理论上需要注意的地方:
2 第N层只能调用第N-1层的服务。这是分层架构最重要的一点,所以很多人把MVC与三层架构相混淆时请思考这一点,MVC模式中MVC之间互相耦合,Model在Controller和View之间传递,Controller处理View的逻辑,他们并不是一个分层的关系。而三层架构将用户界面层,业务逻辑层,数据访问层通过具体的接口方式彼此隔离,不存在跨层的依赖关系。
3 在第N-1层缓存第N层需要调用的数据。分层最大的诟病其实就在于层层调用而产生的性能消耗,所以对于分层模式来说,缓存是非常重要的一环。但是请一定注意一点,缓存是处于被调用方而非调用方,这一点有两个原因: <1> 调用方无需知道这一数据的来源,是数据库读取还是直接缓存提供 <2> 调用方维持数据缓存会遇到复杂的数据同步问题。
4 为每一层起名。这一点看上去会很奇怪,感觉像是流入了形式主义。其实这一点的更大的作用是,名字可以让设计者、使用者更加明确这一层的作用,避免出现层次的不清晰划分。
5 制定好每层的错误处理策略。在大学学习Java的异常处理时一直没有想清楚,异常处理部门throw的作用,当时觉得其实出错的时候打印出错误日志或者让程序异常终止不就可以了么,为什么需要抛出呢?后来结合到分层的架构才想清楚,在分层逻辑中,每一层都有自己的错误处理步骤,例如在数据访问层遇到数据访问异常可能需要尝试重连,如果依然有问题就向上抛出,那么落实到业务逻辑层可能需要的是访问脏数据或者第三方数据,如果依然有问题就向上抛出,真正需要捕获异常而Break程序或者提示用户的只有用户界面层而已。那么落实到更抽象的层面,我们需要考虑层与层之间的调用方式,从而决定了是以异常方式丢给用户,还是状态码,或者是具体的错误信息。
而落实到实际的应用层面,我们却需要对分层架构做出这样那样的妥协和改进:
1 层的穿透。在之前我提过,在标准的分层架构中,第N层只能调用第N-1层的服务,可是在一些场景中是不恰当的。分层的主要问题是性能损失,引起性能损失的原因包括层层调用而产生的网络消耗和方法调用成本,以及中间层必须维持抽象而产生的冗余成本。对于一些对性能要求较高的系统而言,例如操作系统,那么需要为了维持性能而做出跨层调用,但是需要做出平衡的是底层方法修改带来的维护成本 和 性能之间的平衡。
2 层与层之间的接口究竟抽样到何等程度。我们在实际工作中往往发现我们设计分层的目的之一是为了重用,但是实际重用性并不高;开发人员往往的理由在于现有的接口与目标需求并不匹配,所以需要新开接口。而接口开发人员的理由很简单,返回过多的字段(过多的抽象)会产生不必要的性能和网络消耗,那么如何平衡两者依然是我们需要平衡的艺术。
3 层与层之间建立共享数据、方法。我们希望尽可能地减少层与层之间的依赖,希望更好地保持数据同步(例如避免脏读),我们可以在整个系统层面建立共享数据(例如共享内存),从而提高效率。例如我们无法逃脱用户权限的逻辑,所以可以将Auth模块放在公共的服务器上供所有模块访问。
4 层与层之间的解耦。层与层解耦有很多方式,最简单的方式可以通过API调用的方式进行解耦,当然通过消息队列也是一种现代软件系统的变种方式。
5 MVC作为分层架构的退化。在互联网领域,我们已经越来越少听到三层架构了,而大部分从业者也开始逐渐把MVC当作为三层架构,当然这自有基础知识的欠缺,但是也确实容易造成这样的误会。互联网逻辑相对简单,复杂的地方基本都是HTML端相对复杂的逻辑,以及数据访问的优化,所以这时三层架构的业务逻辑层就已经与用户界面层的Controller等同到了一起,数据访问层与Model层混合到一起。当然对于小型项目无可厚非,但是我个人更加倾向于将数据访问层依然独立出来,成为互联网界更加知名的Service层,其实我无法说Service层是属于业务逻辑还是数据访问,原因有二: <1> 互联网对性能的极致追求,使得我们不便做过多的抽象,所以业务逻辑往往都被和数据库的SQL访问产生强依赖 <2> Service层往往需要自己管理数据的Cache,所以从这一点来说他也相当于混合了业务逻辑层和数据访问层的工作。
最后,我们从问题展开再回到问题本身,到底应该以功能层面分拆还是以软件结构进行分拆,该同事给出的答案是No Bullet,在这里我想更展开说一下,我们回到分层的目的,其实无外乎三者: 分工、重用、可维护。而软件的架构本身是强调“高内聚,低耦合”。所以我们在划分该架构时也要遵循上面的原则,那么更加标准的答案应该是优先功能划分,然后结构划分。例如CRM与统计功能彼此独立,那么应该将二者分开,是否应该划分的逻辑很简单,这两者之间有什么需要共享的模块(Service),有多少重复的方法。但是过度的结构划分会产生重用性变低的情况,我曾经见过每个功能划分一个MVC,例如Note, User,这会造成更大的混乱。
公司架构
最近一年极光推送的快速成长,已经从几十人的小公司发展为150余人的公司,所以我进来花了更多精力去思考公司的组织结构问题,希望能让公司更加高效的运转。
其实公司的组织与软件架构是同样的逻辑:
1 高内聚低耦合。无论我们承认还是不承认,跨部门沟通永远是一个公司最大的痛,所以作为组织架构的设计,最重要的就是如何使跨部门沟通变得最少,或者只有一个接口来做这件事情。从这一点来看,也许我们需要对组织架构做更多的思考。
2 关于业务逻辑分还是软件功能分。这一点其实对应的是我们到底是以事业部的方式组织,还是以职责部门的方式组织。在我看来依然是同样的逻辑,当项目足够多的时候,一定要优先以事业部的方式划分,原因很简单,提高整个部门的内聚性,每当任何一个问题产生时,总会有一个中间人(事业部GM)来站出来平衡利益关系,这个利益包含技术和产品的博弈,技术和测试的博弈,产品和运营的博弈等等。之前我给老板讲过这样一个逻辑,为什么一个CEO下面最多只能有四到五个高管,因为每当两个高管进行跨部门合作沟通时,就需要一个利益无关人平衡双方关系,如果是5个人,那么就是(5*4 / 2 = 10)种组合关系,而当这个数字增加到10时,组合关系就变成了45种,这还没有算上三个部门合作的组合关系。这也是我为何一直认为扁平化组织关系并不可行的原因。
3 公共组件的搭建。我们思考下我们的软件架构是不是设置了太多的公共组件,例如我们认为认证模块是公共组件,转而一想好像数据访问也是公共组件,其实每个组件只有一到两个模块在调用。这个产生的原因在于我们基础架构的划分不合理。太多公司也是类似的问题,例如某大型互联网公司的公共技术部门,为何发挥不了作用,最大的原因在于这并不是一个公共技术,例如打算把统计平台做成一个公共技术,疏不知统计这件事情与业务紧密结合,真正抽象出来的应该是更好用的Hive和ETL工具罢了。我们需要把“公共部门”变得尽量薄,如果我们发现一个部门与太多部门之间都存在着调用关系,这时我们就需要思考是否组织结构不合适,或者是否该部门应该被分拆了。
作者:飞林沙,目前就职于极光推送