如何正确地理解应用架构并开发(中)

简介: 如何正确地理解应用架构并开发(中)

如何正确地理解应用架构并开发(上):https://developer.aliyun.com/article/1443483


应用架构的核心

纵观上面介绍的所有应用架构,我们可以发现一个共同点,就是“核心业务逻辑和技术细节分离”。


是的,六边形架构、洋葱圈架构的核心职责就是要做核心业务逻辑和技术细节的分离和解耦


试想一下,业务逻辑和技术细节糅杂在一起的情况,所有的代码都写在ServiceImpl里面,前几行代码是做validation的事,接下来几行是做convert的事,然后是几行业务处理逻辑的代码,穿插着,我们需要通过RPC或者DAO获取更多的数据,拿到数据后,又是几行convert的代码,在接上一段业务逻辑代码,然后还要落库,发消息.....等等。


再简单的业务,按照上面这种写代码的方式,都会变得复杂,难维护。


因此,我认为应用架构的核心使命就是要分离业务逻辑和技术细节让核心业务逻辑可以反映领域模型和领域应用,可以复用,可以很容易被看懂。让技术细节在辅助实现业务功能的同时,可以被替换


如何正确的进行工程划分?

这些应用架构思想虽然很好,但我们很多同学还是“不讲Co德,明白了很多道理,可还是过不好这一生”。


所以我们接下来聊聊这个偏工程实践的话题。


 业务工程中最基本的Module划分


上文提到了Moudle和包之间的关系,我们有个基本共识,Module比Package(包)要大一些。那我们基本的分层结构又应该是怎么样的呢,这里列两种业务工程中最常见的Module之间的依赖关系:


  • 非依赖倒置下的Module分层


  • 依赖倒置(DIP)下的Module分层(推荐)

依赖倒置(DIP):

简单说依赖倒置就是你不要直接依赖我,你和我都同时依赖一个接口(所以有时候也叫面向接口的编程),这样我们之间就解耦了,依赖和被依赖方都可以自由改动了。

  • 依赖倒置下的Module分层的好处


  1. Domain层会变得更加纯粹,完全摆脱了对技术细节(以及技术细节带来的复杂度)的依赖,只需要安心处理业务逻辑就好(我们经常说的核心业务逻辑稳定其实就是Domain层的稳定,上文中的Domain层完全依赖于接口和内部的模型,屏蔽了技术细节,如果我们更换技术组件,也只需要更换技术细节的实现即可)
  2. 并行开发:只要在Domain和Infrastructure约定好接口,可以有两个同学并行编写Domain和Infrastructure的代码
  3. 可测试性:没有任何依赖的Domain里面都是POJO的类,单元测试将会变得非常方便,也非常适合TDD的开发。


分层职责

1.适配层(Adapter Layer):负责对前端展示(web,wireless,wap)的路由和适配,对于传统B/S系统而言,adapter就相当于MVC中的controller

用户展现信息以及解释用户命令

大部分工程为start模块

2.应用层/应用服务层(Application Layer):主要负责获取输入,组装上下文,参数校验,调用领域层做业务处理,如果需要的话,发送消息通知等。层次是开放的

应用层也可以绕过领域层,直接访问基础实施层(CQRS模式中对于查询的说法就是可以绕过DomainModel之间查询数据)

很薄的一层,用来协调应用的活动。它不包含业务逻辑

同样的一个Service应可被不同的Adapter(Web、远程调用、异步消息)复用)

适合处理事务、高层次日志(oplog)、安全(权限)

3.领域层(Domain Layer):主要是封装了核心业务逻辑,并通过领域服务(Domain Service)和领域对象(Domain Entity)的方法对App层提供业务实体和业务逻辑计算。

领域是应用的核心,不依赖任何其他层次

通常每一个聚合(aggregate)一个package。聚合包含实体(entity),值对象(value object),领域事件(domain event),资源库(repository,仅接口)接口和一些工厂(Factory)。

4.基础实施层(Infrastructure Layer):主要负责技术细节问题的处理,比如数据库的CRUD、搜索引擎、文件系统、分布式服务的RPC等。此外,领域防腐的重任也落在这里,外部依赖需要通过防腐层(Anti-Corruption) 实现的转义处理,才能被上面的App层和Domain层使用。


 如果我要提供一个SDK对外提供服务怎么办?


Module的定义应该遵循自身业务需求(如无必要勿增实体)

上文中如果需要一个api包则增加一个API Module包即可

这里的client包不要依赖Domain,这会引入大量无关依赖,对包的使用者造成困扰


 包结构的划分


