前语

最近接手了一个项目,历史悠久,技能债欠的比较多,每次迭代上线心惊胆战,本着边换轮子边前进的准则,对体系进行改造晋级。同时由于团队里边新人较多,事务逻辑还需求学习,最好在改造的同时沉积单元测试的主要内容一些事务范畴常识。DDD的思维刚好对现在的状况是一种比较好的解法,那么首要就缓存视频合并app从运用架构开端了。

运用架构的目的

让团队成APP员能有一个一致的开发标准,下降沟通本钱,进步功率和代码质量。让体系安全、稳定、快速迭代。

下面经过一个事例剖析来引出咱们的运用架构的规划。

一、事例剖析

功用

头绪公海的申领

功用代码段

@PostMapping("/publicsea/apply")
@Validated
publicCommonResultapply(@RequestBodyBasePcRequest<PublicSeaApplyRequest>request){
    BaseAppRequest<PublicSeaApplyRequest> baseAppRequest = new BaseAppRequest<>();
    baseAppRequest.setData(request.getData());
    baseAppRequest.setUserInfoDetailVo(request.getUserInfoDetailVo());
    applyController.apply(baseAppRequest);
    ThreadCache.set(CommonConstants.CURRENT_THREAD_USER, request.getUserInfoDetailVo());
    ApplyResponse applyResponse = businessFlowUpdateService.apply(baseAppRequest);
    if (applyResponse.getKeys().size() > 0) {
        return new CommonResult<>(ResultCode.SUCCESS.getCode(), applyResponse.getMessage(), applyResponse.getKeys());
    }
    return new CommonResult<>(ResultCode.FAILED.getCode(), applyResponse.getMessage(), applyResponse.getKeys());
}
@Override
public ApplyResponse apply(BaseAppRequest<PublicSeaApplyRequest> request) {
    Date now = new Date();
    ApplyResponse applyResponse = new ApplyResponse();
    PublicSeaApplyRequest applyRequest = request.getData();
    //获取当时登录人信息
    UserInfoDetailVo userInfoDetailVo = request.getUserInfoDetailVo();
    //根据stage履行不同的逻辑
    //所选阶段不存在 抛出反常
    if (!ObjectEnum.isExist(applyRequest.getStage())) {
        throw new BusinessRunTimeException(BusinessRuntimeExceptionEnum.PHASE_NOT_EXIST.getCode(), BusinessRuntimeExceptionEnum.PHASE_NOT_EXIST.getMessage());
    }
    List<String> ids = applyRequest.getKeys();
    final int totalCount = applyRequest.getKeys().size();
    CheckPublicApplyBo checkPublicApplyBo = null;
    if (ObjectEnum.CLUE.getApiName().equals(applyRequest.getStage()) || ObjectEnum.CUSTOMER.getApiName().equals(applyRequest.getStage())) {
        checkPublicApplyBo = checkPublicSeaAuth(userInfoDetailVo, applyRequest.getStage(), applyRequest.getKeys());
        ids = checkPublicApplyBo.getKeys();
        if (CollectionUtils.isEmpty(ids)) {
            throw new BusinessParamCheckingException(BusinessParamCheckingExceptionEnum.PUBLIC_SEA_NOT_EXIST.getCode(), BusinessParamCheckingExceptionEnum.PUBLIC_SEA_NOT_EXIST.getMessage());
        }
    }
    //保有量判别
    HoldSize holdSize = resourceHoldUtil.getResourceHold(userInfoDetailVo.getPid(), applyRequest.getStage(), userInfoDetailVo.getUserWid(), ids.size());
    int size = holdSize.getHold();
    if (size <= 0) {
        //代表保有量已经满了
        applyResponse.setMessage("收取失利" + totalCount + "条[超出保有量上限,最多还能收取0条]");
        applyResponse.setKeys(new ArrayList<>());
        applyResponse.setReturnCode(BusinessRuntimeExceptionEnum.CAPACITY_IS_FULL.getCode());
        return applyResponse;
    }
    if (size < ids.size()) {
        applyResponse.setMessage("收取失利" + totalCount + "条[超出保有量上限,最多还能收取" + size + "条]");
        applyResponse.setKeys(new ArrayList<>());
        applyResponse.setReturnCode(BusinessRuntimeExceptionEnum.CAPACITY_IS_FULL.getCode());
        return applyResponse;
    }
    //校验保有量高档规矩
    HoldValidateResultBo resultBo = resourceHoldUtil.validateHoldSeniorRuleByKeys(
            userInfoDetailVo.getPid(), applyRequest.getStage(), holdSize, ids);
    if (resultBo.isOverHold()) {
        applyResponse.setMessage("收取失利" + totalCount + "条" + resultBo.getOverHoldTips());
        applyResponse.setKeys(new ArrayList<>());
        applyResponse.setReturnCode(BusinessRuntimeExceptionEnum.CAPACITY_IS_FULL.getCode());
        return applyResponse;
    }
    String applyLimitMsg = "";
    if (null != checkPublicApplyBo) {
        List<ApplyLimitCheckBo> applyLimitCheckBos = new ArrayList<>();
        if (ObjectEnum.CLUE.getApiName().equals(applyRequest.getStage())) {
            for (TScrmClue clue : checkPublicApplyBo.getClues()) {
                if (clue.getDeadline().compareTo(CustomizeDateUtils.parseDateStr(SystemConstant.publicDeadline, CustomizeDateUtils.parsePatterns[1])) == 0) {
                    //只针对回收资源
                    continue;
                }
                ApplyLimitCheckBo applyLimitCheckBo = new ApplyLimitCheckBo();
                applyLimitCheckBo.setObjectKey(clue.getClueKey());
                applyLimitCheckBo.setFormerOwner(clue.getFormerOwner());
                applyLimitCheckBo.setOwner(userInfoDetailVo.getUserWid());
                applyLimitCheckBo.setPoolTime(clue.getPoolingTime());
                applyLimitCheckBo.setPublicseaId(clue.getPublicSeaId());
                applyLimitCheckBos.add(applyLimitCheckBo);
            }
        }
        if (ObjectEnum.CUSTOMER.getApiName().equals(applyRequest.getStage())) {
            for (TScrmCustomer customer : checkPublicApplyBo.getCustomers()) {
                if (customer.getDeadline().compareTo(CustomizeDateUtils.parseDateStr(SystemConstant.publicDeadline, CustomizeDateUtils.parsePatterns[1])) == 0) {
                    continue;
                }
                ApplyLimitCheckBo applyLimitCheckBo = new ApplyLimitCheckBo();
                applyLimitCheckBo.setPublicseaId(customer.getPublicSeaId());
                applyLimitCheckBo.setPoolTime(customer.getPoolingTime());
                applyLimitCheckBo.setOwner(userInfoDetailVo.getUserWid());
                applyLimitCheckBo.setFormerOwner(customer.getFormerOwner());
                applyLimitCheckBo.setObjectKey(customer.getCustomerKey());
                applyLimitCheckBos.add(applyLimitCheckBo);
            }
        }
        Map<String, String> map = tScrmPublicseaDropRuleService.batchCheckApplyLimit(userInfoDetailVo.getPid(), applyRequest.getStage(), applyLimitCheckBos);
        if (MapUtil.isNotEmpty(map)) {
            applyLimitMsg = map.values().iterator().next();
            ids.removeAll(map.keySet());
        }
        if (CollectionUtils.isEmpty(ids)) {
            applyResponse.setKeys(Collections.emptyList());
            applyResponse.setMessage(String.format(SystemConstant.OPERATION_FAIL, 0, totalCount, map.values().iterator().next()));
            return applyResponse;
        }
    }
    CommonBatchUpdateBo batchUpdateBo = new CommonBatchUpdateBo();
    FieldConditionBo conditionBo = new FieldConditionBo();
    List<FieldConditionBo> fieldConditionBos = new ArrayList<>();
    /*经过装备读取是否更新最近跟进人和最近跟进时间  头绪和客户读取配*/
    if (ObjectEnum.CLUE.getApiName().equals(request.getData().getStage())) {
        ThreadCache.set(CommonConstants.FOLLOW_TYPE, BusinessFlowTypeEnum.CLUE_OPENSEACUSTOMER_RECEIVE);
    }
    if (ObjectEnum.CUSTOMER.getApiName().equals(request.getData().getStage())) {
        ThreadCache.set(CommonConstants.FOLLOW_TYPE, BusinessFlowTypeEnum.CUSTOMER_OPENSEACUSTOMER_RECEIVE);
    }
    List<String> columnNames = new ArrayList<>();
    List<Object> columnValues = new ArrayList<>();
    columnNames.add(FieldKeyEnum.OWNER.getFieldKey());
    columnNames.add(FieldKeyEnum.CLUE_CLAIM_TIME.getFieldKey());
    columnValues.add(userInfoDetailVo.getUserWid());
    columnValues.add(now);
    columnNames.add(FieldKeyEnum.LAST_FOLLOW_TIME.getFieldKey());
    columnNames.add(FieldKeyEnum.LAST_FOLLOW_USER_WID.getFieldKey());
    columnValues.add(now);
    columnValues.add(userInfoDetailVo.getUserWid());
    columnNames.add(FieldKeyEnum.UPDATE_USER_WID.getFieldKey());
    columnValues.add(userInfoDetailVo.getUserWid());
    if (applyRequest.getStage().equals(ObjectEnum.CLUE.getApiName())) {
        conditionBo.setApiName(FieldKeyEnum.CLUE_KEY.getFieldKey());
        columnNames.add(FieldKeyEnum.CLUE_FOLLOW_STATUS.getFieldKey());
        columnValues.add(CluefollowStatusEnum.FOLLOW_STATUS_NUCONTACT.getCode());
    } else if (applyRequest.getStage().equals(ObjectEnum.CUSTOMER.getApiName())) {
        conditionBo.setApiName(FieldKeyEnum.CUSTOMER_KEY.getFieldKey());
        columnNames.add(FieldKeyEnum.CUSTOMER_FOLLOW_STATUS.getFieldKey());
        columnValues.add(CustomerFollowStatusEnum.NO_FOLLOW.getCode());
        columnNames.add(FieldKeyEnum.CUSTOMER_GROUP.getFieldKey());
        columnValues.add(Lists.newArrayList());
    } else if (applyRequest.getStage().equals(ObjectEnum.NICHE.getApiName())) {
        throw new BusinessRunTimeException(BusinessRuntimeExceptionEnum.NOT_SUPPORTED.getCode(), BusinessRuntimeExceptionEnum.NOT_SUPPORTED.getMessage());
    } else {
        throw new BusinessRunTimeException(BusinessRuntimeExceptionEnum.NOT_SUPPORTED.getCode(), BusinessRuntimeExceptionEnum.NOT_SUPPORTED.getMessage());
    }
    conditionBo.setCompareEnum(CompareEnum.IN);
    conditionBo.setValueList(ids);
    fieldConditionBos.add(conditionBo);
    batchUpdateBo.setFieldConditionBo(fieldConditionBos);
    batchUpdateBo.setFieldUpdateBo(businessFlowCommon.setColumn(columnNames.toArray(new String[]{}), columnValues.toArray(new Object[]{})));
    List<SyncChangeOwnerBo> syncChangeOwnerBos = new ArrayList<>();
    StateChangeLogBo stateChangeLogBo = new StateChangeLogBo();
    stateChangeLogBo.setCreateUserWid(userInfoDetailVo.getUserWid());
    stateChangeLogBo.setPid(userInfoDetailVo.getPid());
    stateChangeLogBo.setOpContent("收取");
    stateChangeLogBo.setOpType(OpLogTypeEnum.CLAIM.getCode());
    stateChangeLogBo.setOpResult(SystemConstant.BYTE_YES);
    stateChangeLogBo.setStage(applyRequest.getStage());
    stateChangeLogBo.setOpTime(now);
    stateChangeLogBo.setOpUserNo(userInfoDetailVo.getUserWid());
    ids = businessFlowCommon.applyAndAssign(applyRequest.getStage(), userInfoDetailVo, batchUpdateBo, ids, stateChangeLogBo, 0L);
    if (CollectionUtils.isNotEmpty(ids)) {
        ids.forEach(id -> {
            if (applyRequest.getStage().equals(ObjectEnum.CLUE.getApiName())) {
                SyncChangeOwnerBo syncChangeOwnerBo = new SyncChangeOwnerBo();
                syncChangeOwnerBo.setClueKey(id);
                syncChangeOwnerBo.setOperationType(SyncOpTypeEnum.CLAIM.getCode());
                syncChangeOwnerBo.setPid(userInfoDetailVo.getPid());
                syncChangeOwnerBo.setOwner(userInfoDetailVo.getUserWid());
                syncChangeOwnerBos.add(syncChangeOwnerBo);
            }
        });
        if (applyRequest.getStage().equals(ObjectEnum.CUSTOMER.getApiName())) {
            //同步申领商机
            List<String> nicheKeys = businessFlowMapper.getNotWinNicheKeys(ids, userInfoDetailVo.getPid());
            if (!nicheKeys.isEmpty()) {
                CommonBatchUpdateBo commonBatchUpdateBo = new CommonBatchUpdateBo();
                FieldConditionBo fieldConditionBo = new FieldConditionBo();
                List<FieldConditionBo> conditionBos = new ArrayList<>();
                fieldConditionBo.setApiName(FieldKeyEnum.NICHE_KEY.getFieldKey());
                fieldConditionBo.setCompareEnum(CompareEnum.IN);
                fieldConditionBo.setValueList(nicheKeys);
                conditionBos.add(fieldConditionBo);
                String[] nicheStrs = {FieldKeyEnum.OWNER.getFieldKey(), FieldKeyEnum.LAST_UPDATE_USER_WID.getFieldKey(),
                        FieldKeyEnum.LAST_UPDATE_TIME.getFieldKey(), FieldKeyEnum.NICHE_CLAIM_TIME.getFieldKey(),
                        FieldKeyEnum.LAST_FOLLOW_TIME.getFieldKey(), FieldKeyEnum.LAST_FOLLOW_USER_WID.getFieldKey()};
                Object[] nicheO = {userInfoDetailVo.getUserWid(), userInfoDetailVo.getUserWid(), now, now, now, userInfoDetailVo.getUserWid()};
                commonBatchUpdateBo.setFieldConditionBo(conditionBos);
                commonBatchUpdateBo.setFieldUpdateBo(businessFlowCommon.setColumn(nicheStrs, nicheO));
                commonAdapterServiceImpl.commonBatchUpdate(ObjectEnum.NICHE, userInfoDetailVo.getPid(), commonBatchUpdateBo);
            }
        }
    }
    if (ids.size() < totalCount) {
        int failSize = totalCount - ids.size();
        if (StringUtils.isNotBlank(applyLimitMsg)) {
            applyResponse.setMessage(String.format(SystemConstant.OPERATION_FAIL, ids.size(), failSize, applyLimitMsg));
        } else {
            applyResponse.setMessage(String.format(SystemConstant.OPERATION_FAIL, ids.size(), failSize, "数据有改变,改写后重试"));
        }
    } else {
        applyResponse.setMessage("收取成功" + ids.size() + "条");
    }
    applyResponse.setKeys(ids);
    return applyResponse;
}

