当前位置:   article > 正文

五个分层维度:SpringBoot工程分层实战_springboot项目分层

springboot项目分层

1 分层思想

计算机领域有一句话:计算机中任何问题都可通过增加一个虚拟层解决。这句体现了分层思想重要性,分层思想同样适用于Java工程架构。

分层优点是每层只专注本层工作,可以类比设计模式单一职责原则,或者经济学比较优势原理,每层只做本层最擅长的事情。

分层缺点是层之间通信时,需要通过适配器,翻译成本层或者下层可以理解的信息,通信成本有所增加。

我认为工程分层需要从五个维度思考:

(1) 单一

每层只处理一类事情,满足单一职责原则

(2) 降噪

信息在每一层进行传输,满足最小知识原则,只向下层传输必要信息

(3) 适配

每层都需要一个适配器,翻译信息为本层或者下层可以理解的信息

(4) 业务

业务对象可以整合业务逻辑,例如使用充血模型整合业务

(5) 数据

数据对象尽量纯净,尽量不要聚合业务

1.2 九层结构

综上所述SpringBoot工程可以分为九层:

  • 工具层:util
  • 整合层:integration
  • 基础层:infrastructure
  • 服务层:service
  • 领域层:domain
  • 门面层:facade
  • 控制层:controller
  • 客户端:client
  • 启动层:boot


 

2 分层详解

创建测试项目user-demo-service:

  1. user-demo-service
  2. -user-demo-service-boot
  3. -user-demo-service-client
  4. -user-demo-service-controller
  5. -user-demo-service-domain
  6. -user-demo-service-facade
  7. -user-demo-service-infrastructure
  8. -user-demo-service-integration
  9. -user-demo-service-service
  10. -user-demo-service-util
  11. 复制代码

2.1 util

工具层承载工具代码

不依赖本项目其它模块

只依赖一些通用工具包

  1. user-demo-service-util
  2. -/src/main/java
  3. -date
  4. -DateUtil.java
  5. -json
  6. -JSONUtil.java
  7. -validate
  8. -BizValidator.java
  9. 复制代码

2.2 infrastructure

基础层核心是承载数据访问,entity实体对象承载在本层。

2.2.1 项目结构

代码层分为两个领域:

  • player:运动员
  • game:比赛

每个领域具有两个子包:

  • entity
  • mapper
  1. user-demo-service-infrastructure
  2. -/src/main/java
  3. -player
  4. -entity
  5. -PlayerEntity.java
  6. -mapper
  7. -PlayerEntityMapper.java
  8. -game
  9. -entity
  10. -GameEntity.java
  11. -mapper
  12. -GameEntityMapper.java
  13. -/src/main/resources
  14. -mybatis
  15. -sqlmappers
  16. -gameEntityMappler.xml
  17. -playerEntityMapper.xml
  18. 复制代码

2.2.2 本项目间依赖关系

infrastructure只依赖工具模块

  1. <dependency>
  2. <groupId>com.test.javafront</groupId>
  3. <artifactId>user-demo-service-util</artifactId>
  4. </dependency>
  5. 复制代码

2.2.3 核心代码

