关于java:管理订单状态该上状态机吗轻量级状态机COLA-StateMachine保姆级入门教程

16次阅读

共计 10118 个字符,预计需要花费 26 分钟才能阅读完成。

前言

在平时的后端我的项目开发中,状态机模式的应用其实没有大家设想中那么常见,笔者之前因为不在电商畛域工作,很少在业务代码中用状态机来治理各种状态,个别都是手动 get/set 状态值。去年笔者进入了电商畛域从事后端开发。电商畛域,状态又多又简单,如果依然在业务代码中东一块西一块保护状态值,很容易陷入出了问题难于 Debug,难于追责的困境。

碰巧有个新启动的我的项目须要进行订单状态的治理,我着手将 Spring StateMachine 接入了进来,治理购物订单状态,不得不说,Spring StateMachine 全家桶的文档写的是不错,并且 Spring StateMachine 也是有官网背书的。然而,它切实是太”重“了,想要简略批改一个订单的状态,须要十分复杂的代码来实现。具体就不在这里开展了,不然我感觉能够吐槽一整天。

说到底 Spring StateMachine 上手难度十分大,如果没有用来做重型状态机的需要,非常不举荐一般的小我的项目进行接入。

最最重要的是,因为 Spring StateMachine 状态机实例 不是无状态的,无奈做到线程平安,所以代码要么须要应用锁同步,要么须要用 Threadlocal,十分的苦楚和难用。 例如上面的 Spring StateMachine 代码就用了重量级锁保障线程平安,在高并发的互联网利用中,这种代码留的隐患十分大。

private synchronized boolean sendEvent(Message<PurchaseOrderEvent> message, OrderEntity orderEntity) {
        boolean result = false;
        try {stateMachine.start();
            // 尝试复原状态机状态
            persister.restore(stateMachine, orderEntity);
            // 执行事件
            result = stateMachine.sendEvent(message);
            // 长久化状态机状态
            persister.persist(stateMachine, (OrderEntity) message.getHeaders().get("purchaseOrder"));
        } catch (Exception e) {log.error("sendEvent error", e);
        } finally {stateMachine.stop();
        }
        return result;
    }

吃了一次亏后,我再一次在网上翻阅各种 Java 状态机的实现,有大的开源我的项目,也有小而美的集体实现。后果在 COLA 架构中发现了 COLA 还写了一套状态机实现。COLA 的作者给咱们提供了一个无状态的,轻量化的状态机,接入非常简略。并且因为无状态的特点,能够做到线程平安,反对电商的高并发场景。

COLA 是什么?如果你还没听说过 COLA,无妨看一看我之前的文章,传送门如下:

https://mp.weixin.qq.com/s/07…

如果你须要在我的项目中引入状态机,此时此刻,我会举荐应用 COLA 状态机。

COLA 状态机介绍

COLA 状态机是在 Github 开源的,作者也写了介绍文章:

https://blog.csdn.net/signifi…

官网文章的前半部分重点介绍了 DSL(Domain Specific Languages),这一部分比拟形象和概念化,大家感兴趣,能够返回原文查看。我精简一下 DSL 的次要含意:

什么是 DSL? DSL 是一种工具,它的外围价值在于,它提供了一种伎俩,能够更加清晰地就零碎某局部的用意进行沟通。

比方正则表达式,/\d{3}-\d{3}-\d{4}/就是一个典型的 DSL,解决的是字符串匹配这个特定畛域的问题。

文章的后半局部重点论述了 作者为什么要做 COLA 状态机?想必这也是读者比拟好奇的问题。我帮大家精简一下原文的表述:

  • 首先,状态机的实现应该能够十分的轻量,最简略的状态机用一个 Enum 就能实现,根本是零老本。
  • 其次,应用状态机的 DSL 来表白状态的流转,语义会更加清晰,会加强代码的可读性和可维护性
  • 开源状态机太简单: 就咱们的我的项目而言(其实大部分我的项目都是如此)。我切实不须要那么多状态机的高级玩法:比方状态的嵌套(substate),状态的并行(parallel,fork,join)、子状态机等等
  • 开源状态机性能差: 这些开源的状态机都是有状态的(Stateful)的,因为有状态,状态机的实例就不是线程平安的,而咱们的应用服务器是分布式多线程的,所以在每一次状态机在承受申请的时候,都不得不从新 build 一个新的状态机实例。

