通常在Spring Boot项目中,包结构如下:
com.company.projectentitymapperservicecontroller
实践发现,随着项目越来越复杂,类越来越多,这种组织方式存在明显问题:同一个数据或业务相关的多个类分散在不同的包(目录)中,而编写、阅读代码却又是一起的,所以,在IDE中需要反复上下滚动,来找到需要的类,麻烦、效率低、不清晰。
在backend项目中,我们以数据或业务为中心组织包结构,包结构如下:
com.company.projectdomain:领域包entity/business:实体/业务包data:数据包mapper:Mapper包service:服务包controller:控制器包scheduler:调度器包
领域包:表示一个业务领域,例如某个部门专门负责的业务。领域下可以再划分子领域。
实体/业务包:表示一个实体(例如项目、合同),或者一项专门的业务(例如对某数据的分析)。这是代码组织的基本粒度单位,包内的所有代码都是围绕某个实体/业务进行编写的,相关的服务、控制器等类全部集中在一起,方便编写和阅读。实体/业务包中提供数据结构(data包)和操作(service包),这是面向对象思想在包层次上的体现。
数据包:定义数据结构。之所以叫“数据包”而不是“实体包”,是因为数据不仅仅是实体类,还有枚举、表示某种共性的接口、常量等。
Mapper包:存放Mapper接口,实现数据库层面的操作。我们约定,凡是数据库层面的操作,只要能在Mapper接口中实现,就一定在Mapper接口中实现,因为这可以使Mapper层和服务层的职责更清晰,简化服务层。可以通过default方法、private方法、Mapper互相调用等手段来实现这一点。
服务包:存放服务类。服务层是实现业务逻辑的核心,是对外提供操作的核心,其他实体/业务只能通过服务层的方法来使用本实体/业务的操作。通过精心设计服务层方法的粒度,可以实现良好的代码复用。
控制器包:存放控制器类,对前端提供API。
调度器包:存放调度器类,实现定时任务。我们约定,定时任务必须通过调用控制器层来执行,而不能直接调用服务层,这是因为我们经常有手动触发定时任务的需求,手动触发会通过控制器层的API来执行,所以为了避免不一致,故做此约定。换句话说,调度器只是提供了一个定时触发的机制而已,无论是定时触发还是手动触发,执行都是从控制器层开始的。