[译]按功能(特性)分包

一种流行的方法是经过技术层面对项目进行分包。可是这种方法有一些缺点。相反,咱们能够按功能分包并建立独立自治的程序包。结果是一个易于理解且不易出错的代码库。java

总体分析

按照技术分包形成的缺点:数据库

  1. 对属于某个功能的全部类的概述不佳。api

  2. 通用代码、重用代码和复杂代码趋向于难以理解,而且因为难以把握变动的影响,所以变动很容易破坏其余功能用例。架构

按功能分包从而建立包含功能所需的全部类的程序包。好处是:dom

  1. 更好的可发现性和概览
  2. 独立且自治
  3. 更简单的代码
  4. 可测试性
  5. 便于团队协做开发

按照技术分层分包

项目结构的一种很是流行的方法是逐层分包。这将为每一个技术组所属类提供一个软件包。测试

⚠️:按层分包从技术角度对全部类进行分组编码

让咱们将调用层次结构添加到图片中,以“清楚地”了解哪一个类取决于其余哪一个类。线程

⚠️:调用层次结构遍布整个项目,涉及许多包翻译

那么,按层分包的缺点是什么?日志

  1. 功能概述不佳。一般,当咱们在项目中处理代码时,咱们首先会想到要更改的特定领域或功能。所以,咱们会从领域的角度出发。不幸的是,按技术分层分包迫使咱们从一种软件包过渡到另外一种软件包,才能掌握功能的概况。

  2. 通用,重用和复杂代码的趋势。一般,这种方法致使中心类包含每一个功能用例的全部方法。随着时间的流逝,这些方法愈来愈抽象化(带有额外的参数和泛型)来知足更多用例。上图中仅一个示例是ProductDAO,其中放置了ProductController和ExportController的方法。结果是:

    • 当添加更多方法时,类将变得更大。所以,仅凭代码量,就很难理解它。

    • 更改通用重用代码很危险。尽管您只想处理一个用例,但您能够轻松地打破全部用例。

    • 因为如下两个缘由,难以理解抽象方法和通用方法:首先,要通用,一般须要其余技术构造(例如,switch,参数,泛型),这使得查看与当前用例相关的业务逻辑更加困难。其次,认知需求更高,由于您必须了解全部其余用例,以确保您不会破坏它们。

桑迪·梅斯(Sandi Metz)指出:

“我以为我必须了解全部内容才能提供帮助。”桑迪·梅斯(Sandi Metz)。请参阅个人帖子,了解咱们的编码智慧墙。

⚠️:咱们达到了DRY,但违反了KISS。


按功能(特性)分包

让咱们将这些类从新排列成独立的功能包。

👆用户管理功能包

新的包userManagement包含属于此功能的全部类:控制器,DAO,DTO和实体。

👆产品管理功能包

新软件包productManagement包含相同的类类型,以及StockServiceClient和相应的StockDTO。这个事实清楚地代表:库存服务仅由产品管理人员使用。

userManagement和productManagement使用不一样的域实体和表。将它们分红不一样的包很简单。可是,当一个功能须要与另外一个功能类似或甚至相同的域实体时,会发生什么?

👆产品出口的功能包

如今,它变得愈来愈有趣。exportProduct包也处理产品实体,但具备不一样的功能用例。

咱们的目标是拥有独立自治的功能包。所以,exportProduct应该具备本身的DAO,DTO类和实体类,即便它们看起来与productManagement中的类类似。抵制重用productManagement中的类的冲动。

  • 咱们可使用针对出口用例量身定制的结构(DTO,实体)。它们仅包含相关字段,而且能够基于具备相关列的良好投影的查询来建立实体-别无其余。
  • 专用的ExportProductDAO包含特定于出口功能的查询和预测。

咱们可能不得再也不次编写更多代码,但最终会遇到很是有利的状况:

  • productManagement中的更改永远不会破坏exportProduct代码,反之亦然。它们能够独立发展。
  • 更改代码时,咱们仅需牢记当前功能。
  • 代码自己将变得更加简单易懂,由于它不是通用的,而且没必要在两个用例中均可以使用。

上面的功能包很棒,但实际上,咱们将始终须要一个通用的包。

👆通用软件包包含技术配置和可重复使用的代码

它包含技术配置类(例如用于DI,Spring,对象映射,http客户端,数据库链接,链接池,日志记录,线程池)

它包含可重用的有用代码片断。可是要很是当心代码的过早抽象。我老是先把代码放到尽量接近它的用法的地方,也就是特性包,甚至是使用类。仅当片断确实有更多用途(⚠️:而不是我认为未来可能会使用)时,才将其移动到通用包中。三定律提供了很好的指导。

在通用包中找到全部实体多是有意义的。咱们还对某些项目执行了此操做,其中许多功能包一次又一次地使用相同的实体。一些开发人员还但愿将全部实体放在中心位置,以便可以总体查看数据库架构的映射。目前,我并非教条,由于实体的两个位置均可以合理。不过,一开始我老是尽量多地将代码转移到功能包中,并依赖于定制的特定于用例的实体和投影。


