应用分层
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面向服务架构 
  
- 
一个抽象的业务领域 
  
- 
领域之间的交互 
 