大约流程图

范畴驱动落地实战

一眼无法看清所依靠的目标及服务。

存在的问题

一段事务代码里包含了参数校验、数据读取、事务核算、数据存储等等逻辑,这种代码款式是通称为“事物脚本”。会有以下几个问题:

可保护性差

例如 TScrmClue 直接映射的是数据库中的表结构,一旦表结构产生改变或许换存储方式,许多代码需求随之改动。

参数校验散落在各个旮旯安全教育手抄报,修正需求通盘考虑。

可扩展性差

事物脚本代码通常在写第一个需求时完成起来比较快,随着事务场景逐步变多,可扩展性会越来越差。代码复用性低,copy 痕迹很常见。

if(客资目标==头绪){
公共逻辑
if(动作==申领){
todo
}
.......
}
........
可测验性差

如上图,假如修正了公共逻辑,基本上需求对接口做掩盖测验,测验工作量大(N*M)。

小结

以上剖析来缓存视频变成本地视频看,在规划的时分违背了以下几个软件规划准则:

单一准则:单一性准则要求一个目标/类应该只有一个改变的原因。可是在这个流程里,代码apple可能会由于任意一个外部依approve靠或核算逻辑的改动而改动。

开闭准则:敞开封闭准则指敞开扩展,可是封闭修单元测试主要的测试技术不包括正。以上代码数据过滤逻辑能够封装在不可变代码里,假如需求增加,能够扩展其完成。

