流程编排(流程编排框架)

访客2023-10-07 21:21:5839
背景

项目中经常会遇到一些功能类似,但是各自会多一些少一些功能,这些类似的功能多了,往往会出现上帝类,就是一个类中包含了所有情况,复杂至极,这样的类往往不好维护,最后只能在这个类上往后继续增加业务,最后无比复杂无法维护。还有的是每一种情况就重新写一次定制一个新的,这样虽然可以不会造成类的膨胀,但是这样每次重写造成的很多相似的代码,复用情况差。

业务模块化

针对上述的情况,在一些文章里经常会看到通过模块化处理,像搭积木似的把业务搭建起来。那如何搭这个积木呢?首先要有积木。如何提取这些积木呢?这些模块化的业务就是一些不变的原子业务,也就是DDD中提到的领域模型。以登录注册举例,登录注册功能并不复杂,但是要面对不同的业务线,因此会很多不同的变化,如图

登录注册功能类似,而把他们逐步分割成一个个原子服务模块就是这些:登录密码校验,验证码校验,安全校验token生成等,这些模块不仅登录注册可以用其他也可以用,而这些原子模块往往都有着不可分割,稳定不变的特性,通过不同的编排以应对不同的业务逻辑。

业务的编排

原子模块提取之后,就相当于我们有了积木,我们可以动手自己组装,可是拼装的时候会发现积木无法很好拼接在一起,需要用很多"胶水"粘在一起。胶水用多了总是不太好看,那么又应该怎么做?首先为什么这些积木无法直接组装,因为他们不是一个牌子的,因此要统一牌子,都用乐高的。业务的统一抽象接口

/** */ public interface IBusinessComponent<T> { /** * 业务逻辑执行 * * @param request 执行请求 */ void execute(T request); /** * 是否执行 * @return */ default Boolean executeIs(){ return true; } }

各业务模块实现接口,实现接口的统一封装,并定义自己的参数接口

/** * 注册组件 */ @Component public class RegisterComponent implements IBusinessComponent<RegisterRequest> { @Resource private AccountRegisterBaseAppService accountRegisterBaseAppService; @Override public void execute(RegisterRequest registerRequest) { //避免重新new,无法渠道AccountId AccountRegisterParam accountRegisterParam = registerRequest.getAccountRegisterParam(); accountRegisterBaseAppService.register(accountRegisterParam); registerRequest.setAccountId(accountRegisterParam.getAccountId()); } } /** */ public interface RegisterRequest { /** * 注册入参 * @return */ AccountRegisterParam getAccountRegisterParam(); /** * 回填生成AccountId * @param accountId */ void setAccountId(AccountId accountId); }

接口统一之后,那么就可以抽象出一个编排流程管理,进行管控,执行各个组件,并对异常进项管控

/** * 流式管理, * 类似责任连,命令,stream * 传统责任连没有外围管理,这样不是太好做一些控制 * 比如 超时,降级,异常 */ @Slf4j public class Processor<E, T extends ProcessorDomAIn<E>> { /** * 业务组件 */ List<IBusinessComponent> components = Lists.newArrayList(); /** * 增加组件 * * @return */ public Processor<E, T> APPend(IBusinessComponent component) { components.add(component); return this; } /** * 执行组件 * * @param request * @return */ public E doProcess(T request) { for (IBusinessComponent component : components) { try { if (component.executeIs()){ long beforeMillis = System.currentTimeMillis(); component.execute(request); long afterMillis = System.currentTimeMillis(); log.info("{},SUCCESS,耗时{}毫秒", this.getClass().getSimpleName(), (afterMillis - beforeMillis)); } } catch (BizCheckException e) { log.info("Process Exception common{}", this.getClass(), e); exception(e, request); } } return request.getResult(); } /** * 判断异常是否忽略 * * @param e * @param request */ private void exception(BizCheckException e, T request) { if (!CollectionUtils.isEmpty(request.getIgnoreException())) { if (request.getIgnoreException().contains(e.getCode())) { return; } } throw e; } }

通过品牌的积木有了,现在要做的就是把积木拼起来,每个拼接的积木都有一个对应的拼装方式,这些拼装方式都需要实现各个模块的参数接口

/** */ public interface ProcessorDomain<T> { /** * 获取结果 * @return */ T getResult(); /** * 忽略错误码 * @return */ default Set<String> getIgnoreException(){ return null; } } /** * 1.验证码校验 * 2.用户手机号获取 * 4,注册 * 5,登录 * 6,jwt * 7,获取VO */ public class LoginAndRegWebVerifyCodeProcessorAo implements ProcessorDomain<LoginAndRegWebVerifyCodeVo>, UserMobileRequest, VerifyCodeCheckRequest, RegisterRequest, LoginRequest, TokenRequest, SecurityCheckRequest {

最后的流式的调用方式

public RestResult<LoginAndRegWebVerifyCodeVo> loginAndReg(@Valid @RequestBody LoginAndRegWebVerifyCodeVoParam loginAndRegWebVerifyCodeParam) { LoginAndRegWebVerifyCodeProcessorAo loginAndRegWebVerifyCodeProcessorAo = new LoginAndRegWebVerifyCodeProcessorAo(loginAndRegWebVerifyCodeParam); return RestResult.successResult( new Processor<LoginAndRegWebVerifyCodeVo, LoginAndRegWebVerifyCodeProcessorAo>() .append(verifyCodeCheck) .append(userMobileWrapper) .append(securityCheck) .append(registerComponent) .append(loginWrapper) .append(tokenWrapper) .doProcess(loginAndRegWebVerifyCodeProcessorAo) ); }

类图:框架,IBusinessComponent 是组件接口,但还可以细分成 3种:校验,封装,命令

LoginAndRegWebVerifyCodeProcessorAo 就是将各个模块连接起来的,实现各个模块的请求接口和流程接口,将各个模块组织起来, 每次拼接都要有个一对应的组装

其实模式只是术,只能解决特定问题,对不变业务的理解抽象,业务趋势的把握,以及面向对象思想的理解才是灵活应对多变业务的根本

控制面板

您好,欢迎到访网站!
  查看权限

最新留言