昨天妹子让我帮她解决个问题,本以为可以轻松搞定,但是打开他们项目的一瞬间,我头皮发麻。本身功能不多的一个小项目,解决方案里竟然有几十个类库。仅仅搞明白各个类库的作用,代码层次之间的引用关系就花了一个多小时。

显然可能他们项目结构的代码模型出了问题,设计混乱,不容易上手。

项目中一个好的的代码模型一定是简单、清晰、明了、易于上手的。它总是会让人用起来很舒服,它可以让项目团队成员更好地理解代码,根据代码规范实现团队协作;它可以让各层的逻辑互不干扰、分工协作、各据其位、各司其职,避免不必要的代码混淆。它可以让我们的代码扩展性很好,可以让系统的可维护性更好......

而代码模型的搭建跟项目的分层架构有关系,绝大多数项目开发中都会会采用分层开发,它有着分散关注、松散耦合、逻辑复用、标准定义、扩展性强等众多好处。而在分层架构中最常用的有两种:三层架构和DDD(领域驱动)分层架构。我们选择的分层方式也决定了我们的项目结构中的代码模型。

1.三层架构

三层架构是一种严格分层模式,而严格分层架构模式的特点是上层只能访问相邻的下层,其他层次间的调用都不允许。它把职责划分为界面展示、业务逻辑、数据访问三层,还有一个业务实体,前面三层都要依赖它,所以它并不构成一个层。

说一说Web开发中两种常用的分层架构及其对应的代码模型-LMLPHP

三层架构是一种面向过程的编程思想,它有几个特点

  • 业务实体类中基本上只有属性没有方法。
  • 业务逻辑层的类基本上只有方法没有属性。

在使用三层架构开发的时候我们通常会直接将数据表结构映射为业务实体类。这样的好处是只需要理解一套模型,以至于市场上也产生了一系列的代码生成工具可以一键生成实体和增删改查的代码。但对于复杂点的业务,这样做也会产生很多的问题。

而当业务不再是简单的增删查改时,我们可以在三层架构的基础上有个简单的变形:提取一个服务层出来,用来组合模块间的交互,还为业务逻辑层提供了一个防腐层,可以把记录日志、验证权限、处理异常等职责分配给服务层。

说一说Web开发中两种常用的分层架构及其对应的代码模型-LMLPHP

2. DDD分层架构

三层架构有一个显著缺点就是它的内存模型不是基于业务模型而是是基于数据库模型建立的。而很多时候我们的业务模型并不能和我们的数据库模型完全吻合。

例如:如果你的数据库模型的粒度很小。有些业务就需要连接多张数据库表才能实现。而如果数据库模型的粒度很大(这是大部分项目的选择),代码的质量(重用性、稳定性、扩展性)就很差。由于没有从业务的角度去仔细定义每一个对象,每个人会根据自己的需要建立各种QueryModel或ViewModel(相信三层架构用久了的同学都会遇到这个问题)。

而且现在很多大型系统都会采用分布式的架构,如现在的微服务架构。服务内不仅仅是简单的增删查改,会有更多的与其它服务交互的内容,而服务的拆分也是以业务模型为导向的。

这个时候DDD(领域驱动)分层架构就会更有优势。

说一说Web开发中两种常用的分层架构及其对应的代码模型-LMLPHP

DDD分层架构主要包含四层:用户接口层、应用层、领域层、基础层。

  • 用户接口层:面向前端提供服务适配,面向资源层提供资源适配。这一层聚集了接口适配相关的功能。
  • 应用层:位于领域层的上一层,理论上不应该有业务规则或逻辑。主要用来实现服务组合和编排,协作完成业务操作,负责处理业务用例的执行顺序以及结果的拼装,以粗粒度的服务通过 API 网关向前端发布提供安全认证、权限校验、事务控制、发送或订阅领域事件等功能。
  • 领域层:实现领域的核心业务逻辑。这一层聚集了领域模型的聚合、聚合根、实体、值对象、领域服务和事件等领域对象,以及它们组合所形成的业务能力。
  • 基础层:贯穿所有层的,为其它各层提供通用的技术和基础服务,包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。比较常见的功能还是提供数据库持久化。基础层包含基础服务,它可以采用依赖倒置设计,封装基础资源服务,实现应用层、领域层与基础层的解耦,降低外部资源变化对应用的影响。