二、重构方案

结合咱们现在appreciate的事务特色及技能条件。运用DDD的思维来整理事务逻辑,进行重构。首要要说明的是:DDD 不是一套结构,而是一种架构思维,在代码落当地面,是一种代码安排方式。

过程:

1、抽取DP

DP概念:Domain Primitive,特定范畴里,拥有精准定义的、可自我验证的、拥有行为的 Value Object。

运用DP的收益:代码遵从了 DRY(一个规矩完成一次) 准则和单一性准则。

收集DP行为,替换数据校验和无状况逻辑。

把上下文对入参校验的逻辑进行抽取:

@Data
public class ApplyParam {
    PublicSeaApplyRequest applyRequest;
    UserInfoDetailVo userInfoDetailVo;
    public ApplyParam(PublicSeaApplyRequest applyRequest,UserInfoDetailVo userInfoDetailVo){
        //所选阶段不存在 抛出反常
        if (!ObjectEnum.isExist(applyRequest.getStage())) {
            throw new BusinessRunTimeException(BusinessRuntimeExceptionEnum.PHASE_NOT_EXIST.getCode(), BusinessRuntimeExceptionEnum.PHASE_NOT_EXIST.getMessage());
        }
        List<String> ids = applyRequest.getKeys();
        if (CollectionUtils.isEmpty(ids)) {
            throw new BusinessParamCheckingException(BusinessParamCheckingExceptionEnum.PUBLIC_SEA_NOT_EXIST.getCode(), BusinessParamCheckingExceptionEnum.PUBLIC_SEA_NOT_EXIST.getMessage());
        }
        this.applyRequest =applyRequest;
        this.userInfoDetailVo = userInfoDetailVo;
    }
}

