从设计模式谈业务开发

简介: 本文主要讲述我们如何通过一个主干业务流程承接多个业务场景并在数据上可适配到多端型多场景,实现在服务端高质量高效率的“包接口”。


来源|阿里开发者公众号

作者|幽霄

一、背景

前台业务同学在业务承接过程中总是抱怨大部分业务无法通过设计模式来承接,写的代码是越来越没有追求,理由是我无法预测未来的业务的发展,且设计模式更多的是在框架或中间件中使用。然而设计模式是对能力抽象出的通用模式,从哲学的角度来看世间万物皆尘土,事物都是可以抽象出共同的本质的东西。所以,难道只有底层能力可以抽象,业务逻辑部分就不可以抽象了?必须可以才是啊。在前台业务承接过程中除了能力可以抽象,还有可以抽象出业务流程,假设在有这样一些业务场景,品搜和图搜、直播间评论和点赞、公域直播会场和私域商详透直播等等,这些各领域内的业务流程“大同小异”,因此都可以抽象出通用的业务流程节点。但是通常在一个主干流程需要承接的场景有很多,比如直播间互动这个主干流程包括了直播间评论、点赞、求讲解、看证书、进场等等场景,所以我们需要通过主要流程进行进行多场景承接。但是这样还不够,在面对多端型多场景的情况下需要处理返回不同的数据模型。综上所述,我们如何通过一个主干业务流程承接多个业务场景并在数据上可适配到多端型多场景,实现在服务端高质量高效率的“包接口”,下面会详细介绍。

二、业务承接

如果你面临的问题是在同一个业务域上承接多种类似的业务场景,每天在适配各种端型或者各种场景而对外提供接口时,为了保证应用系统的可扩展性和高效承接业务,那么可以按照如下步骤进行设计。

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,扩展性强

2,业务承接效率高,无需频繁发布代码

1,业务边界不清晰,问题排查效率低

2,接口数据冗余

定制化业务接口

1,业务边界清晰

2,接口出入参数据无冗余

1,无扩展

2,频繁包接口发布代码


对于接口提供者来说肯定不希望频繁改动代码发布代码,但是又希望能够在业务承接过程中能够高效适配多端型多场景,因此这里总结了下接口设计原则:

1、对于越底层的接口应该越通用,例如HSF接口、领域服务、中间件提供的接口;

2、对于越上层的接口应该越定制化,例如对于不同的UI适配、不同的场景适配等;

3、对于业务领域内的接口应该通用化,例如直播业务域的分发领域、互动领域内的接口尽可能的通用化;

四、总结

在承接业务过程中会面临频繁包接口、一个view层的数据模型充满了小100个属性,系统的扩展性遇到瓶颈,这些问题除了通过平台化配置化的能力来解决,但是回归到代码本身我们任然可以通过抽象的设计模式来解决。

  • 基于抽象的理论达到复用、高内聚低耦合,降低系统复杂度的目标,设计模式不只是用在底层能力或中间件中,在业务承接过程中亦有大的功效。
  • 千万不要为了用设计模式而刻意使用设计模式,带来的效果适得其反,在选择设计模式时也要三思,落地后再改动成本将会巨大。
  • 在前台业务开发中,需要划分主各个业务领域,在领域中抽象出该业务的处理流程,基于流程可设计相关的扩展和编排能力,方式有很多种,包括SPI、设计模式、DSL等,本文主要通过模版模式和棒棒糖模式来解决问题。
  • 接口设计应该按照越底层越通用,越上层越定制化的原则进行设计,当然在业务域内的接口应尽可能的通用话。
