项目结构划分
较好的组织方式:按依赖划分
在抛弃了上面三种不理想的方式后,我们只得在搜索引擎中寻找更好的答案。并不困难的,我们找到了这篇 技术文章,其中介绍了一个非常不错的组织方式。巧的是文章作者也经历了和我们一样的困惑和尝试,才最终形成了他文章中的结论。为了方便无法访问原链接的同学们进行理解,截取原文中的一些代码来简要介绍下。
首先,根 package 需要定义整个项目的 domain,并且不依赖项目中的任何其他 package:

接下来,按照外部依赖对实现代码进行包的划分。例如如果 UserService 是依赖 PostgreSQL 作为存储实现的,那么可以用一个 postgres 的子 package 来包含实现代码:

从 MVC 的角度看,这里的 UserService 属于 model 层次。在其上还有对接 HTTP API 的 view-controller 层。可以想象 view-controller 层需要依赖并利用 UserService,这里不再展开代码。这里的关键是,包的命名不再是 models 或这 controllers,而是按照外部依赖而命名。那么当包外引用 UserService 的时候,它的形式会是 postgres.UserService,非常简洁易理解。
此外,需要看到的是,这种组织方式并不只是简单的给包换了个合适的名字,它还抽象出来了 domain。这一层抽象带来了一定的灵活性。比如想象一下,如果此时需要迁移到 MongoDB 作为数据存储,那么新的基于 MongoDB 的 UserService 实现,对于上层的 view-controller 来说是透明的。因为不管是基于什么实现的 UserService,只要它符合 UserService 的接口,那么对它的使用者都是可以无缝替换的。更进一步的,这一层抽象也给我们的测试带来了很多的便利,这方面我们会在后面关于测试的部分进行更多展开。
可以说这种方式解决了前三种方式中各自的问题,是一个非常不错的思路。在我们一些项目的早期,直接采纳了这种组织方式。不过,随着越来越多的业务逻辑加入到 service 的实现中,我们发现有必要在这基础上进行一些改进。