说一说Web开发中两种常用的分层架构及其对应的代码模型-LMLPHP

DDD分层架构也分为严格分层架构和松散分层架构,在严格分层架构中,领域服务只能被应用服务调用,而应用服务只能被用户接口层调用,服务是逐层对外封装或组合的,依赖关系清晰。而在松散分层架构中,领域服务可以同时被应用层或用户接口层调用,服务的依赖关系比较复杂且难管理。因此我个人推荐使用严格的分层架构

3.如何选择三层架构还是DDD分层架构

对于项目开发中应该如何选择我们可以基于两点考虑:系统本身的业务复杂度和团队人员能力。

3.1 系统本身的业务复杂度

如果你们的系统只是个简单的小系统有或者系统本身业务并不复杂,基本都是些增删改查。那么三层架构将是你良好的选择。它会让你的开发变得简单,会大大提高你的开发效率,而且这种分层架构的学习成本很低,新人简单熟悉就可以很容易的上手。对于简单的系统使用DDD分层,反而增加了系统的复杂度。

而如果你们的系统很复杂或者对于一些成长性的网站系统(一开始很小,随着业务发展慢慢壮大的系统),那么你可以考虑使用DDD分层架构,DDD的思想可以让你更好的对业务进行建模,这种分层架构会让你的系统扩展性很好,在网站壮大的过程中,单体系统必然会向分布式系统进行发展,而DDD的思想可以对服务进行很好的拆分,如当下的微服务架构,DDD的思想就可以帮助我们很好的定义服务的边界。

3.2 团队人员能力

团队人员的能力仍然是选择要考虑的因素。因为DDD对于人员的能力要求相对于三层架构要高。它需要团队的人员要有一个良好的逻辑思想和建模能力,而且DDD的学习成本也要高一些。如果你的团队成员能力一般或者以入行不久的新人为主,或者你的团队人员流动性较大的话,也是不建议使用DDD的。这种情况下使用三层架构会更好一些。

总有些人会觉得DDD的概念“高大上”,因此为了使用DDD而使用DDD,更有甚至,根本都没有真正弄明白DDD,就开始使用,最终搞的个四不像,不仅没有解决问题,反而徒增了系统复杂度,拉低性能和效率,其实真正项目中改如何选择,应该结合你的团队和系统来,权衡利弊综合考虑。简单、优雅、方便、快捷的解决问题岂不是更好?

4.两种分层架构对应的代码模型

一旦选定了分层架构,项目中所对应的代码模型也就确定了。我们以.net为例(java只需要把程序集当成jar包来看就可以了),推荐下面这两种代码模型。

4.1 三层架构

说一说Web开发中两种常用的分层架构及其对应的代码模型-LMLPHP