对公海校验逻辑进行抽取:

@Data
public class BatchCheckApplyLimit {
    List<String> keys;
    String applyLimitMsg;
    HashMap<String, String> map;
    public BatchCheckApplyLimit(List<String> ids, UserInfoDetailVo userInfoDetailVo, List<ClueEntity> list, List<TScrmPublicseaDropRuleDTO> publicSeaDropRule, ObjectStatusDTO dto){
        HashMap<String, String> mapresult = new HashMap<>();
        if(CollectionUtils.isEmpty(list)){
             this.map = mapresult;
        }else {
            List<ApplyLimitCheckBo> applyLimitCheckBos = new ArrayList<>();
            for (ClueEntity clue : list) {
                ApplyLimitCheckBo applyLimitCheckBo = new ApplyLimitCheckBo();
                applyLimitCheckBo.setObjectKey(clue.getClueKey());
                applyLimitCheckBo.setFormerOwner(clue.getFormerOwner());
                applyLimitCheckBo.setOwner(userInfoDetailVo.getUserWid());
                applyLimitCheckBo.setPoolTime(clue.getPoolingTime());
                applyLimitCheckBo.setPublicseaId(clue.getPublicSeaId());
                applyLimitCheckBos.add(applyLimitCheckBo);
            }
            Map<Long, TScrmPublicseaDropRuleDTO> dropRuleMap = publicSeaDropRule.stream().collect(Collectors.toMap(TScrmPublicseaDropRuleDTO::getId, TScrmPublicseaDropRule -> TScrmPublicseaDropRule, (k1, k2) -> k2));
            String anybodyApplyDaysToast = "同一"+dto.getTranslateName()+"%d天内不能被任何人收取";
            String formerOwnerApplyDaysToast = "同一"+dto.getTranslateName()+"%d天内不能被前所属人收取";
            applyLimitCheckBos.stream().forEach(bo -> {
                TScrmPublicseaDropRuleDTO scrmPublicseaDropRule = dropRuleMap.get(bo.getPublicseaId());
                Integer anybodyApplyDays = scrmPublicseaDropRule.getAnybodyApplyDays();
                if(anybodyApplyDays !=0){
                    if(DateUtil.compare(DateUtil.offsetDay(bo.getPoolTime(),anybodyApplyDays),new Date()) > 0){
                        map.put(bo.getObjectKey(),String.format(anybodyApplyDaysToast,anybodyApplyDays));
                        return;
                    }
                }
                Integer formerOwnerApplyDays = scrmPublicseaDropRule.getFormerOwnerApplyDays();
                if(formerOwnerApplyDays !=0){
                    if(DateUtil.compare(DateUtil.offsetDay(bo.getPoolTime(),formerOwnerApplyDays),new Date()) > 0 && Objects.equals(bo.getOwner(),bo.getFormerOwner())){
                        map.put(bo.getObjectKey(),String.format(formerOwnerApplyDaysToast,formerOwnerApplyDays));
                        return;
                    }
                }
            });
            this.map = mapresult;
            this.applyLimitMsg = map.values().iterator().next();
            ids.removeAll(map.keySet());
            this.keys = ids;
        }
    }
}

