应用分层
DDD 代码架构
大的层次上分为四层
- api:用户接口层,向外提供服务
- app:应用层,包含应用服务,负责用例流程调度,事务控制
- domain:领域层,包含领域对象和领域服务,专注核心业务
- infra:基础设施层,提供数据持久化、防腐层实现、第三方库、消息等
api 层
- controller:提供资源服务,XxxController.java
- dto:数据传输对象,XxxDTO.java,对于一些复杂页面需要多个实体组合时,可使用DTO对象来传输数据。
app 层
- service:应用服务,XxxService.java,应用服务里进行事务控制,流程调度
- service.impl:应用服务实现,XxxServiceImpl.java
- assembler:DTO组装器,XxxAssembler.java,复杂DTO的组装,简单的直接使用Entity即可
domain 层
- entity:实体对象,与表做映射,具备一些简单的自治的业务方法
- service:领域服务,命名一般按提供的业务功能命名,通常用于封装一个领域内的复杂业务逻辑,简单的业务逻辑在 app 层完成即可,不需要领域层。
- repository:资源库接口,XxxRepository.java,提供数据资源的操作方法,如数据库增删改查、Redis增删改查等,查询操作建议写到 repository 内。
- vo:值对象,XxxVO.java,领域内用到的数据封装,对于一些没有实体对象的数据对象但又在领域中用到,使用值对象封装
infra 层
- mapper:Mapper接口,XxxMapper.java
- repository.impl:资源库实现,XxxRepositoryImpl.java,业务一定不要侵入到这里
- constant:常量
- util:工具
最简单的DDD架构
至少需要包含如下的结构,将业务和流程分开,应用服务专注用例调度,反应用户故事;领域对象/服务专注核心业务。整个模块通用的放到基础设施层,资源库和外部服务实现也放到基础设施层,屏蔽实现细节。
└─src
├─main
│ ├─java
│ │ └─com
│ │ └─hand
│ │ └─<module>
│ │ ├─api
│ │ │ ├─controller
│ │ │ │ └─v1
│ │ │ │ └─XxxController.java
│ │ │ └─dto
│ │ │ └─XxxDTO.java
│ │ │
│ │ ├─app
│ │ │ └─service
│ │ │ ├─XxxService.java
│ │ │ └─impl
│ │ │ └─XxxServiceImpl.java
│ │ │
│ │ ├─domain
│ │ │ ├─entity
│ │ │ │ └─Xxx.java
│ │ │ └─repository
│ │ │ └─XxxRepository.java
│ │ │
│ │ └─infra
│ │ ├─mapper
│ │ │ └─XxxMapper.java
│ │ └─repository
│ │ └─impl
│ │ └─XxxRepositoryImpl.java
│ │
│ └─resources
│
└─test
开发步骤
1.实体
- 实体继承
AuditDomain
,AuditDomain 包含标准的审计字段 - 使用
@Table
映射表名 - 使用
@ModifyAudit
注解标明在更新数据时需要更新lastUpdateDate
、lastUpdatedBy
两个审计字段 - 使用
@VersionAudit
注解标明在更新数据时需要更新版本号objectVersionNumber
- 使用
@ApiModel
注解说明实体含义,在 Swagger 文档上就可以看到实体说明。 - 实体中可以包含一些实体自治的方法,这些方法通常用于对本身的属性做一些计算、操作等。
- 实体主键使用
@Id
、@GeneratedValue
注解标注 - 实体字段使用
@ApiModelProperty
说明字段含义,在 Swagger 文档上可以看到字段说明。 - 非数据库字段使用
@Transient
注解标注 - 实体除开数据映射字段,可以包含一些 Transient 注解的非数据库字段。如果页面用到的非数据库字段比较多,建议使用 DTO 封装数据。
package org.hzero.platform.domain.entity;
import ...;
/**
* 事件
*
* @author jiangzhou.bo@hand-china.com 2018/06/08 15:53
*/
@ApiModel("事件")
@VersionAudit
@ModifyAudit
@Table(name = "hpfm_event")
public class Event extends AuditDomain {
public static final String EVENT_ID = "eventId";
public static final String EVENT_EVENT_CODE = "eventCode";
/**
* 比较两个事件的事件编码是否一致
*
* @param anotherEvent 事件
* @throws CommonException 如果两个事件的编码不一致
*/
public void equalsEventCode(Event anotherEvent) {
if (StringUtils.isNotBlank(anotherEvent.getEventCode())
&& !StringUtils.equals(this.eventCode, anotherEvent.getEventCode())) {
throw new CommonException(BaseConstants.ErrorCode.DATA_INVALID);
}
}
@Id
@GeneratedValue
private Long eventId;
@Length(max = 30)
@Pattern(regexp = "^[A-Za-z0-9]*$")
@ApiModelProperty("事件编码")
private String eventCode;
@ApiModelProperty("是否启用")
private Integer enabledFlag;
@ApiModelProperty("事件描述")
private String eventDescription;
@Transient
@ApiModelProperty("事件规则列表")
private List<EventRule> ruleList;
//
// getter/setter
// ------------------------------------------------------------------------------
/**
* @return 事件ID
*/
public Long getEventId() {
return eventId;
}
/**
* @param eventId 事件ID
*/
public void setEventId(Long eventId) {
this.eventId = eventId;
}
/**
* @return 事件编码
*/
public String getEventCode() {
return eventCode;
}
/**
* @param eventCode 事件编码
*/
public void setEventCode(String eventCode) {
this.eventCode = eventCode;
}
/**
*
* @return 是否启用 <br/>
* <ul>
* <li>1 - 启用</li>
* <li>0 - 禁用</li>
* </ul>
*/
public Integer getEnabledFlag() {
return enabledFlag;
}
/**
* @param enabledFlag 是否启用 <br/>
* <ul>
* <li>1 - 启用</li>
* <li>0 - 禁用</li>
* </ul>
*/
public void setEnabledFlag(Integer enabledFlag) {
this.enabledFlag = enabledFlag;
}
/**
* @return 事件说明
*/
public String getEventDescription() {
return eventDescription;
}
/**
* @param eventDescription 事件说明
*/
public void setEventDescription(String eventDescription) {
this.eventDescription = eventDescription;
}
@ApiIgnore
public List<EventRule> getRuleList() {
return ruleList;
}
public void setRuleList(List<EventRule> ruleList) {
this.ruleList = ruleList;
}
}
2.资源库
- 领域层资源库只提供接口,资源库可以返回实体、DTO、VO等对象,一般对于
insert
/update
/delete
可以直接使用基础功能实现,对于查询,根据需求返回特定对象。 - 对于查询,在controller里可以直接依赖repository,不需要在应用服务里再封装一次。
- Redis 缓存、查询也是一种资源库操作,所以应该写在 repository 中。
package org.hzero.platform.domain.repository;
import ...;
/**
* 事件资源库
*
* @author jiangzhou.bo@hand-china.com 2018/06/11 16:55
*/
public interface EventRepository extends BaseRepository<Event> {
/**
* 分页查询
*/
Page<Event> page(Event event, int page, int size);
/**
* 获取事件规则
*
* @param eventId 事件ID
* @return Event detail
*/
Event get(Long eventId);
/**
* 删除事件
*
* @param eventId 事件ID
*/
void remove(Long eventId);
/**
* 导出数据
*/
List<Event> export(Event event, ExportParam exportParam, PageRequest pageRequest);
/**
* 缓存key为事件编码,value为事件规则列表<br/>
* 如果为启用状态,查询出事件规则,并刷新到缓存中<br/>
* 如果为禁用状态,清除该事件缓存
*/
void refreshCache(Event event);
/**
* 清除缓存
*
* @param redisHelper RedisHelper
*/
void clearCache(Event event);
}
3.应用服务
- Controller依赖应用服务,应用服务做事务控制,流程调度,展示用户故事。
package org.hzero.platform.app.service;
import ...;
/**
* 事件应用服务
*
* @author jiangzhou.bo@hand-china.com 2018/06/11 16:53
*/
public interface EventService {
/**
* 创建事件
*
* @param event 事件
* @return
*/
Event create(Event event);
/**
* 更新事件
*
* @param event 事件
* @return
*/
Event update(Event event);
/**
* 删除事件
*
* @param eventId 事件ID not null.
*/
void remove(Long eventId);
/**
* 批量删除事件
*
* @param eventIds 事件ID
*/
void batchRemove(Long[] eventIds);
/**
* 创建事件规则
*
* @param eventId 事件ID
* @param eventRule 事件规则
* @return 事件规则
*/
EventRule createEventRule(Long eventId, EventRule eventRule);
/**
* 修改事件规则
*
* @param eventId 事件ID
* @param eventRule 事件规则
* @return 事件规则
*/
EventRule updateEventRule(Long eventId, EventRule eventRule);
/**
* 批量删除事件规则
*
* @param eventId 事件ID
* @param eventRuleIds 事件规则ID集合
*/
void batchRemoveEventRule(Long eventId, Long[] eventRuleIds);
}
4.用户接口
- Controller 可以直接依赖应用服务或者资源库,资源库用于查询
- 使用
ResponseEntity
封装返回数据 - 注意API的写法,
GET
用于查询,POST
用于创建,PUT
用于修改,DELETE
用于删除,其它参考API规约 - Controller 的名称,首字母小写+版本号,以区分不同版本的Controller(如:
eventController.v1
),避免对象冲突。
package org.hzero.platform.api.controller.v1;
import ...;
/**
* 事件管理 API
*
* @author jiangzhou.bo@hand-china.com 2018/06/11 16:29
*/
@Api(tags = {SwaggerApiConfig.EVENT})
@RestController("eventController.v1")
@RequestMapping("/v1/{organizationId}/events")
public class EventController extends BaseController {
@Autowired
private EventRepository eventRepository;
@Autowired
private EventRuleRepository eventRuleRepository;
@Autowired
private EventService eventService;
@ApiOperation(value = "查询事件列表")
@ApiImplicitParams({
@ApiImplicitParam(name = "eventCode", value = "事件编码", paramType = "query"),
@ApiImplicitParam(name = "eventDescription", value = "事件描述", paramType = "query")
})
@Permission(level = ResourceLevel.ORGANIZATION)
@GetMapping
public ResponseEntity<Page<Event> list(String eventCode, String eventDescription, PageRequest pageRequest) {
Event event = new Event();
event.setEventCode(eventCode);
event.setEventDescription(eventDescription);
return Results.success(eventRepository.page(event, pageRequest.getPage(), pageRequest.getSize()));
}
@ApiOperation(value = "事件及规则列表")
@ApiImplicitParams({@ApiImplicitParam(name = "eventId", value = "事件ID", paramType = "query"),})
@Permission(level = ResourceLevel.ORGANIZATION)
@GetMapping("/{eventId}")
public ResponseEntity<Event> listEventRule(@PathVariable Long eventId) {
Event event = eventRepository.get(eventId);
return Results.success(event);
}
@ApiOperation(value = "创建事件")
@Permission(level = ResourceLevel.ORGANIZATION)
@PostMapping
public ResponseEntity<Event> create(@RequestBody Event event) {
return Results.success(eventService.create(event));
}
@ApiOperation(value = "修改事件")
@Permission(level = ResourceLevel.ORGANIZATION)
@PutMapping
public ResponseEntity<Event> update(@RequestBody Event event) {
SecurityTokenHelper.validToken(event, false);
return Results.success(eventService.update(event));
}
@ApiOperation(value = "批量删除事件")
@ApiImplicitParams({@ApiImplicitParam(name = "eventId", value = "事件ID集合", paramType = "query"),})
@Permission(level = ResourceLevel.ORGANIZATION)
@DeleteMapping
public ResponseEntity batchRemove(@RequestBody List<Event> events) {
SecurityTokenHelper.validToken(events, false);
Long[] eventIds = events.stream().map(Event::getEventId).collect(Collectors.toList())
.toArray(ArrayUtils.EMPTY_LONG_OBJECT_ARRAY);
eventService.batchRemove(eventIds);
return Results.success();
}
@ApiOperation(value = "查询事件规则")
@ApiImplicitParams({@ApiImplicitParam(name = "eventId", value = "事件ID", paramType = "query"),
@ApiImplicitParam(name = "eventRuleId", value = "事件规则ID", paramType = "query"),})
@Permission(level = ResourceLevel.ORGANIZATION)
@GetMapping("/{eventId}/rules/{eventRuleId}")
public ResponseEntity<EventRule> getEventRule(@PathVariable Long eventId, @PathVariable Long eventRuleId) {
EventRule eventRule = new EventRule();
eventRule.setEventId(eventId);
eventRule.setEventRuleId(eventRuleId);
return Results.success(eventRuleRepository.get(eventRule));
}
@ApiOperation(value = "创建事件规则")
@Permission(level = ResourceLevel.ORGANIZATION)
@PostMapping("/{eventId}/rules")
public ResponseEntity<EventRule> createEventRule(@PathVariable Long eventId, @RequestBody EventRule eventRule) {
return Results.success(eventService.createEventRule(eventId, eventRule));
}
@ApiOperation(value = "修改事件规则")
@Permission(level = ResourceLevel.ORGANIZATION)
@PutMapping("/{eventId}/rules")
public ResponseEntity<EventRule> updateEventRule(@PathVariable Long eventId, @RequestBody EventRule eventRule) {
SecurityTokenHelper.validToken(eventRule);
return Results.success(eventService.updateEventRule(eventId, eventRule));
}
@ApiOperation(value = "批量删除事件规则")
@Permission(level = ResourceLevel.ORGANIZATION)
@DeleteMapping("/{eventId}/rules")
public ResponseEntity batchRemoveEventRule(@PathVariable Long eventId, @RequestBody List<EventRule> eventRules) {
SecurityTokenHelper.validToken(eventRules);
eventService.batchRemoveEventRule(eventId, eventRules);
return Results.success();
}
@ApiOperation(value = "导出事件规则")
@Permission(level = ResourceLevel.ORGANIZATION)
@GetMapping("/export")
@ExcelExport(Event.class)
public ResponseEntity export(Event event, ExportParam exportParam, HttpServletResponse response,
PageRequest pageRequest) {
return Results.success(eventRepository.export(event, exportParam, pageRequest));
}
}
5.Mapper
- 使用实体做数据映射
- 开发对应的Mapper.xml,单表增删改一般不需要Mapper.xml文件.
package org.hzero.platform.infra.mapper;
import ...;
/**
* 事件Mapper
*
* @author jiangzhou.bo@hand-china.com 2018/06/11 17:01
*/
public interface EventMapper extends BaseMapper<Event> {
/**
* 查询事件列表
*
* @param event Event
* @return List<Event>
*/
List<Event> selectEvent(Event event);
}
6.资源库实现
package org.hzero.platform.infra.repository.impl;
import ...;
/**
* 事件 资源库实现
*
* @author jiangzhou.bo@hand-china.com 2018/06/11 21:11
*/
@Component
public class EventRepositoryImpl extends BaseRepositoryImpl<Event> implements EventRepository {
@Autowired
private EventRuleRepository eventRuleRepository;
@Autowired
private EventMapper eventMapper;
@Autowired
private EventRuleMapper eventRuleMapper;
@Autowired
private RedisHelper redisHelper;
@Autowired
private ObjectMapper objectMapper;
@Override
public Page<Event> page(Event event, int page, int size) {
return PageHelper.doPage(page, size, () -> eventMapper.selectEvent(event));
}
@Override
public Event get(Long eventId) {
Event dto = new Event();
dto.setEventId(eventId);
List<Event> events = eventMapper.selectEvent(dto);
if (CollectionUtils.isNotEmpty(events)) {
dto = events.get(0);
EventRule eventRule = new EventRule();
eventRule.setEventId(eventId);
dto.setRuleList(eventRuleMapper.selectEventRule(eventRule));
return dto;
}
return null;
}
@Override
public void remove(Long eventId) {
// 删除事件规则
eventRuleRepository.removeByEventId(eventId);
// 删除事件
this.deleteByPrimaryKey(eventId);
}
@Override
public List<Event> export(Event event, ExportParam exportParam, PageRequest pageRequest) {
List<Event> list = PageHelper.doPageAndSort(pageRequest, () -> eventMapper.selectEvent(event));
if (exportParam.getSelection().contains(Event.EVENT_EVENT_RULE_LIST)) {
EventRule eventRule = new EventRule();
for (Event dto : list) {
eventRule.setEventId(dto.getEventId());
dto.setRuleList(eventRuleMapper.selectEventRule(eventRule));
}
}
return list;
}
@Override
public void refreshCache(Event event) {
clearCache(event);
if (BaseConstants.Flag.YES.equals(event.getEnabledFlag())) {
List<EventRule> ruleList = eventRuleRepository.select(Event.EVENT_ID, event.getEventId());
if (CollectionUtils.isEmpty(ruleList)) {
return;
}
List<EventRuleVO> voList = createEventRuleVO(ruleList);
voList.forEach(vo -> {
try {
redisHelper.lstRightPush(FndConstants.CacheKey.EVENT_KEY + ":" + event.getEventCode(),
objectMapper.writeValueAsString(vo));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
});
}
}
@Override
public void clearCache(Event event) {
String key = FndConstants.CacheKey.EVENT_KEY + ":" + event.getEventCode();
redisHelper.delKey(key);
}
}
7.应用服务实现
package org.hzero.platform.app.service.impl;
import ...;
/**
* 事件应用服务默认实现
*
* @author jiangzhou.bo@hand-china.com 2018/06/11 16:54
*/
@SuppressWarnings("rawtypes")
@Service
public class EventServiceImpl implements EventService {
@Autowired
private EventRepository eventRepository;
@Autowired
private EventRuleRepository eventRuleRepository;
@Autowired
private RedisHelper redisHelper;
@Autowired
private ObjectMapper objectMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Event create(Event event) {
eventRepository.insertSelective(event);
eventRepository.refreshCache(event);
return event;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Event update(Event event) {
Assert.notNull(event.getEventId(), BaseConstants.ErrorCode.DATA_INVALID);
Event entity = eventRepository.selectByPrimaryKey(event.getEventId());
Assert.notNull(entity, BaseConstants.ErrorCode.DATA_NOT_EXISTS);
entity.equalsEventCode(event);
eventRepository.updateByPrimaryKeySelective(event);
eventRepository.refreshCache(event);
return event;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void remove(Long eventId) {
Event event = eventRepository.selectByPrimaryKey(eventId);
Assert.notNull(event, BaseConstants.ErrorCode.DATA_NOT_EXISTS);
// 删除事件
eventRepository.remove(eventId);
// 清除缓存
eventRepository.clearCache(event);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void batchRemove(Long[] eventIds) {
if (ArrayUtils.isNotEmpty(eventIds)) {
for (int i = 0; i < eventIds.length; i++) {
remove(eventIds[i]);
}
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public EventRule createEventRule(Long eventId, EventRule eventRule) {
Event event = eventRepository.selectByPrimaryKey(eventId);
Assert.notNull(event, BaseConstants.ErrorCode.DATA_NOT_EXISTS);
eventRule.setEventId(eventId);
eventRuleRepository.insertSelective(eventRule);
eventRepository.refreshCache(event);
return eventRule;
}
@Override
@Transactional(rollbackFor = Exception.class)
public EventRule updateEventRule(Long eventId, EventRule eventRule) {
Event event = eventRepository.selectByPrimaryKey(eventId);
Assert.notNull(event, BaseConstants.ErrorCode.DATA_NOT_EXISTS);
eventRule.setEventId(eventId);
eventRuleRepository.updateByPrimaryKeySelective(eventRule);
eventRepository.refreshCache(event);
return eventRule;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void removeEventRule(Long eventId, Long eventRuleId) {
Event event = eventRepository.selectByPrimaryKey(eventId);
Assert.notNull(event, BaseConstants.ErrorCode.DATA_NOT_EXISTS);
eventRuleRepository.deleteByPrimaryKey(eventRuleId);
eventRepository.refreshCache(event);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void batchRemoveEventRule(Long eventId, Long[] eventRuleIds) {
Event event = eventRepository.selectByPrimaryKey(eventId);
Assert.notNull(event, BaseConstants.ErrorCode.DATA_NOT_EXISTS);
// 校验事件ID与事件规则行中事件ID是否一致
eventRules.forEach(eventRule -> {
Assert.isTrue(eventRule.getEventId() == eventId, HpfmMsgCodeConstants.ERROR_EVENT_NOT_MATCH);
});
eventRuleRepository.batchDelete(eventRules);
eventRepository.refreshCache(event);
}
}
三层架构
DDD架构
-
四层架构
-
六边形架构(端口与适配器)
-
SOA面向服务架构
-
一个抽象的业务领域
-
领域之间的交互