• 五个分层维度: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 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类型保存比赛表现,数据层无需解析

  • 相关阅读:
    easypoi导入案例
    【牛客】WY49数对,JZ65不用加减乘除做加法
    【JavaWeb】13-EL表达式
    java 实现外观模式
    openEuler-20.03 LTS管理用户和用户组
    python忽略警告信息
    剑指:二叉树有关题目
    【esp32c3配置arduino IDE教程】
    vue图片显示
    数据库:Hive转Presto(二)
  • 原文地址:https://blog.csdn.net/BASK2311/article/details/128198005