共计 7289 个字符,预计需要花费 19 分钟才能阅读完成。
一、背景
前台业务同学在业务承接过程中总是埋怨大部分业务无奈通过设计模式来承接,写的代码是越来越没有谋求,理由是我无奈预测将来的业务的倒退,且设计模式更多的是在框架或中间件中应用。然而设计模式是对能力形象出的通用模式,从哲学的角度来看世间万物皆尘土,事物都是能够形象出独特的实质的货色。所以,难道只有底层能力能够形象,业务逻辑局部就不能够形象了?必须能够才是啊。在前台业务承接过程中除了能力能够形象,还有能够形象出业务流程,假如在有这样一些业务场景,品搜和图搜、直播间评论和点赞、公域直播会场和私域商详透直播等等,这些各畛域内的业务流程“大同小异”,因而都能够形象出通用的业务流程节点。然而通常在一个骨干流程须要承接的场景有很多,比方直播间互动这个骨干流程包含了直播间评论、点赞、求解说、看证书、进场等等场景,所以咱们须要通过次要流程进行进行多场景承接。然而这样还不够,在面对多端型多场景的状况下须要解决返回不同的数据模型。综上所述,咱们如何通过一个骨干业务流程承接多个业务场景并在数据上可适配到多端型多场景,实现在服务端高质量高效率的“包接口”,上面会具体介绍。
二、业务承接
如果你面临的问题是在同一个业务域上承接多种相似的业务场景,每天在适配各种端型或者各种场景而对外提供接口时,为了保障利用零碎的可扩展性和高效承接业务,那么能够依照如下步骤进行设计。
2.1 业务流程形象
首先须要进行业务建模,形象出用户用例或者 user story,当然具体的粒度能够本人把控,具体如下:
2.1.1 用户用例
在直播间互动畛域内的用户用例如下:
从整个零碎登程,挖掘出面向不同的用户提供的能力有哪些,在这些用例背地须要进行的流程和节点又是什么。通过这些流程和节点能力进行后续的零碎时序和流程形象,举例如下
在互动畛域内的流程和节点如下:
- 风控查看
- 评论长久化
- 评论上屏
- 评论进沟通
2.2.2 零碎时序
基于用户用例进行剖析,这些用例都须要通过什么的流程节点进行解决,而后将这些流程依照零碎时序进行出现。
到此基于上述的用例和时序是不是能够形象出具体互动流程了,不言而喻。
2.2.3 业务流程形象
有了零碎用例和零碎时序这一步就比较简单,从零碎时序里很容易能够形象出具体的流程和流程中的解决节点,具体如下:
- 对于直播间互动畛域能够形象出的业务流程:风控查看 -> 互动内容长久化 -》音讯上屏 -》互动进 IM
- 对于直播间散发畛域能够形象出的业务流程:直播举荐 -》直播间根底信息 -》直播流信息 -》直播间品信息
到此,大家能够依照上述步骤在大脑里对本人的业务域进行形象进去了。
2.2.4 设计模式固化主流程
依照业务主流程能够通过模板模式将解决流程固定下来,如下所示:
@Override
@LiveLog(logResult = true)
public InteractionResult interactionSubmit(MobileInteractionRequest request, InteractionLiveRoom liveRoom) {Boolean needSave = MapUtils.getBoolean(request.getExtInfo(), LiveInteractionConstant.NEED_SAVE);
// 默认保留
InteractionResult saveResult = null;
if (Objects.isNull(request.getExtInfo()) || Objects.isNull(needSave) || needSave) {saveResult = save(request, liveRoom);
if(Objects.nonNull(saveResult) && !saveResult.isSuccess()) {return saveResult;}
}
// 默认进沟通
InteractionResult chatResult;
if (Objects.isNull(request.getSendToChat()) || Boolean.parseBoolean(request.getSendToChat())) {chatResult = sendToChat(request);
if(Objects.nonNull(chatResult) && !chatResult.isSuccess()) {return chatResult;}
}
if(Objects.nonNull(saveResult) && saveResult.isSuccess()) {return saveResult;}
return null;
}
/**
* 互动行为保留到数据库或者缓存中
*
* @param request
* @return
*/
protected abstract InteractionResult save(MobileInteractionRequest request, InteractionLiveRoom liveRoom);
/**
* 进沟通
*
* @param request
* @return
*/
protected abstract InteractionResult sendToChat(MobileInteractionRequest request);
2.2 业务流程扩大
因在上述模版模式中预留了两个扩大点,所以在子类中能够通过扩大点进行扩大,举例如下:
如果有更多的场景就须要扩大实现上述两个扩大点进行扩大即可,这样保障了业务的高效承接。这里会有两个问题:
- 在程序运行时如何依据具体的场景抉择哪个子类进行逻辑解决
- 如何进行适配端和场景返回的数据模型
针对第一个问题,其实就是如何去 if else 的问题,这里也给出比拟经典的计划:
- 枚举法
- 表驱动法
- 策略模式 + 工厂模式
其中枚举法和表驱动法比较简单易用,原理就是将映射关系封装在枚举类或本地缓存中,这里简略介绍下如何通过策略模式打消 if else。
// 策略接口
public interface Opt {int apply(int a, int b);
}
// 策略实现类
@Component(value = "addOpt")
public class AddOpt implements Opt {
@Autowired
xxxAddResource resource; // 这里通过 Spring 框架注入了资源
@Override
public int apply(int a, int b) {return resource.process(a, b);
}
}
// 策略实现类
@Component(value = "devideOpt")
public class devideOpt implements Opt {
@Autowired
xxxDivResource resource; // 这里通过 Spring 框架注入了资源
@Override
public int apply(int a, int b) {return resource.process(a, b);
}
}
// 策略解决
@Component
public class OptStrategyContext{private Map<String, Opt> strategyMap = new ConcurrentHashMap<>();
@Autowired
public OptStrategyContext(Map<String, TalkService> strategyMap) {this.strategyMap.clear();
this.strategyMap.putAll(strategyMap);
}
public int apply(Sting opt, int a, int b) {return strategyMap.get(opt).apply(a, b);
}
}
总结伪代码:
// 抽象类固定业务流程 预留扩大点
public abstract class AbstractXxxx {doXxx(Object context) {
// 节点 1
doNode1(context);
// 节点 2
doNode2(context);
// 节点 3
doNode3(context);
// 节点 n
...
}
// 扩大点 1
protected abstract Result doNode1(Object context);
// 扩大点 2
protected abstract Result doNode2(Object context);
// 扩大点 3
protected abstract Result doNode3(Object context);
}
// 策略解决
public class OptStrategyContext{private Map<String, Opt> strategyMap = new ConcurrentHashMap<>();
static {
// 上述模版模式的实现类
strategyMap.put("business1", Xxxx1);
strategyMap.put("business2", Xxxx2);
strategyMap.put("business3", Xxxx3);
}
// 初始化
public OptStrategyContext(Map<String, Opt> strategyMap) {this.strategyMap.clear();
this.strategyMap.putAll(strategyMap);
}
public int doXxxx(Object context) {return strategyMap.get(business).doXxxx(context);
}
}
2.3 多场景多端型适配
下面咱们只是通过模版模式形象出了骨干业务流程,然而如何适配不同的端型和不同的场景,返回不同的数据模型呢,这里有两种答案,一种是模版模式、另一种是“棒棒糖”模式,上面逐个介绍。
2.3.1 模版模式适配
既然是模版模式,这里的骨干流程又是什么呢?次要跟咱们解决的问题有关系,依照 2.1 中的流程步骤,能够形象出固定的流程为:申请入参解决 -》业务逻辑解决 -》后果返回解决。
其中业务逻辑解决能够选定为 2.2 中介绍的通过策略模式抉择业务扩大的子类,来处里业务局部;申请入参和后果返回解决局部能够设置为扩大点,供子类扩大。具体伪代码如下:
// 抽象类固定业务流程 预留扩大点 适配多端型多场景
public abstract class AbstractSceneAdapter {<T> T doXxx(Object context) {
// 节点 1
doRequestFilter(context);
// 节点 2
getBusinessService(context).doBusiness(context);
// 节点 3
return doResultWrap(context);
}
// 扩大点 1
protected abstract Result doRequestFilter(Object context);
// 扩大点 2
protected abstract Result doBusiness(Object context);
// 扩大点 3
protected abstract Result doResultWrap(Object context);
// 业务逻辑解决子类
protected abstract BusinessService getBusinessService(Object context);
}
// 策略解决 依据不同端型场景抉择适合的子类
public class SceneAdapterViewService {private Map<String, SceneAdapter> strategyMap = new ConcurrentHashMap<>();
static {
// 上述模版模式的实现类
strategyMap.put("scene1", Xxxx1);
strategyMap.put("scene2", Xxxx2);
strategyMap.put("scene3", Xxxx3);
}
// 初始化
public SceneAdapterViewService(Map<String, SceneAdapter> strategyMap) {this.strategyMap.clear();
this.strategyMap.putAll(strategyMap);
}
public Result doXxxx(Object context) {return strategyMap.get(scene).doXxxx(context);
}
}
注:因要适配不同端型不同场景返回不同的数据模型,所以上述伪代码中主流程最终返回的后果是一个泛型,在子类实现的时候进行确定具体返回的类型。
2.3.1 棒棒糖模式适配
通过模版模式来适配时会有一个小问题,当须要有多个申请入参处理器或者多个后果包装器的时候须要在模版里减少解决节点,但其实这些节点是有共性的可形象进去的。因而能够针对入参处理器和后果包装器定义独自的接口,须要多个处理器时同时实现接口进行解决。而后这些实现类打包放在独自的类中顺次执行即可。当然其中的业务解决局部也能够定义接口动静实现。伪代码如下:
// 入参处理器
public interface IRequestFilter<> {void doFilter(T t);
}
// 后果包装器
public interface IResultWrapper<R, T> {Result<R> doWrap(Result<T> res);
}
public class SceneAdapterViewService implements InitializingBean {
private List<IRequestFilter> filters;
private List<IResultWrapper> wrappers;
private Map<String, SceneAdapter> strategyMap = new ConcurrentHashMap<>();
// 申请过滤器实现类
@Autowired
@Qualifier("filter1")
private IRequestFilter filter1;
@Autowired
@Qualifier("filter2")
private IRequestFilter filter2;
// 后果处理器实现类
@Autowired
@Qualifier("wrapper1")
private IResultWrapper wrapper1;
// 业务解决实现类
@Autowired
@Qualifier("scene1")
private SceneAdapter scene1;
@Autowired
@Qualifier("scene2")
private SceneAdapter scene2;
@Autowired
@Qualifier("scene3")
private SceneAdapter scene3;
// 主办法
publice Result sceneAdapte(Object context) {
// 申请入参过滤 异样时返回
for(int i = 0; i<filters.size(); i++) {
try {filters.get(i).doFilter(context)
} catch {return null;}
}
// 策略模式执行业务逻辑,执行是依照模版模式
Result res = strategyMap.get(scene).doXxxx(context);
Result result = res;
// 过滤解决,包含树结构扭转,数据字段裁剪等
for(int i = 0; i<wrappers.size(); i++) {
try {result = wrappers.get(i).doWrap(result)
} catch {return res;}
}
return result;
}
// 初始化各个节点值
@Override
public void afterPropertiesSet() throws Exception {
// 入参过滤器 可多个
filters.add(filter1);
filters.add(filter2);
// 后果处理器 可多个
wrappers.add(wrapper1);// 业务解决局部
strategyMap.put("scene1", Xxxx1);
strategyMap.put("scene2", Xxxx2);
strategyMap.put("scene3", Xxxx3);
}
}
三、接口设计
基于上述两种设计模式来适配时咱们的接口又该如何设计,是设计面向通用的业务层接口还是面向定制化的业务接口,两种形式各有优缺点:
对于接口提供者来说必定不心愿频繁改变代码公布代码,然而又心愿可能在业务承接过程中可能高效适配多端型多场景,因而这里总结了下接口设计准则:
1、对于越底层的接口应该越通用,例如 HSF 接口、畛域服务、中间件提供的接口;
2、对于越下层的接口应该越定制化,例如对于不同的 UI 适配、不同的场景适配等;
3、对于业务畛域内的接口应该通用化,例如直播业务域的散发畛域、互动畛域内的接口尽可能的通用化;
四、总结
在承接业务过程中会面临频繁包接口、一个 view 层的数据模型充斥了小 100 个属性,零碎的扩展性遇到瓶颈,这些问题除了通过平台化配置化的能力来解决,然而回归到代码自身咱们任然能够通过形象的设计模式来解决。
- 基于形象的实践达到复用、高内聚低耦合,升高零碎复杂度的指标,设计模式不只是用在底层能力或中间件中,在业务承接过程中亦有大的效用。
- 千万不要为了用设计模式而刻意应用设计模式,带来的成果事与愿违,在抉择设计模式时也要三思,落地后再改变老本将会微小。
- 在前台业务开发中,须要划分主各个业务畛域,在畛域中形象出该业务的解决流程,基于流程可设计相干的扩大和编排能力,形式有很多种,包含 SPI、设计模式、DSL 等,本文次要通过模版模式和棒棒糖模式来解决问题。
- 接口设计应该依照越底层越通用,越下层越定制化的准则进行设计,当然在业务域内的接口应尽可能的通用话。
作者|幽霄
点击立刻收费试用云产品 开启云上实际之旅!
原文链接
本文为阿里云原创内容,未经容许不得转载。