SpringBoot 项目就连创建目录都让人抓狂
很多同学创建一个项目之后,就迫不及待的上手开写了。项目代码不像一些框架代码一样可以随意的去写,但一般都是采用 MVC 的模式进行开发。很悲催的是,Java 中 Web 开发的这些目录名称,到现在还是一团乱麻,你需要自己去规划。
什么 Controller、Service、Dao 等,但其实这种划分方式弊端很多! 本文将先介绍两种典型的分层结构,然后稍微借鉴一下 DDD 的思想,谈一下我在项目中常用的目录结构。本篇文章非常的实用,将探讨怎样做一个应对大型项目的目录划分。
清晰的目录结构,能够辅助其他同学轻而易举的了解项目的功能模块,在项目中保持整体一致的约定也是一个非常好的习惯。如果再加上一个扩展性,那目录划分就是重中之重。
有两种典型的分类方式,但也有很多细节。
1、最简单的 MVC
我们平常最熟悉的,就是 MVC 结构。这种结构很流行,写简单项目很方便,但是会产生严重的耦合问题、Service 爆炸问题,数千、上万行的代码是家常便饭。
Model(模型)表示应用程序核心(比如数据库记录字段)。
View(视图)显示数据(数据库记录)。
Controller(控制器)处理输入(写入数据库记录)。
在项目划分上,就类似下面的目录结构:
1.1 模型
domain 是 DDD 中一个非常宽泛的概念。不过,我们平常就当作数据库对应的 Java 类使用了(没什么错)。在实际操作中,它还可能有下面几种名字,在普通项目中区别不大,你最好在项目中保持相同的意义来避免歧义。
entity 这个意义比较明显,就是实体的意思,最常用。比如 JPA 的 Entity 注解
model 模型的意思,一般用来在不同系统之间交互。但如果你的模型非常简单,直接用 entity 来表示也是可以的。
domain 这个范围有点大,甚至会包含领域内 service。如果你对 DDD 的概念不是很熟悉,那就玩上面几种
对于简单的项目,我通常在项目中使用 entity 来表示和数据库的交互。在 JPA 之类的 ORM 中,也是做相关处理的。
比如 javax.persistence.Entity 注解。你要明白的是,Spring Data 其实取了一个比较折衷的点,把很多东西揉在一起了。
1.2 Dao
dao 层叫数据访问层,全称为 data access object,属于一种比较底层,比较基础的操作。在一些其他框架中,还会叫别的名字。
mapper 这个一般是 Mybaits 之类的框架所生成的目录,通常是一些接口。
repository 仓库的意思,在 jpa 中经常用。
Dao 应该满足最小封装原则,理论上只涉及一句 SQL 的执行。如果有多个数据的存取动作,需要封装在 Service 中,并用事务进行管理(虽然这么说,但 repository 在 DDD 中,是不和具体的数据库打交道的)。
1.3 service 和 controller
这个没什么好说的,基本上所有重要的逻辑都在这里完成。service 用于逻辑处理,controller 用于接口暴露。
2、根据功能组织
大多数情况下,我们使用上面的这种划分模式,能够很好的完成工作。比如,所有的数据处理,都放在 Dao 层,所有的逻辑处理,都放在 Service 层。
这在小项目中相安无事,但如果项目中,有成百上千个 Entity,这些目录中的文件就会爆炸,以至于最后无法维护。
另外一个问题就是,仅仅一个简单的功能,就可能分散在多个 package 下的多个文件中,大型项目维护变得困难。
我们有另外一个思路,就是根据功能进行分组。比如下面的截图。
我们把相似功能,放在 modules 下的单个文件夹中。如果这个功能模块比较大,我么可以在功能模块下,再进行分层设计。
比如上图,有一个商品服务,我们单独给它分配了一个目录空间 goods,然后在里面又划分了 dao、entity 等目录;但对于 Service 喝 Controller,我们简单的放在了外层,可以看到在模块内的分配是比较灵活的。
这么做的好处是显而易见的。功能变的非常的集中,各个 package 之间的内容互不影响。
3、还是不够优雅
其实,即使我们这样划分了,项目仍然会面临很大的挑战(很多 DDD 的书籍,会大量讨论各层的交互)。
下面分享一个我在平常使用的分层模式,兼顾高内聚和第耦合,有着良好的扩展性。
config,最外层的一些全局配置,比如 web 配置,消息队列配置等
system,全局的工具和依赖功能,在 DDD 中叫做基础设施(但在非 DDD 实践的项目中名称太怪异了)
auth,权限认证模块,比如 JWT 或者 Spring Sercurity,这部分的设计要独立,以便后续抽离到 Zuul 之类的网关
bc,在 DDD 中是限界上下文的意思(Bounded Context),我们也可以直接叫模块,这些模块有着严格的界限,可以根据请求量,拆分成相应的微服务。在上图,crm、images、order 等等,都可以抽离成独立的微服务
我们再来看一下每个模块之内的结构:
和传统的 MVC 类似。不过,为了屏蔽变化,兼顾扩展性,我们增加了更多的内容。
persitence,持久层,具体使用 JPA 还是 Mybatis,这个是无关紧要的。我们的目标,就是尽量的弱化持久层的实现,将变化封装在 Domain 层中
persitence/dao,具体的持久层接口,比如 MyBatis 的 Mapper 文件,或者 JPA 的 Repository
domain 层,具体的业务层,你可以认为是一堆 Getter、Setter 的 Bean。我们尽量会把大多数验证类和变化封装在这里(可以大体认为是 DDD 中的充血模型)
controller,具体的 Rest 接口层。但不同的是,有很多不同的请求和返回,我们封装成了 Request 和 Response,用来接受提交的数据,对返回数据进行瘦身等
application,应对传统的 service 层,除了在 application 能够调用 Dao,其他层是没有权利调用 Dao 的
api,和 application 的功能是相同的。只不过,api 的接口,指的是模块之间可以相互调用的接口。出了 api 暴露的这些接口,bc 之间的类和接口,默认彼此是不可见的
util,不通用的 util,会放在模块内部,而不是抽离出公共的 util
除了要解决目录方面的问题,我们还要把数据的流向给规划清楚。
一个上层的应用,是可以通过 API 接口直接调用下层服务的。比如,订单系统访问商品基础信息的数据;反之却不可以,比如商品基础信息模块访问订单系统的接口。
低层想要对高层的数据产生变化,就只能通过消息模块,将变更发布出去,其他的模块就可以订阅这些变化。
小结
综上所述,xjjdog 认为,如果你的项目,可能会比较大,单纯的使用分层的 package,并不是一个好的习惯。
你可能对这种后台管理类的项目驾轻就熟,有很多有用的模版,它们都是简单的 MVC 分层。这应付一些外包项目,干一些一锤子买卖的时活,或许没什么问题,但一旦是比较大的长期项目,这种分层的目录接口就显现出它的弊端。
这是因为:项目的短期风险,是工期问题;而长期风险,是扩展问题。随着访问量的增加,还有低耦合高内聚的需求增加,如何快速的应对需求,减少 BUG,将会是制约项目发展的最主要因素。
作者:小姐姐味道
链接:https://juejin.cn/post/6922001268164526094
来源:掘金
评论