所以COLA 状态机设计的指标很明确,有两个核心理念:

  1. 简洁的仅反对状态流转的状态机,不须要反对嵌套、并行等高级玩法。
  2. 状态机自身须要是 Stateless(无状态)的,这样一个 Singleton Instance 就能服务所有的状态流转申请了。

COLA 状态机的外围概念如下图所示,次要包含:

State:状态
Event:事件,状态由事件触发,引起变动
Transition:流转,示意从一个状态到另一个状态
External Transition:内部流转,两个不同状态之间的流转
Internal Transition:外部流转,同一个状态之间的流转
Condition:条件,示意是否容许达到某个状态
Action:动作,达到某个状态之后,能够做什么
StateMachine:状态机

COLA 状态机原理

这一大节,咱们先讲几个 COLA 状态机最重要两个局部,一个是它应用的连贯接口,一个是状态机的注册和应用原理。如果你临时对它的实现原理不感兴趣,能够间接跳过本大节,间接看前面的实战代码局部。

PS:解说的代码版本为 cola-component-statemachine 4.2.0-SNAPSHOT

下图展现了 COLA 状态机的源代码目录,能够看到十分的简洁。

1. 连贯接口 Fluent Interfaces

COLA 状态机的定义应用了连贯接口 Fluent Interfaces,连贯接口的一个重要作用是,限定办法调用的程序。比方,在构建状态机的时候,咱们只有在调用了 from 办法后,能力调用 to 办法,Builder 模式没有这个性能。

下图中能够看到,咱们在应用的时候是被严格限度的:

StateMachineBuilder<States, Events, Context> builder = StateMachineBuilderFactory.create();
        builder.externalTransition()
                .from(States.STATE1)
                .to(States.STATE2)
                .on(Events.EVENT1)
                .when(checkCondition())
                .perform(doAction());

这是如何实现的?其实是应用了 Java 接口来实现。

2. 状态机注册和触发原理

这里简略梳理一下状态机的注册和触发原理。

用户执行如下代码来创立一个状态机,指定一个 MACHINE_ID:

StateMachine<States, Events, Context> stateMachine = builder.build(MACHINE_ID);

COLA 会将该状态机在 StateMachineFactory 类中,放入一个 ConcurrentHashMap,以状态机名为 key 注册。

static Map<String /* machineId */, StateMachine> stateMachineMap = new ConcurrentHashMap<>();

注册好后,用户便能够应用状态机,通过相似下方的代码触发状态机的状态流转:

stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new Context("1"));

外部实现如下:

  1. 首先判断 COLA 状态机整个组件是否初始化实现。
  2. 通过 routeTransition 寻找是否有符合条件的状态流转。
  3. transition.transit 执行状态流转。

transition.transit 办法中:

查看本次流转是否合乎 condition,合乎,则执行对应的 action。

COLA 状态机实战

**PS:以下实战代码取自 COLA 官网仓库测试类

一、状态流转应用示例

  1. 从繁多状态流转到另一个状态
@Test
public void testExternalNormal(){StateMachineBuilder<States, Events, Context> builder = StateMachineBuilderFactory.create();
    builder.externalTransition()
            .from(States.STATE1)
            .to(States.STATE2)
            .on(Events.EVENT1)
            .when(checkCondition())
            .perform(doAction());

    StateMachine<States, Events, Context> stateMachine = builder.build(MACHINE_ID);
    States target = stateMachine.fireEvent(States.STATE1, Events.EVENT1, new Context());
    Assert.assertEquals(States.STATE2, target);
}

private Condition<Context> checkCondition() {return (ctx) -> {return true;};
}

private Action<States, Events, Context> doAction() {return (from, to, event, ctx)->{System.out.println(ctx.operator+"is operating"+ctx.entityId+"from:"+from+"to:"+to+"on:"+event);
        };
}