这是最简单的三层架构代码模型,业务逻辑层,数据访问层,应用接口层(界面层,界面层可以是mvc,也可以是webapi。(因为现在很多项目都是前后端分离,服务端开发人员不需要写页面,所以就没有写MVC,界面层我也改叫用户接口层了)。。当然现在几乎是没有人这么用的。因为这样做的依赖性很高。不利于扩展。因此我们要引入依赖倒置的概念。因此我们的结构需要做如下变形

说一说Web开发中两种常用的分层架构及其对应的代码模型-LMLPHP

这种结构在简单三层的基础上对业务逻辑层和数据访问层引入了其抽象层,这样就很好的将层与层之间的依赖关系进行了解耦。

比如,我曾经遇到多个项目数据库从MSSQL转到MySql。在三层架构中,其实大量的逻辑都应该被封装在业务逻辑层。这个时候我们是需要把DAL换成MySql的DAL,业务逻辑不需要任何改动。如果是最简单的三层架构那种绝对依赖关系,我们必然要改动业务逻辑层以接入新的DAL。而这种依赖倒置的层次结构则不需要。

由于三层架构中同层引用时应该避免的。业务逻辑也是基于数据库模型建立的,而有些业务则需要组合多个业务来实现,为了实现业务逻辑代码复用有些可以采用继承和多态的方式来实现。有些时候则需要再引入一个服务层(防腐层)来组合模块间的交互。也可以把记录日志、验证权限、处理异常等职责分配给这一层。

说一说Web开发中两种常用的分层架构及其对应的代码模型-LMLPHP

很多项目可能还要加个工具层,用来放一些常用的工具类。但是工具类这个东西,与项目有关的可以放在这里,如果是多项目之间可以复用的,最好用更专业的做法:单独管理维护,打包成Nuget包(maven包),由各个项目进行调用

4.2 DDD分层代码模型

按照 DDD 分层架构模型设计出来的代码模型应该长什么样子呢?

说一说Web开发中两种常用的分层架构及其对应的代码模型-LMLPHP

如上图所示:

  1. 用户接口层
  • Controller:提供较粗粒度的调用接口,将用户请求委派给一个或多个应用服务进行处理
    • DTO:数据传输的载体,内部不存在任何业务逻辑,我们通过 DTO 把内部的领域对象与外界隔离。
    • Mapper: 实现 DTO 与领域对象之间的相互转换和数据交换。
  1. 应用层

    • Event:存放事件相关代码,可以进行事件的发布和处理。事件发布和订阅放在应用层,事件相关的业务逻辑放在领域层。

    • Service:对多个领域服务或外部应用服务进行封装、编排和组合,对外提供粗粒度的服务。应用服务主要实现服务组合和编排,是一段独立的业务逻辑。

  2. 领域层

    • Aggregate:是Entity、Event、Service、Repository的根目录,聚合内实现高内聚的业务逻辑,可以根据实际项目中业务的聚合名称命名。在聚合内定义聚合根、实体和值对象以及领域服务之间的关系和边界。如果我们的项目需要进行微服务的拆分,那么一个聚合里的内容可以拆分为一个单独的服务。
    • Entity:用来存放聚合根、实体、值对象以及工厂模式(Factory)相关代码。实体类采用充血模型,同一实体相关的业务逻辑都在实体类代码中实现。跨实体的业务逻辑代码在领域服务中实现。
    • Event:存放事件实体以及与事件活动相关的业务逻辑代码。
    • Service:存放领域服务代码。一个领域服务是多个实体组合出来的一段业务逻辑。领域服务封装多个实体或方法后向上层提供应用服务调用。
    • Repository:存放所在聚合的查询或持久化领域对象的代码,通常包括仓储接口和仓储实现方法。为了方便聚合的拆分和组合,最好一个聚合对应一个仓储。
  3. 基础设施层

    • Config:主要存放一些配置相关代码。
    • Util:其它诸如数据库、缓存、文件,第三方类库相关的代码可以在这个目录下建立子目录。

在DDD的代码模型中需要注意的是:

  • 分层的概念一定要清晰,明确各层的职责。
  • 聚合的代码边界一定要清晰,聚合之间一定是松耦合低关联的。

小结

毫无疑问,项目中选择合适的分层架构并设计一个优秀的代码模型有着巨大的好处,但实际上无论是三层架构还是DDD分层架构都没有明确的规定其标准的代码模型。因此以上两种代码模型仅供大家参考。

而经常会有些设计者在设计的时候总喜欢“炫技”,设计出来的代码模型“深奥复杂”、晦涩难懂,美其名曰“高大上”。熟不知大道至简,优秀的设计是用简单的方式解决复杂的问题,而不是把简单的问题复杂化,在解决问题的基础上,简单实用才是正途!

最后,希望每个项目都能有一个好的设计人员,结合实际情况,设计出一个好的代码模型,利己利人!

05-07 15:03