假如有新的校验逻辑,那么只需求新增一个构造函数,不需求修正老逻辑单元测试的主要内容,下降测验杂乱度。

和传统代码里的校验方式对比

在传统Java架构里有几个办法能够去解决一部分问题,常见的如BeanValidation注解或ValidationUtils类,但这几个传统的办法同样有问题:

BeanValidation

通常只能解决简略的校验逻辑,杂乱的校验逻辑相同要写代码完成定制校验器

安全教育平台登录入口添加了新缓存是什么意思校验逻辑时,同样会出现在某些当地apple忘掉添加一个注解的状况,DRY准则仍是会被违背

Contacts createWithBeanValidation(
  @NotNull @NotBlank String name,
  @NotNull @Pattern(regexp = "^0?[1-9]{2,3}-?\d{8}$") String phone,
  @NotNull String address
);

当很多的校验逻辑会集在一个类里之后,违背了单一性准则,最后也会导致代码混乱和不可保护Validati缓存英文onU安全教育平台作业登录tils类:

事务反常和校验反常仍是会稠浊。

**抽取静态工具类 xxxUtils 类:**项目里充斥着很多的静态工具类,事务代码散在多个文件傍边时,很难找到或许整理中心的事务逻单元测试能发现约80的软件缺陷辑。

2、笼统数据库appointment存储层