分层是属于大粒度的职责划分,太粗,我们有必要往下再down一层,细化到包结构的粒度,才能更好的指导我们的工作。


还是拿一堆玩具举例子,分层类似于拿来了一个架子,分包类似于在每一层架子上又放置了多个收纳盒。所谓的内聚,就是把功能类似的玩具放在一个盒子里,这样可以让应用结构清晰,极大的降低系统的认知成本和维护成本

那么,对于一个后端应用来说,应该需要哪些收纳盒。


各个包结构的简要功能描述,如下表所示:

  • 你可能会有疑问,为什么Domain的model是可选的?


因为是应用架构,不是DDD架构。对于应用架构来说:无有必要勿增实体领域模型对设计能力要求很高,没把握用好,一个错误的抽象还不如不抽象,宁可不要用,也不要滥用,不要为了DDD而DDD

问题的关键是要看,新增的模型没有给你带来收益。比如有没有帮助系统解耦,有没有提升业务语义表达能力的提升,有没有提升系统的可维护性和可测性等等


模型虽然可选,但DDD的思想是一定要去学习和贯彻的,特别是统一语言、边界上下文、防腐层的思想,值得深入学习,仔细体会。实际上,应用架构里面的很多设计思想都来自于DDD。其中就包括领域包的设计。


前面的包定义,都是功能维度的定义。为了兼顾领域维度的内聚性,我们有必要对包结构进行一下微调,即顶层包结构应该是按照领域划分,让领域内聚


也就是说,我们要综合考虑功能和领域两个维度包结构定义。按照领域和功能两个维度分包策略,最后呈现出来的,是如下图所示的顶层包节点是领域名称,领域之下,再按功能划分包




下图是一个例子,按照分包策略,我们在每一个Module下面首先按照领域做一个顶层划分,然后在领域内,再按照功能进行分包。



如何解决应用架构中的问题


 应用架构中如何处理解耦问题


高内聚,低耦合”这句话,你工作的越久,就越会觉得其有道理。


所谓耦合就是联系的紧密程度,只要有依赖就会有耦合,不管是进程内的依赖,还是跨进程的RPC依赖,都会产生耦合。依赖不可消除,同样,耦合也不可避免。我们所能做的不是消除耦合,而是把耦合降低到可以接受的程度。在软件设计中,有大量的设计模式,设计原则都是为了解耦这一目的。


在DDD中有一个很棒的解耦设计思想——防腐层(Anti-Corruption),简单说,就是应用不要直接依赖外域的信息,要把外域的信息转换成自己领域上下文(Context)的实体再去使用,从而实现本域和外部依赖的解耦。


  • 防腐层设计在应用架构中的应用


我们把防腐层(Anti-Corruption)这个概念进行泛化,将数据库、搜索引擎等数据存储都列为外部依赖的范畴。利用依赖倒置,统一使用防腐层(Anti-Corruption)/Repository等接口来实现业务领域和外部依赖的解耦。
其实现方式如下图所示,主要是在Domain层定义防腐层(Anti-Corruption)/Repository等接口,然后在Infrastructure提供防腐层(Anti-Corruption) 接口的实现。

  • 高内聚低耦合在包划分中的体现


刚刚我们提到了在包划分的过程中,推荐先按照领域分包,这其实就是一种高内聚低耦合,这样做的好处有以下几方面:

  1. 明确这个包下面提供的类和功能应该放在一起考虑
  2. 通过包的可见性现在自身领域服务(功能)对外的可见性(重点)

a.写java的同学经常会给领域模型DomainModel上直接加上Setter方法,这样会比较方便构造领域对象,但是这样的做法其实违反了开闭原则,意味着你的领域对象中的所有属性对外都是暴露的,可以直接通过set的方式而不是通过领域对象提供的方法来进行变更,这对领域对象的稳定性来说很不友好b.通过包的可见性来暴露自身想暴露的能力这里推荐一种比较好的做法:

  1. 对于setter方法以及内部调用的方法设置为包可见(package private)
  2. 通过构造函数/Factory(复杂的聚合根)创建领域对象


 如何对齐文中的概念和实际业务中的Module?


由于各个团队之间自身规范的问题以及建项目的同学自身对应用架构的理解不同,你可能会发现如下几种情况:


业务系统中的Module层数和文中描述的不同

  1. 业务复杂度决定了包的层级,对于简单的web系统,单module其实更能降低架构复杂度
  2. 拆分粒度不同,有些工程可能会拆分出类似Infrastruct-client(对应上文中的防腐层(Anti-Corruption)Interface)这样的Module
  3. 业务诉求不同:有些业务系统并不需要提供API,而是通过DTS/Controller驱动业务,自然也就不需要API Module


