DDD及CQRS模式的落地实现

DDD基本概念

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 <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(这里采用了DIP,只申明接口,解耦了基础设施层):

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

}

6、@Repository(利用NoSQL缓存了查询结果)

@Repository
@Slf4j
public class HardwareSolutionRepository extends DomainRepository<HardwareSolution,Long> {
    private final IHardwareSolutionDAO hardwareSolutionDAO;
    private final HardwareSolutionDomainConverter hardwareSolutionDomainConverter;
    private final LokiLoadingCache<Long, HardwareSolutionDO> solutionIdLoadingCache;
    private final LokiLoadingCache<String, HardwareSolutionDO> 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<HardwareSolution> findOneById(Long id) {
        return solutionIdLoadingCache.get(id)
                .map(hardwareSolutionDomainConverter::toEntity)
                .map(this::autowireEntity);
    }

    public Optional<HardwareSolution> 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<HardwareSolution> queryBy(HardwareSolutionDO queryDO, PageRowBounds pageRowBounds) {
        List<HardwareSolutionDO> hardwareSolutionDOs = hardwareSolutionDAO.queryBy(queryDO, pageRowBounds);
        return hardwareSolutionDOs.stream()
                .map(hardwareSolutionDomainConverter::toEntity)
                .map(this::autowireEntity)
                .collect(Collectors.toList());
    }


}

LokiLoadingCache实现:

@Slf4j
public class LokiLoadingCache<K, T> {
    private final String cacheName;
    private final LokiClient<String, T> lokiClient;
    private final Function<K, T> loadingFunction;
    private final Duration cacheDuration;

    public LokiLoadingCache(String cacheName,
                            LokiClient<String, T> lokiClient,
                            Function<K, T> loadingFunction,
                            Duration cacheDuration) {
        this.cacheName = cacheName;
        this.lokiClient = lokiClient;
        this.loadingFunction = loadingFunction;
        this.cacheDuration = cacheDuration;
    }


    public Optional<T> 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);
        }
    }
}

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值