创建运动员数据表:

  1. CREATE TABLE `player` (
  2. `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  3. `player_id` varchar(256) NOT NULL COMMENT '运动员编号',
  4. `player_name` varchar(256) NOT NULL COMMENT '运动员名称',
  5. `height` int(11) NOT NULL COMMENT '身高',
  6. `weight` int(11) NOT NULL COMMENT '体重',
  7. `game_performance` text COMMENT '最近一场比赛表现',
  8. `creator` varchar(256) NOT NULL COMMENT '创建人',
  9. `updator` varchar(256) NOT NULL COMMENT '修改人',
  10. `create_time` datetime NOT NULL COMMENT '创建时间',
  11. `update_time` datetime NOT NULL COMMENT '修改时间',
  12. PRIMARY KEY (`id`)
  13. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
  14. 复制代码

运动员实体对象,gamePerformance字段作为string保存在数据库,这体现了数据层尽量纯净,不要整合过多业务,解析任务应该放在业务层:

  1. public class PlayerEntity {
  2. private Long id;
  3. private String playerId;
  4. private String playerName;
  5. private Integer height;
  6. private Integer weight;
  7. private String creator;
  8. private String updator;
  9. private Date createTime;
  10. private Date updateTime;
  11. private String gamePerformance;
  12. }
  13. 复制代码

运动员Mapper对象:

  1. @Repository
  2. public interface PlayerEntityMapper {
  3. int insert(PlayerEntity record);
  4. int updateById(PlayerEntity record);
  5. PlayerEntity selectById(@Param("playerId") String playerId);
  6. }
  7. 复制代码

2.3 domain

2.3.1 概念说明

领域层是DDD流行兴起之概念

可以通过三组对比理解领域层

  • 领域对象 VS 数据对象
  • 领域对象 VS 业务对象
  • 领域层 VS 业务层

(1) 领域对象 VS 数据对象

数据对象字段尽量纯净,使用基本类型

  1. public class PlayerEntity {
  2. private Long id;
  3. private String playerId;
  4. private String playerName;
  5. private Integer height;
  6. private Integer weight;
  7. private String creator;
  8. private String updator;
  9. private Date createTime;
  10. private Date updateTime;
  11. private String gamePerformance;
  12. }
  13. 复制代码

以查询结果领域对象为例

领域对象需要体现业务含义

  1. public class PlayerQueryResultDomain {
  2. private String playerId;
  3. private String playerName;
  4. private Integer height;
  5. private Integer weight;
  6. private GamePerformanceVO gamePerformance;
  7. }
  8. public class GamePerformanceVO {
  9. // 跑动距离
  10. private Double runDistance;
  11. // 传球成功率
  12. private Double passSuccess;
  13. // 进球数
  14. private Integer scoreNum;
  15. }
  16. 复制代码

(2) 领域对象 VS 业务对象

业务对象同样会体现业务,领域对象和业务对象有什么不同呢?其中一个最大不同是领域对象采用充血模型聚合业务。

运动员新增业务对象:

  1. public class PlayerCreateBO {
  2. private String playerName;
  3. private Integer height;
  4. private Integer weight;
  5. private GamePerformanceVO gamePerformance;
  6. private MaintainCreateVO maintainInfo;
  7. }
  8. 复制代码

运动员新增领域对象:

  1. public class PlayerCreateDomain implements BizValidator {
  2. private String playerName;
  3. private Integer height;
  4. private Integer weight;
  5. private GamePerformanceVO gamePerformance;
  6. private MaintainCreateVO maintainInfo;
  7. @Override
  8. public void validate() {
  9. if (StringUtils.isEmpty(playerName)) {
  10. throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
  11. }
  12. if (null == height) {
  13. throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
  14. }
  15. if (height > 300) {
  16. throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
  17. }
  18. if (null == weight) {
  19. throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
  20. }
  21. if (null != gamePerformance) {
  22. gamePerformance.validate();
  23. }
  24. if (null == maintainInfo) {
  25. throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
  26. }
  27. maintainInfo.validate();
  28. }
  29. }
  30. 复制代码

(3) 领域层 VS 业务层

领域层和业务层都包含业务,二者不是替代关系,而是互补关系。业务层可以更加灵活组合不同领域业务,并且可以增加流控、监控、日志、权限,分布式锁等控制,相较于领域层功能更为丰富。

2.3.2 项目结构

代码层分为两个领域:

  • player:运动员
  • game:比赛

每个领域具有三个子包:

  • domain:领域对象
  • event:领域事件
  • vo:值对象
  1. user-demo-service-domain
  2. -/src/main/java
  3. -base
  4. -domain
  5. -BaseDomain.java
  6. -event
  7. -BaseEvent.java
  8. -vo
  9. -BaseVO.java
  10. -MaintainCreateVO.java
  11. -MaintainUpdateVO.java
  12. -player
  13. -domain
  14. -PlayerCreateDomain.java
  15. -PlayerUpdateDomain.java
  16. -PlayerQueryResultDomain.java
  17. -event
  18. -PlayerUpdateEvent.java
  19. -vo
  20. -GamePerformanceVO.java
  21. -game
  22. -domain
  23. -GameCreateDomain.java
  24. -GameUpdateDomain.java
  25. -GameQueryResultDomain.java
  26. -event
  27. -GameUpdateEvent.java
  28. -vo
  29. -GameSubstitutionVO.java
  30. 复制代码

2.3.3 本项目间依赖关系

domain依赖本项目两个模块:

  • util
  • client

之所以依赖client模块是因为领域对象聚合了业务校验,以下信息需要暴露至外部:

  • BizException
  • ErrorCodeBizEnum
  1. <dependency>
  2. <groupId>com.test.javafront</groupId>
  3. <artifactId>user-demo-service-util</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>com.test.javafront</groupId>
  7. <artifactId>user-demo-service-client</artifactId>
  8. </dependency>
  9. 复制代码

2.3.4 核心代码

以运动员修改领域对象为例:

  1. // 运动员修改领域对象
  2. public class PlayerUpdateDomain extends BaseDomain implements BizValidator {
  3. private String playerId;
  4. private String playerName;
  5. private Integer height;
  6. private Integer weight;
  7. private String updator;
  8. private Date updatetime;
  9. private GamePerformanceVO gamePerformance;
  10. private MaintainUpdateVO maintainInfo;
  11. @Override
  12. public void validate() {
  13. if (StringUtils.isEmpty(playerId)) {
  14. throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
  15. }
  16. if (StringUtils.isEmpty(playerName)) {
  17. throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
  18. }
  19. if (null == height) {
  20. throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
  21. }
  22. if (height > 300) {
  23. throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
  24. }
  25. if (null == weight) {
  26. throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
  27. }
  28. if (null != gamePerformance) {
  29. gamePerformance.validate();
  30. }
  31. if (null == maintainInfo) {
  32. throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
  33. }
  34. maintainInfo.validate();
  35. }
  36. }
  37. // 比赛表现值对象
  38. public class GamePerformanceVO implements BizValidator {
  39. // 跑动距离
  40. private Double runDistance;
  41. // 传球成功率
  42. private Double passSuccess;
  43. // 进球数
  44. private Integer scoreNum;
  45. @Override
  46. public void validate() {
  47. if (null == runDistance) {
  48. throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
  49. }
  50. if (null == passSuccess) {
  51. throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
  52. }
  53. if (Double.compare(passSuccess, 100) > 0) {
  54. throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
  55. }
  56. if (null == runDistance) {
  57. throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
  58. }
  59. if (null == scoreNum) {
  60. throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
  61. }
  62. }
  63. }
  64. // 修改人值对象
  65. public class MaintainUpdateVO implements BizValidator {
  66. // 修改人
  67. private String updator;
  68. // 修改时间
  69. private Date updateTime;
  70. @Override
  71. public void validate() {
  72. if (null == updator) {
  73. throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
  74. }
  75. if (null == updateTime) {
  76. throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
  77. }
  78. }
  79. }
  80. 复制代码

2.4 service

2.4.1 项目结构

  1. user-demo-service-service
  2. -/src/main/java
  3. -player
  4. -adapter
  5. -PlayerServiceAdapter.java
  6. -event
  7. -PlayerMessageSender.java
  8. -service
  9. -PlayerService.java
  10. -game
  11. -adapter
  12. -GameServiceAdapter.java
  13. -event
  14. -GameMessageSender.java
  15. -service
  16. -GameService.java
  17. 复制代码

2.4.2 本项目间依赖关系

service依赖本项目四个模块:

  • util
  • domain
  • integration
  • infrastructure
  1. <dependency>
  2. <groupId>com.test.javafront</groupId>
  3. <artifactId>user-demo-service-domain</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>com.test.javafront</groupId>
  7. <artifactId>user-demo-service-infrastructure</artifactId>
  8. </dependency>
  9. <dependency>
  10. <groupId>com.test.javafront</groupId>
  11. <artifactId>user-demo-service-util</artifactId>
  12. </dependency>
  13. <dependency>
  14. <groupId>com.test.javafront</groupId>
  15. <artifactId>user-demo-service-integration</artifactId>
  16. </dependency>
  17. 复制代码

2.4.3 核心代码

以运动员编辑服务为例:

  1. // 运动员服务
  2. public class PlayerService {
  3. @Resource
  4. private PlayerEntityMapper playerEntityMapper;
  5. @Resource
  6. private PlayerMessageSender playerMessageSender;
  7. @Resource
  8. private PlayerServiceAdapter playerServiceAdapter;
  9. public boolean updatePlayer(PlayerUpdateDomain player) {
  10. AssertUtil.notNull(player, new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT));
  11. player.validate();
  12. PlayerEntity entity = playerServiceAdapter.convertUpdate(player);
  13. playerEntityMapper.updateById(entity);
  14. playerMessageSender.sendPlayerUpdatemessage(player);
  15. return true;
  16. }
  17. }
  18. // 运动员消息服务
  19. public class PlayerMessageSender {
  20. @Resource
  21. private PlayerServiceAdapter playerServiceAdapter;
  22. public boolean sendPlayerUpdatemessage(PlayerUpdateDomain domain) {
  23. PlayerUpdateEvent event = playerServiceAdapter.convertUpdateEvent(domain);
  24. log.info("sendPlayerUpdatemessage event={}", event);
  25. return true;
  26. }
  27. }
  28. // 服务适配器
  29. public class PlayerServiceAdapter {
  30. // domain -> entity
  31. public PlayerEntity convertUpdate(PlayerUpdateDomain domain) {
  32. PlayerEntity player = new PlayerEntity();
  33. player.setPlayerId(domain.getPlayerId());
  34. player.setPlayerName(domain.getPlayerName());
  35. player.setWeight(domain.getWeight());
  36. player.setHeight(domain.getHeight());
  37. if (null != domain.getGamePerformance()) {
  38. player.setGamePerformance(JacksonUtil.bean2Json(domain.getGamePerformance()));
  39. }
  40. String updator = domain.getMaintainInfo().getUpdator();
  41. Date updateTime = domain.getMaintainInfo().getUpdateTime();
  42. player.setUpdator(updator);
  43. player.setUpdateTime(updateTime);
  44. return player;
  45. }
  46. // domain -> event
  47. public PlayerUpdateEvent convertUpdateEvent(PlayerUpdateDomain domain) {
  48. PlayerUpdateEvent event = new PlayerUpdateEvent();
  49. event.setPlayerUpdateDomain(domain);
  50. event.setMessageId(UUID.randomUUID().toString());
  51. event.setMessageId(PlayerMessageType.UPDATE.getMsg());
  52. return event;
  53. }
  54. }
  55. 复制代码

2.5 intergration

本项目可能会依赖外部服务,那么将外部DTO转换为本项目可以理解的对象,需要在本层处理。

2.5.1 项目结构

假设本项目调用了用户中心服务:

  1. user-demo-service-intergration
  2. -/src/main/java
  3. -user
  4. -adapter
  5. -UserClientAdapter.java
  6. -proxy
  7. -UserClientProxy.java
  8. 复制代码

2.5.2 本项目间依赖关系

intergration依赖本项目两个模块:

  • util
  • domain

之所以依赖domain模块,是因为本层需要将外部DTO转换为本项目可以理解的对象,这些对象就放在domain模块。

  1. <dependency>
  2. <groupId>com.test.javafront</groupId>
  3. <artifactId>user-demo-service-domain</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>com.test.javafront</groupId>
  7. <artifactId>user-demo-service-util</artifactId>
  8. </dependency>
  9. 复制代码

2.5.3 核心代码

现在我们将外部对象UserClientDTO

转换为本项目领域对象UserInfoDomain

(1) 外部服务

  1. // 外部对象
  2. public class UserInfoClientDTO implements Serializable {
  3. private String id;
  4. private String name;
  5. private Date createTime;
  6. private Date updateTime;
  7. private String mobile;
  8. private String cityCode;
  9. private String addressDetail;
  10. }
  11. // 外部服务
  12. public class UserClientService {
  13. // RPC模拟
  14. public UserInfoClientDTO getUserInfo(String userId) {
  15. UserInfoClientDTO userInfo = new UserInfoClientDTO();
  16. userInfo.setId(userId);
  17. userInfo.setName(userId);
  18. userInfo.setCreateTime(DateUtil.now());
  19. userInfo.setUpdateTime(DateUtil.now());
  20. userInfo.setMobile("test-mobile");
  21. userInfo.setCityCode("test-city-code");
  22. userInfo.setAddressDetail("test-address-detail");
  23. return userInfo;
  24. }
  25. }
  26. 复制代码

(2) 本项目领域对象

domain模块新增user领域:

  1. user-demo-service-domain
  2. -/src/main/java
  3. -user
  4. -domain
  5. -UserDomain.java
  6. -vo
  7. -UserAddressVO.java
  8. -UserContactVO.java
  9. 复制代码

user领域对象代码:

  1. // 用户领域
  2. public class UserInfoDomain extends BaseDomain {
  3. private UserContactVO contactInfo;
  4. private UserAddressVO addressInfo;
  5. }
  6. // 地址值对象
  7. public class UserAddressVO extends BaseVO {
  8. private String cityCode;
  9. private String addressDetail;
  10. }
  11. // 联系方式值对象
  12. public class UserContactVO extends BaseVO {
  13. private String mobile;
  14. }
  15. 复制代码

(3) 适配器

  1. public class UserClientAdapter {
  2. // third dto -> domain
  3. public UserInfoDomain convertUserDomain(UserInfoClientDTO userInfo) {
  4. UserInfoDomain userDomain = new UserInfoDomain();
  5. UserContactVO contactVO = new UserContactVO();
  6. contactVO.setMobile(userInfo.getMobile());
  7. userDomain.setContactInfo(contactVO);
  8. UserAddressVO addressVO = new UserAddressVO();
  9. addressVO.setCityCode(userInfo.getCityCode());
  10. addressVO.setAddressDetail(userInfo.getAddressDetail());
  11. userDomain.setAddressInfo(addressVO);
  12. return userDomain;
  13. }
  14. }
  15. 复制代码

(4) 调用外部服务

  1. public class UserClientProxy {
  2. @Resource
  3. private UserClientService userClientService;
  4. @Resource
  5. private UserClientAdapter userClientAdapter;
  6. public UserInfoDomain getUserInfo(String userId) {
  7. UserInfoClientDTO user = userClientService.getUserInfo(userId);
  8. UserInfoDomain result = userClientAdapter.convertUserDomain(user);
  9. return result;
  10. }
  11. }
  12. 复制代码

2.6 facade + client

设计模式中有一种Facade模式,称为门面模式或者外观模式。这种模式提供一个简洁对外语义,屏蔽内部系统复杂性。

client承载数据对外传输对象DTO,facade承载对外服务,这两层必须满足最小知识原则,无关信息不必对外透出。

这样做有两个优点:

  • 简洁性:对外服务语义明确简洁
  • 安全性:敏感字段不能对外透出

2.6.1 项目结构

(1) client

  1. user-demo-service-client
  2. -/src/main/java
  3. -base
  4. -dto
  5. -BaseDTO.java
  6. -error
  7. -BizException.java
  8. -BizErrorCode.java
  9. -event
  10. -BaseEventDTO.java
  11. -result
  12. -ResultDTO.java
  13. -player
  14. -dto
  15. -PlayerCreateDTO.java
  16. -PlayerQueryResultDTO.java
  17. -PlayerUpdateDTO.java
  18. -enums
  19. -PlayerMessageTypeEnum.java
  20. -event
  21. -PlayerUpdateEventDTO.java
  22. -service
  23. -PlayerClientService.java
  24. 复制代码

(2) facade

  1. user-demo-service-facade
  2. -/src/main/java
  3. -player
  4. -adapter
  5. -PlayerFacadeAdapter.java
  6. -impl
  7. -PlayerClientServiceImpl.java
  8. -game
  9. -adapter
  10. -GameFacadeAdapter.java
  11. -impl
  12. -GameClientServiceImpl.java
  13. 复制代码

2.6.2 本项目间依赖关系

client不依赖本项目其它模块,这一点非常重要,因为client会被外部引用,必须保证这一层简洁和安全。

facade依赖本项目三个模块:

  • domain
  • client
  • service
  1. <dependency>
  2. <groupId>com.test.javafront</groupId>
  3. <artifactId>user-demo-service-domain</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>com.test.javafront</groupId>
  7. <artifactId>user-demo-service-client</artifactId>
  8. </dependency>
  9. <dependency>
  10. <groupId>com.test.javafront</groupId>
  11. <artifactId>user-demo-service-service</artifactId>
  12. </dependency>
  13. 复制代码

2.6.3 核心代码

(1) DTO

以查询运动员信息为例,查询结果DTO只封装最关键字段,例如运动员ID、创建时间、修改时间等业务不强字段就无须透出:

  1. public class PlayerQueryResultDTO implements Serializable {
  2. private String playerName;
  3. private Integer height;
  4. private Integer weight;
  5. private GamePerformanceDTO gamePerformanceDTO;
  6. }
  7. 复制代码

(2) 客户端服务

  1. public interface PlayerClientService {
  2. public ResultDTO<PlayerQueryResultDTO> queryById(String playerId);
  3. }
  4. 复制代码

(3) 适配器

  1. public class PlayerFacadeAdapter {
  2. // domain -> dto
  3. public PlayerQueryResultDTO convertQuery(PlayerQueryResultDomain domain) {
  4. if (null == domain) {
  5. return null;
  6. }
  7. PlayerQueryResultDTO result = new PlayerQueryResultDTO();
  8. result.setPlayerId(domain.getPlayerId());
  9. result.setPlayerName(domain.getPlayerName());
  10. result.setHeight(domain.getHeight());
  11. result.setWeight(domain.getWeight());
  12. if (null != domain.getGamePerformance()) {
  13. GamePerformanceDTO performance = convertGamePerformance(domain.getGamePerformance());
  14. result.setGamePerformanceDTO(performance);
  15. }
  16. return result;
  17. }
  18. }
  19. 复制代码

(4) 服务实现

  1. public class PlayerClientServiceImpl implements PlayerClientService {
  2. @Resource
  3. private PlayerService playerService;
  4. @Resource
  5. private PlayerFacadeAdapter playerFacadeAdapter;
  6. @Override
  7. public ResultDTO<PlayerQueryResultDTO> queryById(String playerId) {
  8. PlayerQueryResultDomain resultDomain = playerService.queryPlayerById(playerId);
  9. if (null == resultDomain) {
  10. return ResultCommonDTO.success();
  11. }
  12. PlayerQueryResultDTO result = playerFacadeAdapter.convertQuery(resultDomain);
  13. return ResultCommonDTO.success(result);
  14. }
  15. }
  16. 复制代码

2.7 controller

facade服务实现可以作为RPC提供服务,controller则作为本项目HTTP接口提供服务,供前端调用。

controller需要注意HTTP相关特性,敏感信息例如登陆用户ID不能依赖前端传递,登陆后前端会在请求头带一个登陆用户信息,服务端需要从请求头中获取并解析。

2.7.1 项目结构

  1. user-demo-service-controller
  2. -/src/main/java
  3. -config
  4. -CharsetConfig.java
  5. -controller
  6. -player
  7. -PlayerController.java
  8. -game
  9. -GameController.java
  10. 复制代码

2.7.2 本项目依赖关系

controller依赖本项目一个模块:

  • facade

根据依赖传递原理同时依赖以下模块:

  • domain
  • client
  • service
  1. <dependency>
  2. <groupId>com.test.javafront</groupId>
  3. <artifactId>user-demo-service-facade</artifactId>
  4. </dependency>
  5. 复制代码

2.7.3 核心代码

  1. @RestController
  2. @RequestMapping("/player")
  3. public class PlayerController {
  4. @Resource
  5. private PlayerClientService playerClientService;
  6. @PostMapping("/add")
  7. public ResultDTO<Boolean> add(@RequestHeader("test-login-info") String loginUserId, @RequestBody PlayerCreateDTO dto) {
  8. dto.setCreator(loginUserId);
  9. ResultCommonDTO<Boolean> resultDTO = playerClientService.addPlayer(dto);
  10. return resultDTO;
  11. }
  12. @PostMapping("/update")
  13. public ResultDTO<Boolean> update(@RequestHeader("test-login-info") String loginUserId, @RequestBody PlayerUpdateDTO dto) {
  14. dto.setUpdator(loginUserId);
  15. ResultCommonDTO<Boolean> resultDTO = playerClientService.updatePlayer(dto);
  16. return resultDTO;
  17. }
  18. @GetMapping("/{playerId}/query")
  19. public ResultDTO<PlayerQueryResultDTO> queryById(@RequestHeader("test-login-info") String loginUserId, @PathVariable("playerId") String playerId) {
  20. ResultCommonDTO<PlayerQueryResultDTO> resultDTO = playerClientService.queryById(playerId);
  21. return resultDTO;
  22. }
  23. }
  24. 复制代码

2.8 boot

boot作为启动层,只有启动入口代码

2.8.1 项目结构

所有模块代码均必须属于com.user.demo.service子路径

  1. user-demo-service-boot
  2. -/src/main/java
  3. -com.user.demo.service
  4. -MainApplication.java
  5. 复制代码

2.8.2 本项目间依赖

boot引用本项目所有模块

  • util
  • integration
  • infrastructure
  • service
  • domain
  • facade
  • controller
  • client

2.8.3 核心代码

  1. @MapperScan("com.user.demo.service.infrastructure.*.mapper")
  2. @SpringBootApplication
  3. public class MainApplication {
  4. public static void main(final String[] args) {
  5. SpringApplication.run(MainApplication.class, args);
  6. }
  7. }
  8. 复制代码

3 文章总结

我们再次回顾分层五个思考维度:

(1) 单一

每层只处理一类事情,例如util只承载工具对象,integration只处理外部服务,每层职责单一且清晰

(2) 降噪

如无必要无增实体,例如查询结果DTO只透出最关键字段,例如运动员ID、创建时间、修改时间等业务不强字段无须透出

(3) 适配

service、facade、intergration层都存在适配器,翻译信息为本层或者下层可以理解的信息

(4) 业务

业务对象可以通过充血模型聚合业务,例如在业务对象中聚合业务校验逻辑

(5) 数据

数据对象要纯净,例如通过string类型保存比赛表现,数据层无需解析

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号