现在很多代码直接调用的mapper,对数据缓存视频合并app库强依靠。笼统出一个数据存储层来下降依靠。不关心数据存储层详细运用什么数据库。

范畴驱动落地实战

这里会引进几个模型概念缓存视频在手机哪里找缓存视频怎样转入相册

Entity、Data Object (DO)和Data Transfer Object (D缓存视频在手机哪里找TO):

DO:DO的字段类型和称号应该和数据库物理表格的字段类型和称号一一对应;

Entity:实体目标映射运用中的事务模型,它的字段和办法应该和事务语言坚持一致,和耐久APP化无关。一般来说 Entity 中的字段应该比 DO 里边要多,或许有DO 的嵌套联系(聚合根);Ent安全模式ity 不需求序列化和耐久化,只是存在内存中。

DTO(传输目标):主要作为Ap单元测试主要的测试技术不包括plication层的入参和出参,比方CQRS里的Command、Query、Eventapprove,以及Request、Response等都归于DTO的范畴。DTO的价值在于适配不同的事务场景的入参和出参。

关于杂乱的实体Entity,举个例子:

范畴驱动落地实战

Entity、DO和DTO 的彼此转化:

范畴驱动落地实战

经过笼统出一个Assembler/Converter目标,咱们能把杂乱的转化逻辑都收敛到一个目标中。目标映射联系改变时,只需求改这个目标就能够了。

留意:

笼统的repo单元测试评语怎么写sitory接口 操作的 只能是 实体目标。

从运用杂乱度视点来看,区分了DO、E缓存视频合并appntity、DTO带来了代码量的膨胀。可是在实践杂缓存是什么意思乱事务场景下,经缓存清理过功用来区分模型带来的价值是功用性的单一和可测验、可预期application,最终反缓存视频变成本地视频而是逻辑杂乱性的下降(所以简略的事务并不适合DDD)。

3、防腐层(appointmentACL)

这个事例是以头绪为例,和头绪不相关的服approve务都能够称为三方服务。在当时体系需求依靠其他体系的时分,可能会依单元测试家长评语靠对方的数据结构,API,假单元测试常用方法如对方接口或许数据结构产生改变,会导致当时体系被“腐蚀”,安全期计算器这个时分咱们需求加一层防腐层来隔离外部依靠,这种模式叫ACL(Anti-Corruption Layer).

范畴驱动落地实战

ACL 不只是是做了一层封装,还能提供一些其他功用缓存是什么意思

  • 适配器:在外部回来数据不符合内部标准时,能够经过适配器模式将数据进行转化。

  • 缓存:对外部体系调用频频且数据改变不频频的数据(比方体系字段)单元测试常用方法

  • 兜底:外部安全教育平台登录入口体系不稳定时回来兜底值,进步体系的稳定性。

  • 易于开发测验:在和外部体approve系进行联调开发时缓存视频怎样转入相册,外部体系没有准备好的状况下能够进行mock 完appearance成。

  • 开关:某些场景定制化能够经过开关在ACL 完成安全模式

4领域驱动设计、封装事务逻辑

用Entity 封装目标的有状况的行为

@ApiModelProperty(value = "资源流入ID")
private String sourceId;
//招领
public void apply(UserInfoDetailVo userInfoDetailVo){
    Date now = new Date();
    this.owner = userInfoDetailVo.getUserWid();
    this.claimTime = now;
    this.lastFollowTime = now;
    this.lastFollowUserWid = userInfoDetailVo.getUserWid();
    this.lastUpdateUserWid = userInfoDetailVo.getUserWid();
    this.followStatus = CluefollowStatusEnum.FOLLOW_STATUS_NUCONTACT.getCode();
}
//抛弃
public void giveup(){
    //to do
}
//改变所属人
public void change_owner(){
    // to do
}

APP过domain service 封装多目标的行为单元测试评语怎么写逻辑