业务系统中的Module命名和文中描述不同

  1. 每个人对module的命名问题,实际还是要看这个module承载的业务职责
  2. 模块的职责不同,比方说对外提供的富客户端一般以Client命名,接口包一般就叫API


业务系统中Module的依赖关系和文中不同

  1. 有没有可能是分层太多倒置后来的同学也搞不清楚该怎么依赖了,导致的错误依赖
  2. 文中也提供了好几种依赖,就像地心说和日心说一样,对于应用结构的认识是有一个过程的


业务系统中的包划分和文中不同

  1. 很多业务系统直接按照功能分包了,比如repository放一起(我认为对于实现来说是ok的,对于领域模型来说还是要有领域的概念)


  • 那么如何找到这个工程目前的工程规范呢?


  1. 如果有工程结构的文档,优先阅读(省时间)
  2. 找一个Use Case,从入口侧去看文中的业务开发中涉及到的业务逻辑写在了哪个包中

注:Use Case是《架构整洁之道》里面的术语,简单理解就是响应一个Request的处理过程

 如果业务代码中的应用架构不合理,你有自己的想法怎么办?


  1. 工程刚开始搭建阶段:大家团队内进行讨论,并产出应用架构图,统一领域语言:module分布、包命名规范、类命名规范等,再进行开发
  2. 工程不复杂,且大家都认可新的应用架构:拉分支修改,改造后进行功能的回归测试,组内review完没问题将变更合并入主干分支
  3. 工程比较复杂或者你只是想让你这部分代码符合你的规范:并不建议这样做,对于一个应用工程来说,不同代码风格往往比合理的工程架构更致命,会导致工程结构混乱,大家理解困难,一个统一的工程规范远比合理更重要


如何编写业务代码?


 DDD下业务代码的组织方式


  • “写操作”在DDD中的实现3种场景,分别是


  1. 通过聚合根完成业务请求,这是DDD完成业务请求的典型方式
  2. 通过Factory完成聚合根的创建
  3. 通过DomainService完成业务请求


  • 读操作,同样给出了3种方式


  1. 基于领域模型的读操作(读写操作糅合在了一起,不推荐)
  2. 基于数据模型的读操作(绕过聚合根和资源库,直接返回数据,推荐)
  3. CQRS(读写操作分别使用不同的数据库,比较重)

(图中的ReadModel可以理解为DTO)


  • 对于业务代码来说,怎么提炼出业务逻辑和技术细节?


  1. 对Use Case进行过程分解
  2. 对象模型建设


举个例子,在上架过程中,有一个校验是检查库存的,其中对于组合品(CombineBackOffer)其库存的处理会和普通品不一样。原来的代码是这么写的:


boolean isCombineProduct = supplierItem.getSign().isCombProductQuote();

// supplier.usc warehouse needn't check
if (WarehouseTypeEnum.isAliWarehouse(supplierItem.getWarehouseType())) {
// quote warehosue check
if (CollectionUtil.isEmpty(supplierItem.getWarehouseIdList()) && !isCombineProduct) {
    throw ExceptionFactory.makeFault(ServiceExceptionCode.SYSTEM_ERROR, "亲,不能发布Offer,请联系仓配运营人员,建立品仓关系!");
}
// inventory amount check
Long sellableAmount = 0L;
if (!isCombineProduct) {
    sellableAmount = normalBiz.acquireSellableAmount(supplierItem.getBackOfferId(), supplierItem.getWarehouseIdList());
} else {
    //组套商品
    OfferModel backOffer = backOfferQueryService.getBackOffer(supplierItem.getBackOfferId());
    if (backOffer != null) {
        sellableAmount = backOffer.getOffer().getTradeModel().getTradeCondition().getAmountOnSale();
    }
}
if (sellableAmount < 1) {
    throw ExceptionFactory.makeFault(ServiceExceptionCode.SYSTEM_ERROR, "亲,实仓库存必须大于0才能发布,请确认已补货.\r[id:" + supplierItem.getId() + "]");
}
}


然而,如果我们在系统中引入领域模型之后,其代码会简化为如下:


if(backOffer.isCloudWarehouse()){
    return;
}

if (backOffer.isNonInWarehouse()){
    throw new BizException("亲,不能发布Offer,请联系仓配运营人员,建立品仓关系!");
}