相关文章
|
19天前
|
设计模式 算法 搜索推荐
后端开发中的设计模式应用与实践
在软件开发的广袤天地中,后端技术如同构筑高楼大厦的钢筋水泥,支撑起整个应用程序的骨架。本文旨在通过深入浅出的方式,探讨后端开发领域内不可或缺的设计模式,这些模式犹如精雕细琢的工具箱,能够助力开发者打造出既健壮又灵活的系统架构。从单例模式到工厂模式,从观察者模式到策略模式,每一种设计模式都蕴含着深刻的哲理与实践价值,它们不仅仅是代码的组织方式,更是解决复杂问题的智慧结晶。
|
1月前
|
设计模式 算法 搜索推荐
后端开发中的设计模式应用
在软件开发的浩瀚海洋中,设计模式犹如一座座灯塔,为后端开发者指引方向。本文将深入探讨后端开发中常见的设计模式,并通过实例展示如何在实际项目中巧妙应用这些模式,以提升代码的可维护性、扩展性和复用性。通过阅读本文,您将能够更加自信地应对复杂后端系统的设计与实现挑战。
54 3
|
1月前
|
设计模式 安全 数据库连接
后端开发中的设计模式应用
在软件开发的浩瀚海洋中,设计模式如同灯塔,为后端开发者指引方向。它们不仅仅是代码的模板,更是解决复杂问题的智慧结晶。本文将深入探讨几种常见的设计模式,包括单例模式、工厂模式和观察者模式,并揭示它们在实际应用中如何提升代码的可维护性、扩展性和重用性。通过实例分析,我们将一窥这些模式如何在后端开发中大放异彩,助力构建高效、灵活的软件系统。
|
2月前
|
设计模式 存储 前端开发
React开发设计模式及原则概念问题之自定义Hooks的作用是什么,自定义Hooks设计时要遵循什么原则呢
React开发设计模式及原则概念问题之自定义Hooks的作用是什么,自定义Hooks设计时要遵循什么原则呢
|
2月前
|
设计模式 消息中间件 缓存
后端开发中的设计模式应用
【8月更文挑战第13天】在软件开发的世界中,设计模式是解决常见问题的最佳实践。对于后端开发者而言,掌握如何将设计模式应用于实际项目不仅可以提高代码质量,还能增强系统的可维护性和扩展性。本文旨在探讨几种常见的设计模式,并解释它们如何优化后端开发流程,提升软件性能和用户体验。
|
2月前
|
设计模式 物联网 Android开发
移动应用与系统:探索未来技术的融合之路后端开发中的设计模式探索
【8月更文挑战第21天】随着科技的飞速发展,移动应用和操作系统已经成为我们日常生活中不可或缺的一部分。本文将深入探讨移动应用开发和移动操作系统的相关话题,包括它们的历史、现状以及未来的发展趋势。我们将从移动应用的开发环境、编程语言和工具等方面进行详细的分析,同时也会讨论移动操作系统的特点、优势以及面临的挑战。最后,我们将展望移动应用与系统在未来技术融合中的可能方向和机遇。
127 58
|
1月前
|
设计模式 算法 搜索推荐
后端开发中的设计模式应用
在软件开发的浩瀚海洋中,设计模式犹如灯塔一般指引着方向。它们不是一成不变的规则,而是前人智慧的结晶。本文将深入探讨几种在后端开发中常用的设计模式,如单例、工厂、观察者和策略模式,并阐述如何在实际项目中灵活运用这些模式来提升代码质量、可维护性和扩展性。通过对比传统开发方式与应用设计模式后的差异,我们将揭示设计模式在解决复杂问题和优化系统架构中的独特价值。
|
10天前
|
设计模式 算法 数据库连接
后端开发中的设计模式应用与实践
在软件开发的广袤天地中,设计模式如同夜空中最亮的星辰,引领着开发者们穿越复杂系统的迷雾。本文旨在通过深入浅出的方式,不仅探讨设计模式的理论精髓,揭示它们在后端架构中的重要性,还将以生动的实践案例,展示如何在实际项目中巧妙运用这些模式。我们邀请您一同踏上这场编程之旅,探索如何借助设计模式的力量,让后端系统更加健壮、灵活且易于维护,共同揭开后端技术神秘面纱的一角。
|
1月前
|
设计模式 JavaScript Java
后端开发中的设计模式应用
本文将深入探讨后端开发中常见的设计模式,包括单例模式、工厂模式和观察者模式。每种模式不仅会介绍其定义和结构,还会结合实际案例展示如何在后端开发中应用这些模式来优化代码的可维护性与扩展性。通过对比传统方法与设计模式的应用,读者可以更清晰地理解设计模式的优势,并学会在项目中灵活运用这些模式解决实际问题。
|
2月前
|
设计模式 缓存 算法
后端开发中的设计模式探索
【8月更文挑战第23天】在软件开发的广阔天地里,后端开发扮演着至关重要的角色。它不仅支撑着应用程序的逻辑处理,还确保数据的一致性与安全性。本文将深入探讨后端开发中常用的设计模式,旨在帮助开发者更好地理解如何通过这些模式解决实际问题,提升代码的可维护性和扩展性。我们将从简单到复杂,逐步揭示设计模式在后端开发中的应用,以及它们是如何影响我们的编码实践和思维过程的。

相关实验场景

更多