public interface ClueService {
    List<ClueEntity> apply(UserInfoDetailVo userInfoDetailVo, List<String> ids, List<ClueEntity> clueEntities);
}
@Service
public class ClueServiceImpl implements ClueService {
    @Override
    public List<ClueEntity> apply(UserInfoDetailVo userInfoDetailVo, List<String> ids, List<ClueEntity> clueEntities) {
        List<ClueEntity> result = new ArrayList<>();
        clueEntities.forEach(e->{
            if (ids.contains(e.getClueKey())){
                e.apply(userInfoDetailVo);
                result.add(e);
            }
        });
        return result;
    }
}

5、重构剖析

修正后的代码

@ApiOperation("公海申领")
@PostMapping("/publicsea/apply")
@Validated
public CommonResult apply(@RequestBody BasePcRequest<PublicSeaApplyRequest> request) {
    // 可根据stage 根据策略
    CommonResult CommonResult = new CommonResult();
    ApplyParamDP applyParam =  new ApplyParamDP(request.getData(),request.getUserInfoDetailVo());
    ApplyResponse applyResponse = applyService.apply(applyParam);
    CommonResult.setErrcode(ResultCode.SUCCESS.getCode());
    CommonResult.setErrmsg(applyResponse.getMessage());
    CommonResult.setData(applyResponse);
    return CommonResult;
}
@Override
public ApplyResponse apply(ApplyParamDP param) {
    ApplyResponse applyResponse = new ApplyResponse();
    //权限校验
    checkAuth(param);
    //数据获取
    List<ClueEntity> clueEntities = clueRepository.queryPublicSeaAuth(new CluePidPublicParam(param.getUserInfoDetailVo().getPid(), param.getApplyRequest().getKeys()));
    Map<Long, Boolean> seaIdCheck = publicSeaService.checkUserExistPublicSea(new CheckUserExistPublicSeaParam(param.getUserInfoDetailVo().getPid(), param.getUserInfoDetailVo().getUserWid(), param.getApplyRequest().getStage(), clueEntities));
    CheckPublicApplyBo checkPublicApplyBo = new CheckPublicApplyBo(param.getApplyRequest().getKeys(),clueEntities,seaIdCheck);
    //保有量数据校验
    ApplyResponse applyRes= chekResourceHold(param, applyResponse, checkPublicApplyBo);
    if (applyRes != null) return applyRes;
    //公海规矩校验
    BatchCheckApplyLimitDP limit = getBatchCheckApplyLimit(param, clueEntities, seaIdCheck, checkPublicApplyBo);
    ApplyResponse responseCheckApplyList = applyResponse.checkApplyList(checkPublicApplyBo.getKeys(), checkPublicApplyBo.getTotalCount(),limit.getApplyLimitMsg());
    if (responseCheckApplyList!=null) { return responseCheckApplyList;}
    //履行招领动作
    List<ClueEntity> applyentitys = clueService.apply(param.getUserInfoDetailVo(), checkPublicApplyBo.getKeys(), clueEntities);
    clueRepository.batchSaveUpdate(applyentitys);
    clueRepository.batchSaveUpdateES(applyentitys);
    // 异步核算掉保时间(可发消息)
    publicSeaService.updateDeadLineDate();
    //构建写跟进日志(可发消息)
    StateChangeLogBo applylog = new ClueApplyStateChangeLogBo(param.getUserInfoDetailVo(),param.getApplyRequest().getStage(),new Date());
    stateChangeLogService.savelog(applylog);
    return applyResponse.buildSuccessResponse(checkPublicApplyBo.getKeys(), checkPublicApplyBo.getTotalCount(),limit.getApplyLimitMsg());
}

安全教育平台登录码从200多行缓存的视频在哪 简化到30多行appreciate

流程图

范畴驱动落地实战

从头编列后的分层

范畴驱动落地实战

经过对外部依靠的笼统和内部逻辑的封装重构,运用整体的依靠联系变单元测试主要的测试技术不包括了:

1、最底层不再是数据库,而是Entity、DP 和Domain Service。这些目标不依靠任何外部服务和结构,而是纯内存中的数据和操作。这些目标咱们打包在单元测试集成测试系统测试范畴层。范畴层没有任何外部依靠联系。

2、然后是负责组件编列的Application Service,可是这些服务只是依靠了一些笼统出来的ACL类和Rep缓存是什么意思ository类,而其详细完缓存视频怎样转入相册成类是经安全教育日过依靠注入注进来的。Application Service、Repositor缓存是什么意思y、ACL缓存清理等咱们统称为运用层。运用层 依靠 范畴层,但不依靠详细完成。

3、最后是ACL,Repository等的详细完成安全,这些完成通常依靠外部详细的技能完成和结构,所以统称为基础设施层。Web结构里的目标如Controll缓存是什么意思er之类的在单元测试集成测试系统测试现在这种完成方式下也归于基础设单元测试家长评语施层。