能够看到,每次进行状态流转时,查看 checkCondition(),当返回 true,执行状态流转的操作 doAction()。

前面所有的 checkCondition()和 doAction()办法在下方就不再反复贴出了。

  1. 从多个状态流传到新的状态
@Test
public void testExternalTransitionsNormal(){StateMachineBuilder<States, Events, Context> builder = StateMachineBuilderFactory.create();
    builder.externalTransitions()
            .fromAmong(States.STATE1, States.STATE2, States.STATE3)
            .to(States.STATE4)
            .on(Events.EVENT1)
            .when(checkCondition())
            .perform(doAction());

    StateMachine<States, Events, Context> stateMachine = builder.build(MACHINE_ID+"1");
    States target = stateMachine.fireEvent(States.STATE2, Events.EVENT1, new Context());
    Assert.assertEquals(States.STATE4, target);
}
  1. 状态外部触发流转
@Test
public void testInternalNormal(){StateMachineBuilder<States, Events, Context> builder = StateMachineBuilderFactory.create();
    builder.internalTransition()
            .within(States.STATE1)
            .on(Events.INTERNAL_EVENT)
            .when(checkCondition())
            .perform(doAction());
    StateMachine<States, Events, Context> stateMachine = builder.build(MACHINE_ID+"2");

    stateMachine.fireEvent(States.STATE1, Events.EVENT1, new Context());
    States target = stateMachine.fireEvent(States.STATE1, Events.INTERNAL_EVENT, new Context());
    Assert.assertEquals(States.STATE1, target);
}
  1. 多线程测试并发测试
@Test
public void testMultiThread(){buildStateMachine("testMultiThread");

  for(int i=0 ; i<10 ; i++){Thread thread = new Thread(()->{StateMachine<States, Events, Context> stateMachine = StateMachineFactory.get("testMultiThread");
      States target = stateMachine.fireEvent(States.STATE1, Events.EVENT1, new Context());
      Assert.assertEquals(States.STATE2, target);
      });
      thread.start();}


    for(int i=0 ; i<10 ; i++) {Thread thread = new Thread(() -> {StateMachine<States, Events, Context> stateMachine = StateMachineFactory.get("testMultiThread");
      States target = stateMachine.fireEvent(States.STATE1, Events.EVENT4, new Context());
      Assert.assertEquals(States.STATE4, target);
      });
      thread.start();}

    for(int i=0 ; i<10 ; i++) {Thread thread = new Thread(() -> {StateMachine<States, Events, Context> stateMachine = StateMachineFactory.get("testMultiThread");
      States target = stateMachine.fireEvent(States.STATE1, Events.EVENT3, new Context());
      Assert.assertEquals(States.STATE3, target);
      });
      thread.start();}

}

因为 COLA 状态机时无状态的状态机,所以性能是很高的。相比起来,SpringStateMachine 因为是有状态的,就须要使用者自行保障线程平安了。

二、多分支状态流转示例

/**
* 测试抉择分支,针对同一个事件:EVENT1
* if condition == "1", STATE1 --> STATE1
* if condition == "2" , STATE1 --> STATE2
* if condition == "3" , STATE1 --> STATE3
*/
@Test
public void testChoice(){StateMachineBuilder<StateMachineTest.States, StateMachineTest.Events, Context> builder = StateMachineBuilderFactory.create();
  builder.internalTransition()
  .within(StateMachineTest.States.STATE1)
  .on(StateMachineTest.Events.EVENT1)
  .when(checkCondition1())
  .perform(doAction());
  builder.externalTransition()
  .from(StateMachineTest.States.STATE1)
  .to(StateMachineTest.States.STATE2)
  .on(StateMachineTest.Events.EVENT1)
  .when(checkCondition2())
  .perform(doAction());
  builder.externalTransition()
  .from(StateMachineTest.States.STATE1)
  .to(StateMachineTest.States.STATE3)
  .on(StateMachineTest.Events.EVENT1)
  .when(checkCondition3())
  .perform(doAction());

  StateMachine<StateMachineTest.States, StateMachineTest.Events, Context> stateMachine = builder.build("ChoiceConditionMachine");
  StateMachineTest.States target1 = stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new Context("1"));
  Assert.assertEquals(StateMachineTest.States.STATE1,target1);
  StateMachineTest.States target2 = stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new Context("2"));
  Assert.assertEquals(StateMachineTest.States.STATE2,target2);
  StateMachineTest.States target3 = stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new Context("3"));
  Assert.assertEquals(StateMachineTest.States.STATE3,target3);
  }