大图景

最终,咱们的大图看起来像这样:

👆按功能分包的大图

好处

让咱们简要总结一下好处:

  1. 从域的角度来看,更好的可发现性和概述。属于业务功能的大多数代码位于一块儿。这很关键,由于咱们一般会在考虑某个业务需求的状况下访问代码库。
  2. 独立的和自治的。功能所需的大多数代码都位于一个程序包中。所以,咱们避免依赖其余功能包。结果是:在开发功能时,咱们不太可能破坏其余功能。须要较少的认知能力来估计变化的影响。一般,咱们只须要记住当前的软件包便可。
  3. 更简单的代码。因为咱们避免使用通用和抽象的代码,所以代码变得更加简单,由于它只须要处理一个用例。所以,更容易理解和改进代码。
  4. 可测试性。一般,与试图知足全部用例的技术包中的“上帝类”相比,功能包中的类具备较少的依赖关系。所以,因为咱们能够建立更少的测试依赖,所以测试变得更加容易。

缺点

  1. 咱们必须编写更多代码。
  2. 咱们可能会屡次编写相似的代码。
  3. 决定什么时候才能更好地将代码移至通用软件包并重用它是很难的。有疑问时,“三定律”颇有用。我想强调指出,重用仍然是容许且有用的。
  4. 找出功能包的适当范围和大小也很棘手。有关详细信息,请参阅问题部分。

可是,我认为优势大于缺点。


背后的原理

拟议的按功能分包方法遵循的原则很是贴切:

KISS > DRY

再次,我想引用桑迪·梅斯(Sandi Metz)

“我以为我必须了解全部内容才能提供帮助。”桑迪·梅斯(Sandi Metz)。请参阅个人帖子,了解咱们的编码智慧墙。


按功能包装的方法

咱们的团队记录了其遵循的编码准则和原则。关于按功能分包的部分以下所示:

咱们基于功能分包。每一个功能包均包含提供该功能所需的大多数代码。每一个功能包都应独立且自治。

├── feature1
│   ├── Feature1Controller
│   ├── Feature1DAO
│   ├── Feature1Client
│   ├── Feature1DTOs.kt
│   ├── Feature1Entities.kt
│   └── Feature1Configuration
├── feature2
├── feature3
└── common
  1. 这种方法影响全部层。例如,每一个程序包都有本身的DAO和客户端。不该有庞大的DAO类神。
  2. 一个程序包应该与其余程序包只有几个关系。该功能所需的全部逻辑事物都应放在程序包内。
  3. 经验法则:若是要删除功能,则只需删除相应的程序包。
  4. 尽管如此,也能够在通用软件包中重复使用东西,但它只应包含屡次使用的代码(请参阅三定律)。它不该该包含业务逻辑。可是技术上有用是能够的。
  5. 若是存在特定于特性的Spring Bean,咱们将把它们的配置放在特性包中。

问题

功能包中的结构如何?

这取决于项目和功能包的大小。

对于中小型项目,我喜欢避免定义可能会增长更多仪式而非价值的规则(例如,要求定义某些接口和子包)。只要您构建独立的、自治的、从您的特定业务领域派生的包,您就在正确的轨道上。

若是要处理更大的代码库,则可能须要定义有关子包结构和方式的更多规则,则容许一个功能包访问另外一个功能包。“模块”或“组件”而不是“功能包”的概念可能更有帮助。例如,Tom Hombergs建议在每一个组件包中添加api和内部包,这些组件包定义组件的哪些部分容许其余组件使用。有关详细信息,请参阅他的文章“使用Spring Boot和ArchUnit清理架构边界”。

我最终会一次又一次写相同的代码吗?

是的,会有一些重复,可是根据个人经验,您可能不会相信那么多100%相同的代码。因为类似的代码涵盖了不一样的用例,所以一般是不一样的。例如,两种方法能够按产品名称查询产品,可是它们在计划的字段,排序和其余条件方面有所不一样。所以,最好将方法分开放在不一样的程序包中。

并且,复制自己并非邪恶的。在开始将代码提取到通用重用方法以前,我喜欢应用三定律

最后,我想强调指出,仍然容许集中使用可重用的代码,有时甚至是合理的,可是这些状况再也不那么常见了。

Kotlin能够支持这种方法吗?

分包方法与语言无关。可是Kotlin使其易于遵循:

使用数据类,编写量身定制的特定于功能的结构(如DTO或实体)仅需几行,而无需样板。

Kotlin容许将多个类放在一个文件中。所以,咱们可使一个包含全部数据类定义的DTOs.kt或Entities.kt文件成为一个单独的DTOs.kt或Entities.kt文件,而不是有一个子包DTO或包含每一个POJO类的许多Java文件的实体。

本文翻译自:https://phauer.com/2020/package-by-feature/


关注笔者公众号,推送各种原创/优质技术文章 ⬇️

WechatIMG6

相关文章
相关标签/搜索