DDD及CQRS模式的落地實現

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);
        }
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章