现在咱们很简略看出招领的逻辑流程和外部依靠。单元测试主要的测试技术不包括假如现在来完成这个事务需求,咱们能够:

  1. 先写Domain层的事务逻辑;

  2. 然后对外部依靠单元测试用例笼统接口进行编写;

  3. 再写Application层approve的组件编列;

  4. 最后才写每个外部依靠的详细完成;

像填空题相同就把代安全教育平台作业登录码写完了,**这种架构思路安全和代码安排结构就叫做Domain-Driven Desi单元测试的主要内容gn(范畴驱动规划)。所以DDD不是一个特殊的架构规划,而是一种架构思维,**在代码落当地面,是一种代码安排方式。

总的来说DDD就是从以数据库为中心过度到以范畴模型为中心,将侧重点从功率改动为保护。从久远的视点看,以范畴模型为中心的规划更加明晰,也是一种更忠诚于范缓存视频变成本地视频畴笼统的完成,因此可保护性更高。

三、代码安排结构

Java中apple咱们能够经过POM Module和POM依靠安全模式来处理彼此的联系,避免基层代码缓存是什么意思依靠到上层完成的状况。

范畴驱动落地实战

types 模块

能够单元测试中设计测试用例的依据是保存对外露出的DP,由于DP 是无状况的逻辑,能够对外露出,能够包含在对单元测试评语怎么写外的API 接口中,不依靠任何类库,能够单独打包。纯 POJO。

范畴驱动落地实战

Domain 模块

Domain 模块是中心事务逻辑的会集地,包含有状况的Entity、范畴服务Domain Service、以及各种外部依靠的接口类:如Repository、ACL、中间件等。Domai单元测试家长评语n模块仅依靠Types模块。纯 POJO。

范畴驱动落地实战

Application模块

Application模块依靠Domain模块,对事务逻辑进行编列。缓存的视频在哪纯P缓存英文OJ安全工程师O。

范畴驱动落地实战
Inf单元测试能发现约80的软件缺陷rastructure模appetite

Infrastructure模块依靠Domainappstore模块,Infrastructure模块包含了Persistence、ES、repositoryimpl等模块。其中耐久化模块要依靠详细的ORM类库,比方MyBatis。依靠其他的服务的详细完成,converter中包含Entity到DO的转化类。

范畴驱动落地实战

接口模块

Web模块包含Controller等相关代码和发动类,interface 模块包含du缓存视频合并appbbo 接口相关代码。

可测验性的进步

1、Typesappear,Domain模块都归于无外部依靠的纯POJO,基本上都能够100%的被单元测验掩盖approve

2、Aapplicationpplication模块的代码依靠外部笼统类,假如经过测验结构去Mock所有外部依靠,但仍然能够100%被单元测验。

3、Infrastructure的每个模块的代码相对独立,接口数量比较少,相对比较简略写单测。并且模块的改变不会很频频,归于一劳永逸。

4、Web模块有两种测验办法:经过Spring的MockMVC测验,或许经过HttpClient调用接口测验。可是在测验时最好把Controller依靠的服务类都Mock掉。单元测试评语怎么写假如把Controller的逻辑单元测试的主要内容都后置到Appli单元测试评语怎么写cation Service中时,Controller的逻辑变得极为简略,很简略100%掩盖。

代码的改变速度

传统的的架构中,代码的从上到下的演进速度是相同的,改个需求可能从接口到事务逻辑到数据耐久层都要修正。那么DDD的代码改变速度是不相同的。

Doma安全生产法in层归于中心安全模式事务逻辑,归于经常被修正的当地。

Application层归于事务用例的编列。事务用例一般都是描述比较大方向的需求,接口相对稳定,特别是对外的接口一般不会频频改变。

Infrastrappointmentucture层归于最低频改变的。

所以在DDD架构中,能明显看出越外层的代码越稳定,越内层的代码演安全生产法进越快,真正表现了范畴“驱动”的中心思维。

总结

经过DP 收集和封装无状况的核算逻辑,经过实体来整理事务目标所具有的能力和动作,经过防腐层笼统外部依靠。DDD的架构能带来以下收益

  • 代码结构明晰

  • 高可保护性

  • 高可扩展性

  • 高可测验性

DDD不是一个特殊的架构规划,而是一种架构思维,是一种代码安排方式。

在功率和可保护性方面做平衡,在现在这种代码搬迁单元测试是什么的过程中,能够经过减少entity 的聚合 和原来逻辑坚持一致性的方式 来平衡 功率和可保护性!