1. 经典分层架构(四层架构)
2.工程目录结构
map
┣ api
┃ ┣ assembler
┃ ┣ controller
┃ ┃ ┣ v1
┃ ┣ dto
┣ app
┃ ┣ service
┃ ┃ ┣ impl
┣ config
┣ domain
┃ ┣ aggregate
┃ ┣ entity
┃ ┣ factory
┃ ┣ repository
┃ ┣ service
┣ infra
┃ ┣ exception
┃ ┣ constant
┃ ┣ feign
┃ ┣ mapper
┃ ┣ repository
┃ ┃ ┣ impl
┃ ┣ util
3.各层级结构
3.1 api(用户展现层)
负责向用户展现信息,并且解析用户行为
请求应用层以获取要表现的数据
发送命令给应用层执行某个用户的命令
┣ api
┃ ┣ assembler
┃ ┃ ┣ Assembler
┃ ┣ controller
┃ ┃ ┣ v1
┃ ┃ ┃ ┣ MappingHeaderController
┃ ┃ ┃ ┣ MappingItemController
┃ ┃ ┃ ┣ MappingRuleHeaderController
┃ ┣ dto
┃ ┃ ┣ MappingItemValueDTO
┃ ┃ ┣ MappingLineDTO
- assembler
负责dto与领域对象之前的相互转化,数据交换 - controller
负责定义用户接口 - dto
用于接口传参以及前端显示
数据传输的载体,通过DTO把内部的对象与外界隔离
3.2 app(应用层)
为程序提供任务处理,用于跨领域服务的操作,业务流程的整合
相对于领域层,应用层是很薄的一层,应用层定义了软件要完成的任务,要尽量简单
它做业务流程的整合,为下一层的领域对象协助任务,委托工作
对外,为展现层提供service
对内,调用领域层完成各种业务逻辑
┣ app
┃ ┣ service
┃ ┃ ┣ MappingHeaderService
┃ ┃ ┣ MappingItemService
┃ ┃ ┣ MappingLineService
┃ ┃ ┣ MappingRuleLineService
┃ ┃ ┣ impl
┃ ┃ ┃ ┣ MappingHeaderServiceImpl
┃ ┃ ┃ ┣ MappingItemServiceImpl
┃ ┃ ┃ ┣ MappingLineServiceImpl
┃ ┃ ┃ ┣ MappingRuleLineServiceImpl
- service
应用服务接口
尽量薄的一层,主要用来协调领域层和基础层的操作来满足业务流程
3.3 domain(领域层)
主要负责表达业务逻辑,是整个系统的核心层,包括聚合对象,实体,工厂,资源库,领域服务
┣ domain
┃ ┣ aggregate
┃ ┣ entity
┃ ┃ ┣ MappingHeader
┃ ┃ ┣ MappingItem
┃ ┃ ┣ MappingLine
┃ ┃ ┣ MappingRuleLine
┃ ┣ factory
┃ ┣ repository
┃ ┃ ┣ MappingHeaderRepository
┃ ┃ ┣ MappingItemRepository
┃ ┃ ┣ MappingLineRepository
┃ ┃ ┣ MappingRuleLineRepository
┃ ┣ service
┃ ┃ ┣ MappingDomainService
┃ ┃ ┣ MappingLineDomainService
┃ ┃ ┣ MappingRuleDomainService
┃ ┃ ┣ MappingValueDomainService
┃ ┃ ┣ impl
┃ ┃ ┃ ┣ MappingDomainServiceImpl
┃ ┃ ┃ ┣ MappingLineDomainServiceImpl
┃ ┃ ┃ ┣ MappingRuleDomainServiceImpl
┃ ┃ ┃ ┣ MappingValueDomainServiceImpl
┃ ┣ vo
┃ ┃ ┣ MappingItemValueValidateVO
┃ ┃ ┣ MappingItemVO
┃ ┃ ┣ MappingLineVO
- aggregate 聚合对象
一组聚合内聚关系的相关对象的集合.暂时未使用 - entity 实体对象
具有唯一标识的对象 - factory工厂类
用于构建复杂实体或者聚合对象,隐藏创建细节 - repository资源库
对领域的存储和访问进行统一管理的对象,提供查找和持久化对象的方法 - service领域服务
实现领域内部对象的行为或操作,处理领域内核心业务逻辑,无法归类于实体对象或值对象的一些操作 - vo值对象
无需唯一标识的对象
3.4config层(配置层)
┣ config
┃ ┣ MapSwaggerApiConfig
┃ ┣ MapInitalizingConfig
3.5 infra(基础设施层)
为其他层提供底层依赖
- 为应用层,传递消息
- 为领域层,提供持久化机制
- 为用户展现层,提供组件配置
┣ infra
┃ ┣ constant
┃ ┃ ┣ ExceptionConstants
┃ ┃ ┣ H1BaseConstants
┃ ┣ feign
┃ ┃ ┣ HzeroLovClient
┃ ┣ mapper
┃ ┃ ┣ MappingHeaderMapper
┃ ┃ ┣ MappingItemMapper
┃ ┃ ┣ MappingRuleLineMapper
┃ ┃ ┣ MappingSourceLineMapper
┃ ┣ repository
┃ ┃ ┣ impl
┃ ┃ ┃ ┣ MappingHeaderRespositoryImpl
┃ ┃ ┃ ┣ MappingItemRepositoryImpl
┃ ┃ ┃ ┣ MappingRuleHeaderRespositoryImpl
┃ ┃ ┃ ┣ MappingRuleLineRepositoryImpl
┃ ┣ util
┃ ┃ ┣ DateUtils
┃ ┃ ┣ TimeSection
┃ ┃ ┣ H1DetailsHelper
- constant 系统常量
- feign feign调用
- mapper mybatis的mapper接口
- repository.impl 资源库实现类接口
- util 工具类
实战
4.1 单体定义功能
单表基础数据的定义功能,不需要领域服务,可以直接在展现层调用基础设施层资源库的方法做持久化。以 映射匹配项定义 功能为例:
map
┣ api
┃ ┣ controller
┃ ┃ ┣ v1
┃ ┃ ┃ ┣ MappingItemController
┣ app
┃ ┣ service
┃ ┃ ┣ MappingItemService
┃ ┃ ┣ impl
┃ ┃ ┃ ┣ MappingItemServiceImpl
┣ domain
┃ ┣ entity
┃ ┃ ┣ MappingItem
┃ ┣ repository
┃ ┃ ┣ MappingItemRepository
┣ infra
┃ ┣ constant
┃ ┃ ┣ RedisKeyConstants
┃ ┣ mapper
┃ ┃ ┣ MappingItemMapper
┃ ┣ repository
┃ ┃ ┣ impl
┃ ┃ ┃ ┣ MappingItemRepositoryImpl
resource
┣ mapper
┃ ┣ MappingItemMapper.xml
4.1.1 api展现层
- 查询直接使用的资源库进行查询
- 批量更新和批量删除使用app层的service的方法
public class MappingItemController extends BaseController {
@Autowired
private MappingItemRepository mappingItemRepository;
@Autowired
private MappingItemService mappingItemService;
@ApiOperation(value = "映射匹配项表列表")
@Permission(level = ResourceLevel.ORGANIZATION)
@GetMapping("/list")
public ResponseEntity<Page<MappingItem>> list(MappingItem mappingItem,
@ApiIgnore @SortDefault(value = MappingItem.FIELD_MAPPING_ITEM_CODE,
direction = Sort.Direction.ASC) PageRequest pageRequest) {
Criteria criteria = new Criteria(mappingItem);
criteria.where(new WhereField(MappingItem.FIELD_MAPPING_ITEM_CODE, Comparison.LIKE),
new WhereField(MappingItem.FIELD_DESCRIPTION, Comparison.LIKE),
new WhereField(MappingItem.FIELD_SOURCE, Comparison.LIKE));
Page<MappingItem> list = PageHelper.doPageAndSort(pageRequest,
() -> mappingItemRepository.selectOptional(mappingItem, criteria));
return Results.success(list);
}
@ApiOperation(value = "批量更新映射匹配项表")
@Permission(level = ResourceLevel.ORGANIZATION)
@PostMapping("/batchSave")
public ResponseEntity<List<MappingItem>> batchSave(@PathVariable Long organizationId,
@RequestBody List<MappingItem> lists) {
lists.forEach(r -> r.setTenantId(organizationId));
validObject(lists);
mappingItemService.batchSave(lists);
return Results.success(lists);
}
@ApiOperation(value = "批量删除映射匹配项表")
@Permission(level = ResourceLevel.ORGANIZATION)
@DeleteMapping("/batchDelete")
public ResponseEntity<List<MappingItem>> batchDelete(@PathVariable Long organizationId,
@RequestBody List<MappingItem> lists) {
lists.forEach(r -> r.setTenantId(organizationId));
validObject(lists);
mappingItemService.batchDelete(lists);
return Results.success(lists);
}
}
4.1.2 app应用层
- 进行事务的控制,调用infra层资源库的操作持久化并做缓存的处理
@Service
@Transactional(rollbackFor = Exception.class)
public class MappingItemServiceImpl implements MappingItemService {
@Autowired
private MappingItemRepository mappingItemRepository;
@Override
public int batchSave(List<MappingItem> lists) {
int saveCount = mappingItemRepository.batchSave(lists);
for (MappingItem item : lists) {
if (AuditDomain.RecordStatus.create.equals(item.get_status())) {
mappingItemRepository.setMappingItemToCache(item);
} else {
mappingItemRepository.delMappingItemFromCache(item);
}
}
return saveCount;
}
@Override
public void batchDelete(List<MappingItem> lists) {
mappingItemRepository.batchDelete(lists);
for (MappingItem item : lists) {
mappingItemRepository.delMappingItemFromCache(item);
}
}
}
4.1.3 domain领域层
- 数据库表字段
- 构造redis的key
public class MappingItem extends AuditDomain {
//
// 业务方法(按public protected private顺序排列)
// ------------------------------------------------------------------------------
public static String getCacheKey(Long tenantId, String mappingItemCode) {
return RedisKeyConstants.MAP_ITEM + tenantId + "_" + mappingItemCode;
}
//
// 数据库字段
// ------------------------------------------------------------------------------
@ApiModelProperty("映射项ID")
@Id
@GeneratedValue
@Where
private Long mappingItemId;
@ApiModelProperty(value = "映射项代码", required = true)
@NotBlank
@Where
private String mappingItemCode;
@Where
@MultiLanguageField
@ApiModelProperty(value = "描述")
private String description;
...
}
4.1.4 infra基础层
- 资源库中增加了redis的操作,处理缓存
@Component
public class MappingItemRepositoryImpl extends BaseRepositoryImpl<MappingItem> implements MappingItemRepository {
@Autowired
private MappingItemMapper mappingItemMapper;
@Qualifier("redisHelper")
@Autowired
private RedisHelper redisHelper;
public static Logger logger = LoggerFactory.getLogger(MappingItemServiceImpl.class);
public void setMappingItemToCache(MappingItem item) {
this.redisHelper.objectSet(MappingItem.getCacheKey(item.getTenantId(), item.getMappingItemCode()), item);
}
public void delMappingItemFromCache(MappingItem item) {
this.redisHelper.delKey(MappingItem.getCacheKey(item.getTenantId(), item.getMappingItemCode()));
}
}
4.2 复杂业务逻辑功能
- 领域
广义上讲,领域(Domain)即是一个组织所做的事件以及其中所包含的一切。每个组织都有它自己的业务范围和做事方式。这个业务范围以及在其中所进行的活动便是领域 - 子域
在整个领域中,我们如何对其进行拆分,然后满足我们的业务逻辑 - 限界上下文
- 限的意思就是划分、规定,界就是界限、或者一个边界,上下文就是业务流程
- 限界上下文定义了领域模型的边界,目的是清理子域,然后区分子域哪些是核心域,支撑子域和通用子域
- 一般一个限界上下文对应一个子域。另外,一个限界上下文有可能包含的不只一个子域
以 映射值配置功能为例:
4.2.1 梳理限界上下文
4.2.2 目录结构
map
┣ api
┃ ┣ assembler
┃ ┃ ┣ Assembler #dto装配器
┃ ┣ controller
┃ ┃ ┣ v1
┃ ┃ ┃ ┣ MappingHeaderController #映射头
┃ ┃ ┃ ┣ MappingSourceLineController #来源行
┃ ┃ ┃ ┣ MappingValueRelationController #关联关系
┃ ┃ ┃ ┣ ...
┃ ┣ dto
┃ ┃ ┣ MappingItemValueDTO #映射项值dto
┃ ┃ ┣ MappingLineDTO #映射行dto(包括来源行dto和目标行dto)
┃ ┃ ┣ MappingSourceLineDTO #来源行dto
┃ ┃ ┣ MappingTargetLineDTO #目标行dto
┃ ┃ ┣ MappingValueLineDTO #映射值行dto(包括映射项值dto)
┃ ┃ ┃ ...
┣ app
┃ ┣ service
┃ ┃ ┣ MappingLineService #映射行服务
┃ ┃ ┣ MappingValueLineService #映射行值服务
┃ ┃ ┣ ...
┃ ┃ ┣ impl
┃ ┃ ┃ ┣ MappingHeaderServiceImpl
┃ ┃ ┃ ┣ ...
┣ domain
┃ ┣ entity
┃ ┃ ┣ MappingHeader #映射头实体
┃ ┃ ┣ MappingSourceLine #来源行实体
┃ ┃ ┣ MappingTargetLine #目标行实体
┃ ┃ ┣ MappingValueRelation #映射关联实体
┃ ┃ ┣ ...
┃ ┣ vo
┃ ┃ ┣ MappingItemValueValidateVO #映射项值校验vo
┃ ┃ ┣ MappingLineVO #映射行vo
┃ ┣ service
┃ ┃ ┣ MappingDomainService #映射领域服务
┃ ┃ ┣ MappingLineDomainService #映射行领域服务
┃ ┃ ┣ MappingValueDomainService #映射行值领域服务
┃ ┃ ┣ impl
┃ ┃ ┃ ┣ MappingDomainServiceImpl
┃ ┃ ┃ ┣ ...
┃ ┣ repository
┃ ┃ ┣ MappingValueRelationRepository
┃ ┃ ┣ ...
┣ infra
┃ ┣ constant
┃ ┃ ┣ RedisKeyConstants
┃ ┣ mapper
┃ ┃ ┣ MappingHeaderMapper
┃ ┃ ┣ ...
┃ ┣ repository
┃ ┃ ┣ impl
┃ ┃ ┃ ┣ MappingHeaderRepositoryImpl
┃ ┃ ┃ ┣ ...
┃ ┣ util
┃ ┃ ┣ DateUtils #日期比较工具
┃ ┃ ┣ TimeSection #时间区间工具
resource
┣ mapper
┃ ┣ MappingValueRelationMapper.xml
┃ ┣ ...
4.2.3 api展现层
- 映射配置行页面进行初始化查询,动态加载列。
- 映射配置行的查询,保存和删除
public class MappingValueLineController {
@Autowired
MappingLineService mappingLineService;
@Autowired
MappingValueLineService mappingValueLineService;
/**
* 映射行-初始化查询,将来源和目标封装返回
*
* @param mappingHeaderId 映射头id
* @return
* @author quyuanyuan 2020-05-21 9:54
*/
@GetMapping(value = "/init")
@Permission(level = ResourceLevel.ORGANIZATION)
@ApiOperation(value = "映射配置行进页面初始化查询")
public ResponseEntity<MappingLineDTO> init(
@RequestParam @ApiParam(value = "映射头id", required = true) Long mappingHeaderId) {
MappingLineDTO mappingValueLineInitDTO = mappingLineService.getInitLineByHeaderId(mappingHeaderId);
return Results.success(mappingValueLineInitDTO);
}
/**
* 映射配置值查询
*
* @param queryValueDTO 映射头id
* @return
* @author quyuanyuan 2020-05-21 9:54
*/
@PostMapping(value = "/query")
@Permission(level = ResourceLevel.ORGANIZATION)
@ApiOperation(value = "映射配置值查询")
public ResponseEntity<Page<MappingValueLineDTO>> query(
@RequestBody @ApiParam(value = "映射值查询条件") MappingValueLineDTO queryValueDTO,
@PathVariable @ApiParam(value = "租户id", required = true) Long organizationId,
PageRequest pageRequest) {
Page<MappingValueLineDTO> lists = mappingValueLineService.query(queryValueDTO, organizationId, pageRequest);
return Results.success(lists);
}
/**
* 映射配置值保存
*
* @param list 值
* @param organizationId 租户id
* @return
* @author quyuanyuan 2020-05-21 19:50
*/
@PostMapping(value = "/batchSave")
@Permission(level = ResourceLevel.ORGANIZATION)
@ApiOperation(value = "映射配置行保存")
public ResponseEntity<List<MappingValueLineDTO>> batchSave(
@RequestBody @ApiParam(value = "映射值行列表", required = true) List<MappingValueLineDTO> list,
@PathVariable @ApiParam(value = "租户id", required = true) Long organizationId) {
mappingValueLineService.batchSave(list, organizationId);
return Results.success(list);
}
/**
* 映射配置值删除
*
* @param list 值
* @return
* @author quyuanyuan 2020-05-22 19:50
*/
@PostMapping(value = "/batchDelete")
@Permission(level = ResourceLevel.ORGANIZATION)
@ApiOperation(value = "映射配置行批量删除")
public ResponseEntity<?> batchDelete(
@RequestBody @ApiParam(value = "映射值行列表", required = true) List<MappingValueLineDTO> list,
@PathVariable @ApiParam(value = "租户id", required = true) Long organizationId) {
mappingValueLineService.batchDelete(list, organizationId);
return Results.success();
}
}
4.2.4 app应用层
映射行服务
public interface MappingLineService {
/**
* 根据映射头获取来源和目标列,用于页面字段
*
* @param mappingHeaderId 映射头id
* @author quyuanyuan 2020-05-19 9:23
* @return io.choerodon.core.domain.Page<com.hand.hfins.map.domain.entity.MapMappingSourceLine>
*/
MappingLineDTO getInitLineByHeaderId(Long mappingHeaderId);
}
映射值行服务
public interface MappingValueLineService {
/**
* 映射关联值保存
*
* @param list
* @param tenantId
* @author quyuanyuan 2020-05-21 15:10
* @return java.util.List<com.hand.hfins.map.api.dto.MappingValueLineDTO>
*/
List<MappingValueLineDTO> batchSave(List<MappingValueLineDTO> list, Long tenantId);
/**
* 映射关联值查询
*
* @param queryValueDTO
* @param pageRequest
* @author quyuanyuan 2020-05-21 15:10
* @return List<com.hand.hfins.map.api.dto.MappingValueLineDTO>
*/
Page<MappingValueLineDTO> query(MappingValueLineDTO queryValueDTO, Long tenantId, PageRequest pageRequest);
/**
* 映射关联值批量删除
*
* @param list
* @author quyuanyuan 2020-05-22 15:10
* @return
*/
void batchDelete(List<MappingValueLineDTO> list, Long tenantId);
}
4.2.5 domain领域层
- 映射值领域服务,处理映射值关联关系的新增和更新
- 映射来源行值与目标行值的操作使用单体的资源库
- 校验默认值在时间区间中是否存在交叉
@Service
public class MappingValueDomainServiceImpl<MapMappingValueDomainService> implements MappingValueDomainService {
/**
* 新增关联关系
*
* @param mappingValueRelation
* @author quyuanyuan 2020-06-22 11:24
* @return com.hand.hfins.map.domain.entity.MappingValueRelation
*/
@Override
public MappingValueRelation insertRelation(MappingValueRelation mappingValueRelation) {
...
}
/**
* 更新关联关系
*
* @param mappingValueRelation 新的记录
* @param oldMappingValueRelation 历史的记录
* @author quyuanyuan 2020-06-22 11:24
* @return com.hand.hfins.map.domain.entity.MappingValueRelation
*/
@Override
public MappingValueRelation updateRelation(MappingValueRelation mappingValueRelation,
MappingValueRelation oldMappingValueRelation) {
...
}
/**
*
* 校验维护映射的默认值,所有的默认值中,时间区间存在重叠,则报错
*
* @param mappingHeaderId 映射头ID
* @throws io.choerodon.core.exception.CommonException exception
* @author quyuanyuan 2021/5/27 16:20
**/
private void checkMappingDefaultValue(Long filterRelationId,Long mappingHeaderId, List<MappingSourceValueLn> sourceValueLines,
Date startDate, Date endDate) {
...
}
}