asp.net网站开发框架,网站推广方案怎么写,网络营销的特点包括超前性,福清网站建设专家说明#xff1a;本博文主要参考来自 https://blog.csdn.net/BASK2311/article/details/128198005
据实践内容及代码持续总结更新中。
五个分层维度#xff1a;SpringBoot工程分层实战
1 分层思想
计算机领域有一句话#xff1a;计算机中任何问题都可通过增加一个虚拟层解…说明本博文主要参考来自 https://blog.csdn.net/BASK2311/article/details/128198005
据实践内容及代码持续总结更新中。
五个分层维度SpringBoot工程分层实战
1 分层思想
计算机领域有一句话计算机中任何问题都可通过增加一个虚拟层解决。这句体现了分层思想重要性分层思想同样适用于Java工程架构。
分层优点是每层只专注本层工作可以类比设计模式单一职责原则或者经济学比较优势原理每层只做本层最擅长的事情。
分层缺点是层之间通信时需要通过适配器翻译成本层或者下层可以理解的信息通信成本有所增加。
我认为工程分层需要从五个维度思考
(1) 单一
每层只处理一类事情满足单一职责原则
(2) 降噪
信息在每一层进行传输满足最小知识原则只向下层传输必要信息
(3) 适配
每层都需要一个适配器翻译信息为本层或者下层可以理解的信息
(4) 业务
业务对象可以整合业务逻辑例如使用充血模型整合业务
(5) 数据
数据对象尽量纯净尽量不要聚合业务
1.2 九层结构
综上所述SpringBoot工程可以分为九层
工具层util整合层integration基础层infrastructure服务层service领域层domain门面层facade控制层controller客户端client启动层boot
2 分层详解
创建测试项目user-demo-service
user-demo-service
-user-demo-service-boot
-user-demo-service-client
-user-demo-service-controller
-user-demo-service-domain
-user-demo-service-facade
-user-demo-service-infrastructure
-user-demo-service-integration
-user-demo-service-service
-user-demo-service-util2.1 util 工具层承载工具代码
不依赖本项目其它模块
只依赖一些通用工具包
user-demo-service-util
-/src/main/java
-date
-DateUtil.java
-json
-JSONUtil.java
-validate
-BizValidator.java2.2 infrastructure 基础层核心是承载数据访问entity实体对象承载在本层。
2.2.1 项目结构 代码层分为两个领域
player运动员 game比赛 每个领域具有两个子包
entity
mapper
user-demo-service-infrastructure
-/src/main/java
-player
-entity
-PlayerEntity.java
-mapper
-PlayerEntityMapper.java
-game
-entity
-GameEntity.java
-mapper
-GameEntityMapper.java
-/src/main/resources
-mybatis
-sqlmappers
-gameEntityMappler.xml
-playerEntityMapper.xml复制代码
2.2.2 本项目间依赖关系
infrastructure只依赖工具模块
dependencygroupIdcom.test.javafront/groupIdartifactIduser-demo-service-util/artifactId
/dependency2.2.3 核心代码
创建运动员数据表
CREATE TABLE player (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键,player_id varchar(256) NOT NULL COMMENT 运动员编号,player_name varchar(256) NOT NULL COMMENT 运动员名称,height int(11) NOT NULL COMMENT 身高,weight int(11) NOT NULL COMMENT 体重,game_performance text COMMENT 最近一场比赛表现,creator varchar(256) NOT NULL COMMENT 创建人,updator varchar(256) NOT NULL COMMENT 修改人,create_time datetime NOT NULL COMMENT 创建时间,update_time datetime NOT NULL COMMENT 修改时间,PRIMARY KEY (id)
) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8
运动员实体对象gamePerformance字段作为string保存在数据库这体现了数据层尽量纯净不要整合过多业务解析任务应该放在业务层
public class PlayerEntity {private Long id;private String playerId;private String playerName;private Integer height;private Integer weight;private String creator;private String updator;private Date createTime;private Date updateTime;private String gamePerformance;
}运动员Mapper对象
Repository
public interface PlayerEntityMapper {int insert(PlayerEntity record);int updateById(PlayerEntity record);PlayerEntity selectById(Param(playerId) String playerId);
}2.3 domain
2.3.1 概念说明
领域层是DDD流行兴起之概念
可以通过三组对比理解领域层
领域对象 VS 数据对象 领域对象 VS 业务对象 领域层 VS 业务层
(1) 领域对象 VS 数据对象
数据对象字段尽量纯净使用基本类型
public class PlayerEntity {private Long id;private String playerId;private String playerName;private Integer height;private Integer weight;private String creator;private String updator;private Date createTime;private Date updateTime;private String gamePerformance;
}以查询结果领域对象为例
领域对象需要体现业务含义
public class PlayerQueryResultDomain {private String playerId;private String playerName;private Integer height;private Integer weight;private GamePerformanceVO gamePerformance;
}public class GamePerformanceVO {// 跑动距离private Double runDistance;// 传球成功率private Double passSuccess;// 进球数private Integer scoreNum;
}(2) 领域对象 VS 业务对象
业务对象同样会体现业务领域对象和业务对象有什么不同呢其中一个最大不同是领域对象采用充血模型聚合业务。
运动员新增业务对象
public class PlayerCreateBO {private String playerName;private Integer height;private Integer weight;private GamePerformanceVO gamePerformance;private MaintainCreateVO maintainInfo;
}运动员新增领域对象
public class PlayerCreateDomain implements BizValidator {
private String playerName;
private Integer height;
private Integer weight;
private GamePerformanceVO gamePerformance;
private MaintainCreateVO maintainInfo;Overridepublic void validate() {if (StringUtils.isEmpty(playerName)) {throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);}if (null height) {throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);}if (height 300) {throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);}if (null weight) {throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);}if (null ! gamePerformance) {gamePerformance.validate();}if (null maintainInfo) {throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);}maintainInfo.validate();}
}(3) 领域层 VS 业务层
领域层和业务层都包含业务二者不是替代关系而是互补关系。业务层可以更加灵活组合不同领域业务并且可以增加流控、监控、日志、权限分布式锁等控制相较于领域层功能更为丰富。
2.3.2 项目结构
代码层分为两个领域
player运动员
game比赛每个领域具有三个子包
domain领域对象
event领域事件
vo值对象
user-demo-service-domain
-/src/main/java
-base
-domain
-BaseDomain.java
-event
-BaseEvent.java
-vo
-BaseVO.java
-MaintainCreateVO.java
-MaintainUpdateVO.java
-player
-domain
-PlayerCreateDomain.java
-PlayerUpdateDomain.java
-PlayerQueryResultDomain.java
-event
-PlayerUpdateEvent.java
-vo
-GamePerformanceVO.java
-game
-domain
-GameCreateDomain.java
-GameUpdateDomain.java
-GameQueryResultDomain.java
-event
-GameUpdateEvent.java
-vo
-GameSubstitutionVO.java2.3.3 本项目间依赖关系 domain依赖本项目两个模块
util
client之所以依赖client模块是因为领域对象聚合了业务校验以下信息需要暴露至外部
BizException
ErrorCodeBizEnum
dependency
groupIdcom.test.javafront/groupId
artifactIduser-demo-service-util/artifactId
/dependency
dependency
groupIdcom.test.javafront/groupId
artifactIduser-demo-service-client/artifactId
/dependency2.3.4 核心代码
以运动员修改领域对象为例
// 运动员修改领域对象
public class PlayerUpdateDomain extends BaseDomain implements BizValidator {
private String playerId;
private String playerName;
private Integer height;
private Integer weight;
private String updator;
private Date updatetime;
private GamePerformanceVO gamePerformance;
private MaintainUpdateVO maintainInfo;Overridepublic void validate() {if (StringUtils.isEmpty(playerId)) {throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);}if (StringUtils.isEmpty(playerName)) {throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);}if (null height) {throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);}if (height 300) {throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);}if (null weight) {throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);}if (null ! gamePerformance) {gamePerformance.validate();}if (null maintainInfo) {throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);}maintainInfo.validate();}
}
// 比赛表现值对象
public class GamePerformanceVO implements BizValidator {// 跑动距离private Double runDistance;// 传球成功率private Double passSuccess;// 进球数private Integer scoreNum;Overridepublic void validate() {if (null runDistance) {throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);}if (null passSuccess) {throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);}if (Double.compare(passSuccess, 100) 0) {throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);}if (null runDistance) {throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);}if (null scoreNum) {throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);}}
}// 修改人值对象
public class MaintainUpdateVO implements BizValidator {// 修改人private String updator;// 修改时间private Date updateTime;Overridepublic void validate() {if (null updator) {throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);}if (null updateTime) {throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);}}
}2.4 service
2.4.1 项目结构
user-demo-service-service
-/src/main/java
-player
-adapter
-PlayerServiceAdapter.java
-event
-PlayerMessageSender.java
-service
-PlayerService.java
-game
-adapter
-GameServiceAdapter.java
-event
-GameMessageSender.java
-service
-GameService.java2.4.2 本项目间依赖关系 service依赖本项目四个模块
util
domain
integration
infrastructuredependency
groupIdcom.test.javafront/groupId
artifactIduser-demo-service-domain/artifactId
/dependency
dependency
groupIdcom.test.javafront/groupId
artifactIduser-demo-service-infrastructure/artifactId
/dependency
dependency
groupIdcom.test.javafront/groupId
artifactIduser-demo-service-util/artifactId
/dependency
dependency
groupIdcom.test.javafront/groupId
artifactIduser-demo-service-integration/artifactId
/dependency2.4.3 核心代码
以运动员编辑服务为例
// 运动员服务
public class PlayerService {Resourceprivate PlayerEntityMapper playerEntityMapper;Resourceprivate PlayerMessageSender playerMessageSender;Resourceprivate PlayerServiceAdapter playerServiceAdapter;public boolean updatePlayer(PlayerUpdateDomain player) {AssertUtil.notNull(player, new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT));player.validate();PlayerEntity entity playerServiceAdapter.convertUpdate(player);playerEntityMapper.updateById(entity);playerMessageSender.sendPlayerUpdatemessage(player);return true;}
}
// 运动员消息服务
public class PlayerMessageSender {Resourceprivate PlayerServiceAdapter playerServiceAdapter;public boolean sendPlayerUpdatemessage(PlayerUpdateDomain domain) {PlayerUpdateEvent event playerServiceAdapter.convertUpdateEvent(domain);log.info(sendPlayerUpdatemessage event{}, event);return true;}
}// 服务适配器
public class PlayerServiceAdapter {// domain - entitypublic PlayerEntity convertUpdate(PlayerUpdateDomain domain) {PlayerEntity player new PlayerEntity();player.setPlayerId(domain.getPlayerId());player.setPlayerName(domain.getPlayerName());player.setWeight(domain.getWeight());player.setHeight(domain.getHeight());if (null ! domain.getGamePerformance()) {player.setGamePerformance(JacksonUtil.bean2Json(domain.getGamePerformance()));}String updator domain.getMaintainInfo().getUpdator();Date updateTime domain.getMaintainInfo().getUpdateTime();player.setUpdator(updator);player.setUpdateTime(updateTime);return player;}// domain - eventpublic PlayerUpdateEvent convertUpdateEvent(PlayerUpdateDomain domain) {PlayerUpdateEvent event new PlayerUpdateEvent();event.setPlayerUpdateDomain(domain);event.setMessageId(UUID.randomUUID().toString());event.setMessageId(PlayerMessageType.UPDATE.getMsg());return event;}
}2.5 intergration 本项目可能会依赖外部服务那么将外部DTO转换为本项目可以理解的对象需要在本层处理。
2.5.1 项目结构 假设本项目调用了用户中心服务
user-demo-service-intergration
-/src/main/java
-user
-adapter
-UserClientAdapter.java
-proxy
-UserClientProxy.java复制代码 2.5.2 本项目间依赖关系 intergration依赖本项目两个模块
util
domain之所以依赖domain模块是因为本层需要将外部DTO转换为本项目可以理解的对象这些对象就放在domain模块。
dependencygroupIdcom.test.javafront/groupIdartifactIduser-demo-service-domain/artifactId
/dependency
dependencygroupIdcom.test.javafront/groupIdartifactIduser-demo-service-util/artifactId
/dependency2.5.3 核心代码 现在我们将外部对象UserClientDTO
转换为本项目领域对象UserInfoDomain
(1) 外部服务
// 外部对象
public class UserInfoClientDTO implements Serializable {
private String id;
private String name;
private Date createTime;
private Date updateTime;
private String mobile;
private String cityCode;
private String addressDetail;
}// 外部服务
public class UserClientService {// RPC模拟public UserInfoClientDTO getUserInfo(String userId) {UserInfoClientDTO userInfo new UserInfoClientDTO();userInfo.setId(userId);userInfo.setName(userId);userInfo.setCreateTime(DateUtil.now());userInfo.setUpdateTime(DateUtil.now());userInfo.setMobile(test-mobile);userInfo.setCityCode(test-city-code);userInfo.setAddressDetail(test-address-detail);return userInfo;}
}(2) 本项目领域对象
domain模块新增user领域
user-demo-service-domain
-/src/main/java
-user
-domain
-UserDomain.java
-vo
-UserAddressVO.java
-UserContactVO.javauser领域对象代码
// 用户领域
public class UserInfoDomain extends BaseDomain {private UserContactVO contactInfo;private UserAddressVO addressInfo;
}// 地址值对象
public class UserAddressVO extends BaseVO {private String cityCode;private String addressDetail;
}// 联系方式值对象
public class UserContactVO extends BaseVO {
private String mobile;
}(3) 适配器
public class UserClientAdapter {// third dto - domainpublic UserInfoDomain convertUserDomain(UserInfoClientDTO userInfo) {UserInfoDomain userDomain new UserInfoDomain();UserContactVO contactVO new UserContactVO();contactVO.setMobile(userInfo.getMobile());userDomain.setContactInfo(contactVO);UserAddressVO addressVO new UserAddressVO();addressVO.setCityCode(userInfo.getCityCode());addressVO.setAddressDetail(userInfo.getAddressDetail());userDomain.setAddressInfo(addressVO);return userDomain;}
}(4) 调用外部服务
public class UserClientProxy {Resourceprivate UserClientService userClientService;Resourceprivate UserClientAdapter userClientAdapter;public UserInfoDomain getUserInfo(String userId) {UserInfoClientDTO user userClientService.getUserInfo(userId);UserInfoDomain result userClientAdapter.convertUserDomain(user);return result;}
}2.6 facade client
设计模式中有一种Facade模式称为门面模式或者外观模式。这种模式提供一个简洁对外语义屏蔽内部系统复杂性。
client承载数据对外传输对象DTOfacade承载对外服务这两层必须满足最小知识原则无关信息不必对外透出。
这样做有两个优点
简洁性对外服务语义明确简洁 安全性敏感字段不能对外透出
2.6.1 项目结构
(1) client
user-demo-service-client
-/src/main/java
-base
-dto
-BaseDTO.java
-error
-BizException.java
-BizErrorCode.java
-event
-BaseEventDTO.java
-result
-ResultDTO.java
-player
-dto
-PlayerCreateDTO.java
-PlayerQueryResultDTO.java
-PlayerUpdateDTO.java
-enums
-PlayerMessageTypeEnum.java
-event
-PlayerUpdateEventDTO.java
-service
-PlayerClientService.java(2) facade
user-demo-service-facade
-/src/main/java
-player
-adapter
-PlayerFacadeAdapter.java
-impl
-PlayerClientServiceImpl.java
-game
-adapter
-GameFacadeAdapter.java
-impl
-GameClientServiceImpl.java2.6.2 本项目间依赖关系 client不依赖本项目其它模块这一点非常重要因为client会被外部引用必须保证这一层简洁和安全。
facade依赖本项目三个模块
domain
client
servicedependencygroupIdcom.test.javafront/groupIdartifactIduser-demo-service-domain/artifactId
/dependencydependencygroupIdcom.test.javafront/groupIdartifactIduser-demo-service-client/artifactId
/dependency
dependencygroupIdcom.test.javafront/groupIdartifactIduser-demo-service-service/artifactId
/dependency2.6.3 核心代码
(1) DTO
以查询运动员信息为例查询结果DTO只封装最关键字段例如运动员ID、创建时间、修改时间等业务不强字段就无须透出
public class PlayerQueryResultDTO implements Serializable {private String playerName;private Integer height;private Integer weight;private GamePerformanceDTO gamePerformanceDTO;
}(2) 客户端服务
public interface PlayerClientService {public ResultDTOPlayerQueryResultDTO queryById(String playerId);
}(3) 适配器
public class PlayerFacadeAdapter {// domain - dtopublic PlayerQueryResultDTO convertQuery(PlayerQueryResultDomain domain) {if (null domain) {return null;}PlayerQueryResultDTO result new PlayerQueryResultDTO();result.setPlayerId(domain.getPlayerId());result.setPlayerName(domain.getPlayerName());result.setHeight(domain.getHeight());result.setWeight(domain.getWeight());if (null ! domain.getGamePerformance()) {GamePerformanceDTO performance convertGamePerformance(domain.getGamePerformance());result.setGamePerformanceDTO(performance);}return result;}
} (4) 服务实现
public class PlayerClientServiceImpl implements PlayerClientService {Resourceprivate PlayerService playerService;Resourceprivate PlayerFacadeAdapter playerFacadeAdapter;Overridepublic ResultDTOPlayerQueryResultDTO queryById(String playerId) {PlayerQueryResultDomain resultDomain playerService.queryPlayerById(playerId);if (null resultDomain) {return ResultCommonDTO.success();}PlayerQueryResultDTO result playerFacadeAdapter.convertQuery(resultDomain);return ResultCommonDTO.success(result);}
}2.7 controller
facade服务实现可以作为RPC提供服务controller则作为本项目HTTP接口提供服务供前端调用。
controller需要注意HTTP相关特性敏感信息例如登陆用户ID不能依赖前端传递登陆后前端会在请求头带一个登陆用户信息服务端需要从请求头中获取并解析。
2.7.1 项目结构
user-demo-service-controller
-/src/main/java
-config
-CharsetConfig.java
-controller
-player
-PlayerController.java
-game
-GameController.java2.7.2 本项目依赖关系
controller依赖本项目一个模块
facade根据依赖传递原理同时依赖以下模块
domain
client
servicedependencygroupIdcom.test.javafront/groupIdartifactIduser-demo-service-facade/artifactId
/dependency2.7.3 核心代码
RestController
RequestMapping(/player)
public class PlayerController {Resourceprivate PlayerClientService playerClientService;PostMapping(/add)public ResultDTOBoolean add(RequestHeader(test-login-info) String loginUserId, RequestBody PlayerCreateDTO dto) {dto.setCreator(loginUserId);ResultCommonDTOBoolean resultDTO playerClientService.addPlayer(dto);return resultDTO;}PostMapping(/update)public ResultDTOBoolean update(RequestHeader(test-login-info) String loginUserId, RequestBody PlayerUpdateDTO dto) {dto.setUpdator(loginUserId);ResultCommonDTOBoolean resultDTO playerClientService.updatePlayer(dto);return resultDTO;}GetMapping(/{playerId}/query)public ResultDTOPlayerQueryResultDTO queryById(RequestHeader(test-login-info) String loginUserId, PathVariable(playerId) String playerId) {ResultCommonDTOPlayerQueryResultDTO resultDTO playerClientService.queryById(playerId);return resultDTO;}
}2.8 boot
boot作为启动层只有启动入口代码
2.8.1 项目结构
所有模块代码均必须属于com.user.demo.service子路径
user-demo-service-boot
-/src/main/java
-com.user.demo.service
-MainApplication.java2.8.2 本项目间依赖 boot引用本项目所有模块
util
integration
infrastructure
service
domain
facade
controller
client2.8.3 核心代码
MapperScan(com.user.demo.service.infrastructure.*.mapper)
SpringBootApplication
public class MainApplication {public static void main(final String[] args) {SpringApplication.run(MainApplication.class, args);}
}3 文章总结
我们再次回顾分层五个思考维度
(1) 单一
每层只处理一类事情例如util只承载工具对象integration只处理外部服务每层职责单一且清晰
(2) 降噪
如无必要无增实体例如查询结果DTO只透出最关键字段例如运动员ID、创建时间、修改时间等业务不强字段无须透出
(3) 适配
service、facade、intergration层都存在适配器翻译信息为本层或者下层可以理解的信息
(4) 业务
业务对象可以通过充血模型聚合业务例如在业务对象中聚合业务校验逻辑
(5) 数据
数据对象要纯净例如通过string类型保存比赛表现数据层无需解析