if (backOffer.getStockAmount() < 1){
    throw new BizException("亲,实仓库存必须大于0才能发布,请确认已补货.\r[id:" + backOffer.getSupplierItem().getCspuCode() + "]");
}


有没有发现,使用模型的表达要清晰易懂很多,而且也不需要做关于组合品的判断了,因为我们在系统中引入了更加贴近现实的对象模型(CombineBackOffer继承BackOffer),通过对象的多态可以消除我们代码中的大部分的if-else


如何正确地理解应用架构并开发(下):https://developer.aliyun.com/article/1443481


目录
相关文章
|
16天前
|
机器学习/深度学习 API 语音技术
|
7天前
|
消息中间件 监控 持续交付
构建高效微服务架构:后端开发的进阶之路
【4月更文挑战第20天】 随着现代软件开发的复杂性日益增加,传统的单体应用已难以满足快速迭代和灵活部署的需求。微服务架构作为一种新兴的分布式系统设计方式,以其独立部署、易于扩展和维护的特点,成为解决这一问题的关键。本文将深入探讨微服务的核心概念、设计原则以及在后端开发实践中如何构建一个高效的微服务架构。我们将从服务划分、通信机制、数据一致性、服务发现与注册等方面入手,提供一系列实用的策略和建议,帮助开发者优化后端系统的性能和可维护性。
|
2天前
|
JSON API 数据库
后端架构设计与优化:打造高性能应用后端
后端架构设计与优化:打造高性能应用后端
13 2
|
3天前
|
持续交付 API 开发者
构建高效微服务架构:后端开发的新范式
【4月更文挑战第24天】 随着现代软件系统的复杂性日益增加,传统的单体应用已难以满足快速迭代与灵活扩展的需求。微服务架构作为一种新兴的软件开发模式,以其服务的细粒度、独立部署和弹性伸缩等优势,正在逐渐成为后端开发的重要趋势。本文将深入探讨微服务架构的设计原则、关键技术以及在实际业务中的应用实践,旨在为后端开发者提供构建和维护高效微服务架构的参考指南。
|
4天前
|
监控 API 持续交付
构建高效微服务架构:后端开发的新趋势
【4月更文挑战第23天】 随着现代软件开发实践的不断演进,微服务架构已经成为企业追求敏捷、可扩展和弹性解决方案的首选。本文深入探讨了如何构建一个高效的微服务架构,涵盖了关键的设计原则、技术选型以及实践建议。通过分析微服务的独立性、分布式特性和容错机制,我们将揭示如何利用容器化、服务网格和API网关等技术手段,来优化后端系统的可维护性和性能。文章旨在为后端开发人员提供一套全面的指南,以应对不断变化的业务需求和技术挑战。
|
9天前
|
监控 持续交付 开发者
构建高效微服务架构:后端开发的新趋势
【4月更文挑战第18天】在数字化转型的浪潮中,微服务架构已成为企业提升系统灵活性、加速产品迭代的关键。此文深入探讨了构建高效微服务架构的实践方法,包括服务划分原则、容器化部署、持续集成/持续部署(CI/CD)流程以及监控与日志管理等关键技术点。通过分析具体案例,揭示了微服务在提高开发效率、降低维护成本及促进团队协作方面的显著优势。
|
11天前
|
人工智能 Serverless 数据处理
利用阿里云函数计算实现 Serverless 架构的应用
阿里云函数计算是事件驱动的Serverless服务,免服务器管理,自动扩展资源。它降低了基础设施成本,提高了开发效率,支持Web应用、数据处理、AI和定时任务等多种场景。通过实例展示了如何用Python实现图片压缩应用,通过OSS触发函数自动执行。阿里云函数计算在云计算时代助力企业实现快速迭代和高效运营。
46 0
|
13天前
|
监控 负载均衡 API
构建高性能微服务架构:后端开发的最佳实践
【4月更文挑战第14天】 在当今快速发展的软件开发领域,微服务架构已成为构建可扩展、灵活且容错的系统的首选方法。本文深入探讨了后端开发人员在设计和维护高性能微服务时需要遵循的一系列最佳实践。我们将从服务划分原则、容器化部署、API网关使用、负载均衡、服务监控与故障恢复等方面展开讨论,并结合实际案例分析如何优化微服务性能及可靠性。通过本文的阅读,读者将获得实施高效微服务架构的实用知识与策略。
|
15天前
|
运维 监控 自动驾驶
构建可扩展的应用程序:Apollo与微服务架构的完美结合
构建可扩展的应用程序:Apollo与微服务架构的完美结合
33 10
|
16天前
|
机器学习/深度学习 PyTorch API