能够看到,编写一个多分支的状态机也是十分简单明了的。

三、通过状态机反向生成 PlantUml 图

没想到吧,还能通过代码定义好的状态机反向生成 plantUML 图,实现状态机的可视化。(能够用图谈话,和产品比照下状态实现的是否正确了。)

四、非凡应用示例

  1. 不满足状态流转条件时的解决
@Test
public void testConditionNotMeet(){StateMachineBuilder<StateMachineTest.States, StateMachineTest.Events, StateMachineTest.Context> builder = StateMachineBuilderFactory.create();
  builder.externalTransition()
  .from(StateMachineTest.States.STATE1)
  .to(StateMachineTest.States.STATE2)
  .on(StateMachineTest.Events.EVENT1)
  .when(checkConditionFalse())
  .perform(doAction());

  StateMachine<StateMachineTest.States, StateMachineTest.Events, StateMachineTest.Context> stateMachine = builder.build("NotMeetConditionMachine");
  StateMachineTest.States target = stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new StateMachineTest.Context());
  Assert.assertEquals(StateMachineTest.States.STATE1,target);
}

能够看到,当 checkConditionFalse()执行时,永远不会满足状态流转的条件,则状态不会变动,会间接返回原来的 STATE1。相干源码在这里:

  1. 反复定义雷同的状态流转
@Test(expected = StateMachineException.class)
public void testDuplicatedTransition(){StateMachineBuilder<StateMachineTest.States, StateMachineTest.Events, StateMachineTest.Context> builder = StateMachineBuilderFactory.create();
  builder.externalTransition()
  .from(StateMachineTest.States.STATE1)
  .to(StateMachineTest.States.STATE2)
  .on(StateMachineTest.Events.EVENT1)
  .when(checkCondition())
  .perform(doAction());

  builder.externalTransition()
  .from(StateMachineTest.States.STATE1)
  .to(StateMachineTest.States.STATE2)
  .on(StateMachineTest.Events.EVENT1)
  .when(checkCondition())
  .perform(doAction());
}

会在第二次 builder 执行到 on(StateMachineTest.Events.EVENT1)函数时,抛出 StateMachineException 异样。抛出异样在 on()的 verify 查看这里,如下:

  1. 反复定义状态机
@Test(expected = StateMachineException.class)
public void testDuplicateMachine(){StateMachineBuilder<StateMachineTest.States, StateMachineTest.Events, StateMachineTest.Context> builder = StateMachineBuilderFactory.create();
  builder.externalTransition()
  .from(StateMachineTest.States.STATE1)
  .to(StateMachineTest.States.STATE2)
  .on(StateMachineTest.Events.EVENT1)
  .when(checkCondition())
  .perform(doAction());

  builder.build("DuplicatedMachine");
  builder.build("DuplicatedMachine");
}

会在第二次 build 同名状态机时抛出 StateMachineException 异样。抛出异样的源码在状态机的注册函数中,如下:

结语

为了不把篇幅拉得过长,在这里无奈具体地横向比照几大支流状态机(Spring Statemachine,Squirrel statemachine 等)和 COLA 的区别,不过基于笔者在 Spring Statemachine 踩过的深坑,目前来看,COLA 状态机的简洁设计适宜用在订单治理等小型状态机的保护,如果你想要在你的我的项目中接入状态机,又不须要嵌套、并行等高级玩法,那么 COLA 是个非常适合的抉择。

我是后端工程师,蛮三刀酱。

继续的更新原创优质文章,离不开你的点赞,转发和分享!

我的惟一技术公众号:后端技术漫谈

正文完
 0