Logo
Section 1 / 6

按层级组织

学习如何按层次组织代码。

导入

按层级组织

学习如何按层次组织代码。

简介

如果仅通过查看代码就能识别出系统的架构,那岂不是很理想?

在本章中,我们将探讨不同的代码组织方式,并介绍一种能够直观体现六边形架构思想的、富有表达力的包结构。

绿地软件项目

在绿地软件项目中,我们最先希望做好的就是包结构。我们会设计一个看起来非常整洁的目录结构,并打算在整个项目周期中沿用它。然而随着开发推进,情况逐渐变得混乱,最终发现这个包结构仅仅是一个漂亮的外表,其下掩盖的是缺乏组织的代码乱象。某些包中的类会随意导入本不该依赖的其他包中的类。

Course Image

代码组织方式的选择

  • 按层组织
  • 按功能特性组织

我们将以之前课程中介绍的 BuckPal 示例应用为例,讨论这两种代码组织方式。具体来说,我们会聚焦于“转账”这一用例,即用户从自己的账户向另一个账户汇款的功能。

项目结构

组织代码的第一种方式是按层划分。我们可以这样安排结构:

1buckpal 2├── domain 3| ├── Account 4| ├── Activity 5| ├── AccountRepository 6| └── AccountService 7├── persistence 8| └── AccountRepositoryImpl 9└── web 10 └── AccountController

解释说明

我们为每一层——web、domain 和 persistence——分别设立了独立的包。正如在章节《分层架构有什么问题?》中所讨论的那样,简单的分层结构由于多种原因可能并不适合我们的代码组织。因此,我们在此已经应用了依赖倒置原则(DIP),只允许依赖关系指向 domain 包中的领域代码。这一点通过在 domain 包中定义 AccountRepository 接口,并在 persistence 包中实现该接口来达成。

然而,这种包结构至少存在三个方面的不足之处:

  1. 缺乏功能模块或特性的包边界。如果我们新增一个用户管理功能,就需要在 web 包中添加 UserController,在 domain 包中添加 UserService、UserRepository 和 User,并在 persistence 包中添加 UserRepositoryImpl。如果没有进一步的结构约束,这些类将迅速堆积在一起,导致不同功能之间产生意外的耦合和副作用。

  2. 无法直观看出系统支持哪些用例。你能一眼看出 AccountService 或 AccountController 实现了哪些用例吗?当我们需要查找某个特定功能时,只能猜测由哪个服务负责,然后进入该服务内部搜索对应的方法。

  3. 无法从包结构中清晰识别目标架构。我们或许可以推测这里采用了六边形架构风格,然后去浏览 web 和 persistence 包中的类,找出对应的 Web 适配器和持久化适配器。但我们无法一目了然地知道:哪些功能是由 Web 适配器调用的?哪些功能是持久化适配器为领域层提供的?进出六边形的端口(inbound/outbound ports)被隐藏在代码之中,难以快速识别。

附录

按功能组织