1、DDD分层架构:UI层,应用层,领域层以及基础设施层。
2、DDD元素
Entity可以用来代表一个事物
Value Object是用来描述事物的某一方面的特征,所以它是一个无状态的,且是一个没有标识符的对象,这是和Entity的本质区别
Aggregate是一组相关对象的集合,它作为数据修改的基本单元,为数据修改提供了一个边界
repository用来存储聚合,相当于每一个聚合都应该有一个仓库实例
Factory是用来生成聚合的,当生成一个聚合的步骤过于复杂时,可以将其生成过程放在工厂中
Service:与数据驱动种不同的,不会改变状态,只是操作Value Object,且不合适放在Entity中实现,比如两个Entity实体间进行转账操作
3、设计驱动、数据驱动存在什么问题?
1)重用性,为了重用而重用(比如规则写了5套分佣规则,但有部分细节不同);2)每个操作必须要从头梳理,包括业务的流程和数据的流程,即包含了业务流程,也包含数据流程;3)文档和实现代码差异较大;4)业务之间相互穿插,互相影响;
4、在领域驱动模型中什么叫业务?
之前的理解:从传参到落库到返回整个代码;
领域驱动中:流程的流转,数据查询这些不算业务。
5、领域驱动流程
1)需求分析:DomainStoryTelling
Actors:故事场景的参与者;
WorkObjects:功能点具体的物件;
Activities:Actors与WorkObjects之间的关系;
Annotations:上述三者的解释。
2)领域分析:DomainAnalysis
3)Context Map:领域边界保护方式:分清上下游关系,下游提供接口时建立ACL(防腐层),开放主机服务(OHS),发布语言(PL)
4)Domain Design:Bounded Context, Aggregate, Entites, Value Object, Services, DomainEvents(领域事件是用来捕获领域中发生的具有业务价值的一些事情), Factories, Repositories
5)Unified Modling Language
6)COLA Architecture
1、声明Command Bus
Command.java
public class CommandBus {
private final HandlersProvider handlersProvider;
public CommandBus(HandlersProvider handlersProvider) {
this.handlersProvider = handlersProvider;
}
public Object dispatch(T command) throws ArthasException {
CommandHandler 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, 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 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 handler = beanFactory.getBean(beanName, CommandHandler.class);
return handler;
}
// 获取XXXHandler<>中传入的传入的XXXcommand的class
private Class> getHandledCommandType(Class> clazz) {
// getGenericInterfaces获取由此对象表示的类或接口直接实现的接口的Type,例如: Collection、 List中的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的Map
if (expectedRawType.equals(parametrized.getRawType())) {
return parametrized;
}
}
}
return null;
}
}
2、Handler父类
public abstract class AbstractCommandHandler implements CommandHandler {
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 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 commandContext) {
return false;
}
protected abstract Object execute(C command) throws ArthasException;
protected void sendUserAuditLog(CommandContext commandContext) {
}
}
3、ddd
AggregateRoot
@Component
@Scope("prototype")
public abstract class AggregateRoot extends Entity {
private static final long serialVersionUID = 1L;
}
DomainFactory:
public abstract class DomainFactory {
@Resource
protected AutowireCapableBeanFactory spring;
// 执行注入
protected T autowireEntity(T t) {
spring.autowireBean(t);
return t;
}
public void setSpring(AutowireCapableBeanFactory spring) {
this.spring = spring;
}
}
DomainRepository(这里采用了DIP,只申明接口,解耦了基础设施层):
public abstract class DomainRepository {
@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 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 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 {
@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 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 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 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);
}
}
}
6、@Repository(利用NoSQL缓存了查询结果)
@Repository
@Slf4j
public class HardwareSolutionRepository extends DomainRepository {
private final IHardwareSolutionDAO hardwareSolutionDAO;
private final HardwareSolutionDomainConverter hardwareSolutionDomainConverter;
private final LokiLoadingCache solutionIdLoadingCache;
private final LokiLoadingCache solutionCodeLoadingCache;
public HardwareSolutionRepository(LokiClient lokiClient,
IHardwareSolutionDAO hardwareSolutionDAO,
HardwareSolutionDomainConverter hardwareSolutionDomainConverter) {
this.hardwareSolutionDAO = hardwareSolutionDAO;
this.hardwareSolutionDomainConverter = hardwareSolutionDomainConverter;
solutionIdLoadingCache = new LokiLoadingCache<>(CACHE_NAME_SOLUTION_ID,
lokiClient,
hardwareSolutionDAO::getById,
Duration.ofMinutes(1));
solutionCodeLoadingCache = new LokiLoadingCache<>(CACHE_NAME_SOLUTION_CODE,
lokiClient,
hardwareSolutionDAO::selectOneByCode,
Duration.ofMinutes(1));
}
@Override
public Optional findOneById(Long id) {
return solutionIdLoadingCache.get(id)
.map(hardwareSolutionDomainConverter::toEntity)
.map(this::autowireEntity);
}
public Optional findOneByCode(String solutionCode) {
return solutionCodeLoadingCache.get(solutionCode)
.map(hardwareSolutionDomainConverter::toEntity)
.map(this::autowireEntity);
}
@Override
public boolean exists(HardwareSolution hardwareSolution) {
return solutionCodeLoadingCache.get(hardwareSolution.getCode()).isPresent();
}
@Override
protected void add(HardwareSolution hardwareSolution) {
HardwareSolutionDO hardwareSolutionDO = hardwareSolutionDomainConverter.toDO(hardwareSolution);
hardwareSolutionDAO.add(hardwareSolutionDO);
hardwareSolutionDomainConverter.update(hardwareSolutionDO, hardwareSolution);
}
@Override
protected void update(HardwareSolution hardwareSolution) {
HardwareSolutionDO hardwareSolutionDO = hardwareSolutionDomainConverter.toDO(hardwareSolution);
hardwareSolutionDAO.update(hardwareSolutionDO);
solutionCodeLoadingCache.delete(hardwareSolution.getCode());
solutionIdLoadingCache.delete(hardwareSolution.getId());
}
public List queryBy(HardwareSolutionDO queryDO, PageRowBounds pageRowBounds) {
List hardwareSolutionDOs = hardwareSolutionDAO.queryBy(queryDO, pageRowBounds);
return hardwareSolutionDOs.stream()
.map(hardwareSolutionDomainConverter::toEntity)
.map(this::autowireEntity)
.collect(Collectors.toList());
}
}
LokiLoadingCache实现:
@Slf4j
public class LokiLoadingCache {
private final String cacheName;
private final LokiClient lokiClient;
private final Function loadingFunction;
private final Duration cacheDuration;
public LokiLoadingCache(String cacheName,
LokiClient lokiClient,
Function loadingFunction,
Duration cacheDuration) {
this.cacheName = cacheName;
this.lokiClient = lokiClient;
this.loadingFunction = loadingFunction;
this.cacheDuration = cacheDuration;
}
public Optional get(K key) {
String cacheFullKey = Joiner.on(CACHE_KEY_DELIMITER).join(cacheName, key);
T dataFromCache;
try {
dataFromCache = lokiClient.opsForValue().get(cacheFullKey);
if (dataFromCache != null) {
return Optional.of(dataFromCache);
}
} catch (Exception e) {
log.error("exception to get data from cache for key:[{}]", cacheFullKey, e);
T data = loadingFunction.apply(key);
return Optional.ofNullable(data);
}
try {
lokiClient.opsForValue().trySimpleLock(cacheFullKey, 200, 1000);
dataFromCache = lokiClient.opsForValue().get(cacheFullKey);
if (dataFromCache != null) {
return Optional.of(dataFromCache);
}
T data = loadingFunction.apply(key);
if (data != null) {
try {
lokiClient.opsForValue().set(cacheFullKey, data, cacheDuration);
} catch (Exception exception) {
log.error("exception to put date into cache for key:[{}]", cacheFullKey, exception);
}
}
return Optional.ofNullable(data);
} finally {
try {
lokiClient.opsForValue().releaseSimpleLock(cacheFullKey);
} catch (Exception exception) {
log.error("exception to release lock for key:[{}]", cacheFullKey, exception);
}
}
}
public void delete(K key) {
String cacheFullKey = Joiner.on(CACHE_KEY_DELIMITER).join(cacheName, key);
try {
lokiClient.delete(cacheFullKey);
} catch (Exception exception) {
log.error("exception to clear key:[{}]", cacheFullKey, exception);
}
}
}