DDD基本概念
1、DDD分層架構:UI層,應用層,領域層以及基礎設施層。
2、DDD元素
Entity可以用來代表一個事物
Value Object是用來描述事物的某一方面的特徵,所以它是一個無狀態的,且是一個沒有標識符的對象,這是和Entity的本質區別
Aggregate是一組相關對象的集合,它作爲數據修改的基本單元,爲數據修改提供了一個邊界
repository用來存儲聚合,相當於每一個聚合都應該有一個倉庫實例
Factory是用來生成聚合的,當生成一個聚合的步驟過於複雜時,可以將其生成過程放在工廠中
實現
1、聲明Command Bus
Command.java
public class CommandBus {
private final HandlersProvider handlersProvider;
public CommandBus(HandlersProvider handlersProvider) {
this.handlersProvider = handlersProvider;
}
public <T extends Command> Object dispatch(T command) throws ArthasException {
CommandHandler<Command> handler = handlersProvider.getHandler(command);
if (handler == null) {
throw new RuntimeException("command handler not found. command class is " + command.getClass());
}
return handler.handle(command);
}
}
SpringHandlerProvider.java implenments HandlersProvider.java
@Component
public class SpringHandlerProvider implements HandlersProvider {
private final Map<Class<?>, String> handlerRepository = new HashMap<>();
//ConfigurableListableBeanFactory 提供bean definition的解析,註冊功能,再對單例來個預加載(解決循環依賴問題).
@Resource
private ConfigurableListableBeanFactory beanFactory;
// 初始化,建立Handler與其Command的映射
@PostConstruct
public void init() {
// getBeanNamesForType返回對於指定類型Bean(包括子類)的所有名字
String[] commandHandlersNames = beanFactory.getBeanNamesForType(CommandHandler.class);
for (String beanName : commandHandlersNames) {
BeanDefinition commandHandler = beanFactory.getBeanDefinition(beanName);
try {
Class<?> handlerClass = Class.forName(commandHandler.getBeanClassName());
Class<?> commandType = getHandledCommandType(handlerClass);
handlerRepository.put(commandType, beanName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
// 根據Command獲取其對應的Handler
@Override
public CommandHandler<Command> getHandler(Command command) {
String beanName = handlerRepository.get(command.getClass());
if (beanName == null) {
throw new RuntimeException("command handler not found. CommandAnnotation class is " + command.getClass());
}
CommandHandler<Command> handler = beanFactory.getBean(beanName, CommandHandler.class);
return handler;
}
// 獲取XXXHandler<>中傳入的傳入的XXXcommand的class
private Class<?> getHandledCommandType(Class<?> clazz) {
// getGenericInterfaces獲取由此對象表示的類或接口直接實現的接口的Type,例如: Collection<String>、 List<Coupon>中的String和Coupon
Type[] genericInterfaces = clazz.getGenericInterfaces();
// getGenericSuperclass返回直接繼承的父類(包含泛型參數)
Type genericSuperClass = clazz.getGenericSuperclass();
ParameterizedType type = findByRawType(genericInterfaces, CommandHandler.class);
// 沒找到說明子類,直接使用繼承的父類
if (type == null) {
type = (ParameterizedType) genericSuperClass;
}
// 返回“泛型實例”中<>裏面的“泛型變量”(也叫類型參數)的值,即從父類中獲取到傳入的XXXcomand
return (Class<?>) type.getActualTypeArguments()[0];
}
// 找到implements的是commandHandler的那個type
private ParameterizedType findByRawType(Type[] genericInterfaces, Class<?> expectedRawType) {
for (Type type : genericInterfaces) {
if (type instanceof ParameterizedType) {
ParameterizedType parametrized = (ParameterizedType) type;
// getRawType返回最外層<>前面那個類型,即Map<K ,V>的Map
if (expectedRawType.equals(parametrized.getRawType())) {
return parametrized;
}
}
}
return null;
}
}
2、Handler父類
public abstract class AbstractCommandHandler<C extends Command> implements CommandHandler<C> {
private final CommandValidator paramValidator = new ParamValidator();
@Resource
protected AsyncEventBus asyncEventBus;
@Override
public Object handle(C command) throws ArthasException {
try {
paramValidate(command);
} catch (IllegalArgumentException illegalArgumentException) {
log.warn("illegalArgumentException for command:[{}]", command, illegalArgumentException);
throw new ArthasBizException(ErrorCodeEnum.PARAM_ERROR.getCode(), illegalArgumentException.getMessage());
} catch (BaseException baseException) {
log.warn("baseException for command:[{}]", command, baseException);
throw baseException;
} catch (Throwable throwable) {
log.warn("invalid argument exception for command:[{}]", command, throwable);
throw new ArthasBizException(ErrorCodeEnum.PARAM_ERROR);
}
CommandContext<C> commandContext = new CommandContext(command);
boolean idempotentCheckHit = idempotentCheck(commandContext);
if (idempotentCheckHit) {
return commandContext.getContextParam(CONTEXT_IDEMPOTENT_RETURN);
}
try {
bizValidate(command);
} catch (IllegalArgumentException illegalArgumentException) {
log.warn("illegalArgumentException for command:[{}]", command, illegalArgumentException);
throw new ArthasBizException(ErrorCodeEnum.PARAM_ERROR.getCode(), illegalArgumentException.getMessage());
} catch (BaseException baseException) {
log.warn("baseException for command:[{}]", command, baseException);
throw baseException;
} catch (Throwable throwable) {
log.warn("invalid argument exception for command:[{}]", command, throwable);
throw new ArthasBizException(ErrorCodeEnum.PARAM_ERROR.getCode(), throwable);
}
Object result = null;
result = execute(command);
if (command instanceof UserCommand) {
commandContext.setContextParam(CONTEXT_PARAM_RESULT, result);
sendUserAuditLog(commandContext);
}
return result;
}
/**
* 參數校驗
* @param command
*/
protected void paramValidate(C command) {
paramValidator.validate(command);
}
/**
* 業務規則校驗
* @param command
*/
protected void bizValidate(C command) {
}
/**
* 冪等處理
* @param commandContext
* @return 是否出發冪等處理
*/
protected boolean idempotentCheck(CommandContext<C> commandContext) {
return false;
}
protected abstract Object execute(C command) throws ArthasException;
protected void sendUserAuditLog(CommandContext<C> commandContext) {
}
}
3、ddd
AggregateRoot
@Component
@Scope("prototype")
public abstract class AggregateRoot<T> extends Entity<T> {
private static final long serialVersionUID = 1L;
}
DomainFactory:
public abstract class DomainFactory {
@Resource
protected AutowireCapableBeanFactory spring;
// 執行注入
protected <T extends Entity> T autowireEntity(T t) {
spring.autowireBean(t);
return t;
}
public void setSpring(AutowireCapableBeanFactory spring) {
this.spring = spring;
}
}
DomainRepository:
public abstract class DomainRepository<T extends Entity> {
@Resource
protected AutowireCapableBeanFactory spring;
protected T autowireEntity(T t) {
spring.autowireBean(t);
return t;
}
public void setSpring(AutowireCapableBeanFactory spring) {
this.spring = spring;
}
/**
* 根據id查找實體
*
* @param id
* @return
*/
public abstract Optional<T> findOneById(Long id);
/**
* 是否存在
*
* @param t
* @return
*/
public abstract boolean exists(T t);
/**
* 持久化
*
* @param t
*/
public void persist(T t) {
if (t.isPersisted()) {
update(t);
} else {
add(t);
t.markAsPersisted();
this.autowireEntity(t);
}
}
/**
* 新增
*
* @param t
*/
protected abstract void add(T t);
/**
* 更新
*
* @param t
*/
protected abstract void update(T t);
}
Entity:
@Component
@Scope("prototype")
public abstract class Entity<T> implements Serializable {
private static final long serialVersionUID = 1L;
protected T id;
protected Instant gmtCreate;
protected Instant gmtModified;
public enum EntityStatus {
NEW, PERSISTED, ARCHIVE
}
private EntityStatus entityStatus = EntityStatus.PERSISTED;
public void markAsRemoved() {
entityStatus = EntityStatus.ARCHIVE;
}
public void markAsNew() {
entityStatus = EntityStatus.NEW;
}
public void markAsPersisted() {
entityStatus = EntityStatus.PERSISTED;
}
public boolean isRemoved() {
return entityStatus == EntityStatus.ARCHIVE;
}
public boolean isPersisted() {
return entityStatus == EntityStatus.PERSISTED;
}
public EntityStatus getEntityStatus() {
return entityStatus;
}
public void setEntityStatus(EntityStatus entityStatus) {
this.entityStatus = entityStatus;
}
public T getId() {
return id;
}
public void setId(T id) {
this.id = id;
}
public Instant getGmtCreate() {
return gmtCreate;
}
public void setGmtCreate(Instant gmtCreate) {
this.gmtCreate = gmtCreate;
}
public Instant getGmtModified() {
return gmtModified;
}
public void setGmtModified(Instant gmtModified) {
this.gmtModified = gmtModified;
}
public abstract void persist();
}
ValueObject:
public abstract class ValueObject implements Serializable {
private static final long serialVersionUID = 1L;
@Override
public abstract boolean equals(Object o);
@Override
public abstract int hashCode();
}
4、一個子類Handler
@CommandHandler
@Slf4j
public class HardwareSolutionAddCmdHandler extends AbstractDistributeLockedCommandHandler<HardwareSolutionAddCmd> {
@Resource
private HardwareSolutionFactory hardwareSolutionFactory;
@Resource
private HardwareSolutionRepository hardwareSolutionRepository;
@Resource
private IHardwareSolutionDAO hardwareSolutionDAO;
@Resource
private HardwareSolutionConverter hardwareSolutionConverter;
@Resource
private IotEntityGroupRepository iotEntityGroupRepository;
@Resource
private IMqKafkaProducer mqKafkaProducer;
@Resource
private TransactionTemplate transactionTemplate;
@Resource
private CommandBus commandBus;
@Resource
private ISolutionEventSender solutionEventSender;
// 冪等校驗,此處是通過查詢數據庫比較create的時間來實現
@Override
protected boolean idempotentCheck(CommandContext<HardwareSolutionAddCmd> commandContext) {
HardwareSolutionAddCmd hardwareSolutionUserAddCmd = commandContext.getCommand();
long gmtCreate = Instant.now().minusSeconds(10).toEpochMilli();
HardwareSolutionDO hardwareSolutionDO = hardwareSolutionConverter.toDO(hardwareSolutionUserAddCmd);
hardwareSolutionDO.setState(HardwareSolutionState.CREATED.getValue());
hardwareSolutionDO.setGmtCreate(gmtCreate);
Long solutionId = hardwareSolutionDAO.idempotentCheck(hardwareSolutionDO);
if (solutionId != null) {
commandContext.setContextParam(CONTEXT_IDEMPOTENT_RETURN, solutionId);
return true;
}
return false;
}
// 入參校驗
@Override
protected void bizValidate(HardwareSolutionAddCmd hardwareSolutionUserAddCmd) {
DeviceType deviceType = DeviceType.of(hardwareSolutionUserAddCmd.getDeviceType());
if (deviceType == DeviceType.DEVICE) {
Preconditions.checkArgument(CollectionUtils.isNotEmpty(hardwareSolutionUserAddCmd.getCommunicationTypes()), "通訊能力不能爲空");
}
if (deviceType == DeviceType.GATEWAY) {
Preconditions.checkArgument(CollectionUtils.isNotEmpty(hardwareSolutionUserAddCmd.getUpLinkCommunicationTypes()), "上行通訊能力不能爲空");
Preconditions.checkArgument(CollectionUtils.isNotEmpty(hardwareSolutionUserAddCmd.getDownLinkCommunicationTypes()), "下行通訊能力不能爲空");
}
if (hardwareSolutionRepository.findOneByCode(hardwareSolutionUserAddCmd.getCode()).isPresent()) {
throw new IllegalArgumentException("已存在的硬件方案代碼");
}
iotEntityGroupRepository.findOneById(hardwareSolutionUserAddCmd.getSolutionGroupId())
.orElseThrow(() -> new ArthasBizException(ArthasExceptionCode.SOLUTION_GROUP_ID_NOT_EXIST));
}
// 執行
@Override
protected Object execute(HardwareSolutionAddCmd hardwareSolutionUserAddCmd) throws ArthasException {
HardwareSolution hardwareSolution = createHardwareSolutionAggregate(hardwareSolutionUserAddCmd);
LinkedHashSet<HardwareSolutionCapability> hardwareSolutionCapabilities = hardwareSolutionConverter.toHardwareSolutionCapabilities(hardwareSolutionUserAddCmd);
// 聚合
HardwareSolutionExtra hardwareSolutionExtra = hardwareSolutionFactory.create(hardwareSolution, SolutionType.CUSTOM);
HardwareSolutionAggregate hardwareSolutionAggregate = hardwareSolutionFactory.create(hardwareSolution, hardwareSolutionExtra, hardwareSolutionCapabilities);
// 事務
transactionTemplate.execute(transactionStatus -> {
hardwareSolutionAggregate.persist();
HardwareSolutionAssignToGroupCmd hardwareSolutionAssignToGroupCmd = toHardwareSolutionAssignToGroupCmd(hardwareSolutionAggregate.getId(), hardwareSolutionUserAddCmd);
// 嵌套Command分發
commandBus.dispatch(hardwareSolutionAssignToGroupCmd);
return true;
});
solutionEventSender.sendSolutionEvent(hardwareSolution.getId(), SolutionDomainEventType.SOLUTION_ADDED);
return hardwareSolutionAggregate.getId();
}
@Override
protected void sendUserAuditLog(CommandContext<HardwareSolutionAddCmd> commandContext) {
HardwareSolutionAddCmd hardwareSolutionAddCmd = commandContext.getCommand();
Object result = commandContext.getContextParam(CONTEXT_PARAM_RESULT);
UserAuditEvent userAuditEvent = new UserAuditEvent()
.setBizType("arthas")
.setOperateType(UserOperateType.ADD)
.setModule("hardware_solution")
.setRefKey(hardwareSolutionAddCmd.getCode())
.setCommand(commandContext.getCommand())
.setOperator(hardwareSolutionAddCmd.getOperator())
.setOperateTime(Instant.now())
.setResult(result);
asyncEventBus.post(userAuditEvent);
log.info("sendUserAuditLog:[{}]", userAuditEvent);
}
private HardwareSolution createHardwareSolutionAggregate(HardwareSolutionAddCmd hardwareSolutionUserAddCmd) {
return hardwareSolutionFactory.create(hardwareSolutionUserAddCmd.getCode(),
hardwareSolutionUserAddCmd.getName(),
hardwareSolutionUserAddCmd.getDeviceType(),
hardwareSolutionUserAddCmd.getTopCategoryCode(),
hardwareSolutionUserAddCmd.getSecondCategoryCode(),
hardwareSolutionUserAddCmd.getOperator());
}
private HardwareSolutionAssignToGroupCmd toHardwareSolutionAssignToGroupCmd(Long solutionId, HardwareSolutionAddCmd hardwareSolutionUserAddCmd) {
HardwareSolutionAssignToGroupCmd hardwareSolutionAssignToGroupCmd = new HardwareSolutionAssignToGroupCmd();
hardwareSolutionAssignToGroupCmd.setHardwareSolutionId(solutionId)
.setGroupId(hardwareSolutionUserAddCmd.getSolutionGroupId());
hardwareSolutionAssignToGroupCmd.setTenantId(TENANT_TUYA)
.setOperator(hardwareSolutionUserAddCmd.getOperator());
return hardwareSolutionAssignToGroupCmd;
}
}
5、@CommandHandler
/**
* 被{@link CommandHandler}註解的方法,通過設置{@link #value()}作爲鎖,在被調用時會被同步,可以解決分佈式併發系列的問題。
*
*/
@Component
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CommandHandler {
String value() default "";
}
@Aspect
@Slf4j
public class CommandHandlerAspect {
@Pointcut("within(com.t.a.core.base.command.handler.CommandHandler+) && execution(* handle(..))")
public void commandHandlerPointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
@Around("commandHandlerPointcut()")
public Object process(ProceedingJoinPoint joinPoint) {
log.info("Enter commandhandler: {}.{}() with argument[s] = {}", joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs()));
try {
return joinPoint.proceed();
} catch (ArthasBizException arthasBizException) {
log.info("arthasBizException for commandhandler: {}.{}() with argument[s] = {}", joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs()), arthasBizException);
throw arthasBizException;
} catch (BaseException baseException) {
log.error("arthasException for commandhandler: {}.{}() with argument[s] = {}", joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs()), baseException);
throw new ArthasBizException(baseException.getCode(), baseException.getErrorMsg());
} catch (Throwable throwable) {
log.error("unknown exception for commandhandler: {}.{}() with argument[s] = {}", joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs()), throwable);
throw new RuntimeException(throwable);
}
}
}