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

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

整体分析

按照技术分包造成的缺点:

  1. 对属于某个功能的所有类的概述不佳。

  2. 通用代码、重用代码和复杂代码趋向于难以理解,并且由于难以把握变更的影响,因此变更很容易破坏其他功能用例。

按功能分包从而创建包含功能所需的所有类的程序包。好处是:

  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

posted @ 2020-05-05 14:00  东溪陈姓少年  阅读(478)  评论(0编辑  收藏  举报