Thoroughly understand the principle of Spring state machine and realize the decoupling of order and logistics

Keywords: Java Design Pattern architecture

This article is excerpted from "design patterns should be learned this way"

1 UML class diagram of state pattern

The UML class diagram of the state pattern is shown in the figure below.

2. Use the status mode to realize the free switching of login status

When we read articles in the community, if we think the articles are well written, we will comment, collect and send them twice in a row. If you are logged in, you can comment and collect these behaviors directly. Otherwise, jump to the login interface and continue to perform the previous actions after login. There are two states involved here: logged in and not logged in; There are two kinds of behavior: comment and collection. The following uses the state mode to implement this logic. The code is as follows.
First create the abstract state role UserState class.

public abstract class UserState {
    protected AppContext context;

    public void setContext(AppContext context) {
        this.context = context;
    }

    public abstract void favorite();

    public abstract void comment(String comment);
}

Then create the login state LogInState class.

public class LoginInState extends UserState {
    @Override
    public void favorite() {
        System.out.println("Collection succeeded!");
    }

    @Override
    public void comment(String comment) {
        System.out.println(comment);
    }
}

Create the unregistered state unregistate class.

public class UnLoginState extends UserState {
    @Override
    public void favorite() {
        this.switch2Login();
        this.context.getState().favorite();
    }

    @Override
    public void comment(String comment) {
        this.switch2Login();
        this.context.getState().comment(comment);
    }

    private void switch2Login() {
        System.out.println("Jump to login page!");
        this.context.setState(this.context.STATE_LOGIN);
    }
}

Create the context role AppContext class.

public class AppContext {
    public static final UserState STATE_LOGIN = new LoginInState();
    public static final UserState STATE_UNLOGIN = new UnLoginState();
    private UserState currentState = STATE_UNLOGIN;
    {
        STATE_LOGIN.setContext(this);
        STATE_UNLOGIN.setContext(this);
    }

    public void setState(UserState state) {
        this.currentState = state;
        this.currentState.setContext(this);
    }

    public UserState getState() {
        return this.currentState;
    }

    public void favorite() {
        this.currentState.favorite();
    }

    public void comment(String comment) {
        this.currentState.comment(comment);
    }
}

Finally, write the client test code.

public static void main(String[] args) {
        AppContext context = new AppContext();
        context.favorite();
        context.comment("comment: Good articles, 360 likes!");
}

The operation results are shown in the figure below.

3 use state machine to realize order state flow control

State machine is an application of state pattern, which is equivalent to an upgraded version of context role. It is widely used in various systems such as workflow or games, such as various workflow engines. It is almost a subset and implementation of state machine, encapsulating the change rules of state. Spring also provides a good solution. The component name in spring is called state machine. State machine helps developers simplify the development process of state control and make the structure of state machine more hierarchical. Next, use the spring state machine to simulate an order state flow process.

3.1 add dependency.

<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-core</artifactId>
    <version>2.0.1.RELEASE</version>
</dependency>

3.2 create the Order entity Order class.

public class Order {
    private int id;
    private OrderStatus status;
    public void setStatus(OrderStatus status) {
        this.status = status;
    }

    public OrderStatus getStatus() {
        return status;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    @Override
    public String toString() {
        return "order number:" + id + ", Order status:" + status;
    }
}

3.3 create order status enumeration class and status transition enumeration class.

/**
 * Order status
 */
public enum OrderStatus {
    //To be paid, to be shipped, to be received, end of order
    WAIT_PAYMENT, WAIT_DELIVER, WAIT_RECEIVE, FINISH;
}

/**
 * Event triggered when the order status changes
 */
public enum OrderStatusChangeEvent {
    //Pay, ship, confirm receipt
    PAYED, DELIVERY, RECEIVED;
}

3.4 add status flow configuration.

/**
 * Order state machine configuration
 */
@Configuration
@EnableStateMachine(name = "orderStateMachine")
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderStatusChangeEvent> {
 
    /**
     * Configuration status
     * @param states
     * @throws Exception
     */
    public void configure(StateMachineStateConfigurer<OrderStatus, OrderStatusChangeEvent>     states) throws Exception {
        states
                .withStates()
                .initial(OrderStatus.WAIT_PAYMENT)
                .states(EnumSet.allOf(OrderStatus.class));
    }
 
    /**
     * Configure state transition event relationships
     * @param transitions
     * @throws Exception
     */
    public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderStatusChangeEvent>     transitions) throws Exception {
        transitions
                .withExternal().source(OrderStatus.WAIT_PAYMENT).target(OrderStatus.WAIT_DELIVER)
                .event(OrderStatusChangeEvent.PAYED)
                .and()
                .withExternal().source(OrderStatus.WAIT_DELIVER).target(OrderStatus.WAIT_RECEIVE)
                .event(OrderStatusChangeEvent.DELIVERY)
                .and()
                .withExternal().source(OrderStatus.WAIT_RECEIVE).target(OrderStatus.FINISH)
                .event(OrderStatusChangeEvent.RECEIVED);
    }
 
    /**
     * Persistent configuration
     * In actual use, you can cooperate with Redis and other organizations to perform persistence operations
     * @return
     */
    @Bean
    public DefaultStateMachinePersister persister(){
        return new DefaultStateMachinePersister<>(new StateMachinePersist<Object, Object,             Order>() {
            @Override
            public void write(StateMachineContext<Object, Object> context, Order order) throws             Exception {
                //There is no persistence operation here
            }
 
            @Override
            public StateMachineContext<Object, Object> read(Order order) throws Exception {
                //The status in the Order is directly obtained here. In fact, there is no persistent read operation
                return new DefaultStateMachineContext(order.getStatus(), null, null, null);
            }
        });
    }
}

3.5 add order status listener.

@Component("orderStateListener")
@WithStateMachine(name = "orderStateMachine")
public class OrderStateListenerImpl{
 
    @OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
    public boolean payTransition(Message<OrderStatusChangeEvent> message) {
        Order order = (Order) message.getHeaders().get("order");
        order.setStatus(OrderStatus.WAIT_DELIVER);
        System.out.println("Payment, state machine feedback:" + message.getHeaders().toString());
        return true;
    }
 
    @OnTransition(source = "WAIT_DELIVER", target = "WAIT_RECEIVE")
    public boolean deliverTransition(Message<OrderStatusChangeEvent> message) {
        Order order = (Order) message.getHeaders().get("order");
        order.setStatus(OrderStatus.WAIT_RECEIVE);
        System.out.println("Delivery, state machine feedback:" + message.getHeaders().toString());
        return true;
    }
 
    @OnTransition(source = "WAIT_RECEIVE", target = "FINISH")
    public boolean receiveTransition(Message<OrderStatusChangeEvent> message){
        Order order = (Order) message.getHeaders().get("order");
        order.setStatus(OrderStatus.FINISH);
        System.out.println("Receiving, state machine feedback information:" + message.getHeaders().toString());
        return true;
    }
}

3.6 create IOrderService interface.

public interface IOrderService {
    //Create new order
    Order create();
    //Initiate payment
    Order pay(int id);
    //Order shipment
    Order deliver(int id);
    //Order receipt
    Order receive(int id);
    //Get all order information
    Map<Integer, Order> getOrders();
}

3.7 application in Service business logic.

@Service("orderService")
public class OrderServiceImpl implements IOrderService {

    @Autowired
    private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;
 
    @Autowired
    private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, Order> persister;
 
    private int id = 1;
    private Map<Integer, Order> orders = new HashMap<>();

    public Order create() {
        Order order = new Order();
        order.setStatus(OrderStatus.WAIT_PAYMENT);
        order.setId(id++);
        orders.put(order.getId(), order);
        return order;
    }

    public Order pay(int id) {
        Order order = orders.get(id);
        System.out.println("Thread Name:" + Thread.currentThread().getName() + " Attempted payment, order No.:" + id);
        Message message = MessageBuilder.withPayload(OrderStatusChangeEvent.PAYED).
setHeader("order", order).build();
        if (!sendEvent(message, order)) {
            System.out.println("Thread Name:" + Thread.currentThread().getName() + " Payment failed, Abnormal status, order No.:" + id);
        }
        return orders.get(id);
    }

    public Order deliver(int id) {
        Order order = orders.get(id);
        System.out.println("Thread Name:" + Thread.currentThread().getName() + " Attempted shipment, order No.:" + id);
        if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEvent.DELIVERY)
.setHeader("order", order).build(), orders.get(id))) {
            System.out.println("Thread Name:" + Thread.currentThread().getName() + " Shipment failed, abnormal status, order No.:" + id);
        }
        return orders.get(id);
    }

    public Order receive(int id) {
        Order order = orders.get(id);
        System.out.println("Thread Name:" + Thread.currentThread().getName() + " Attempted receipt, order No.:" + id);
        if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEvent.RECEIVED)
.setHeader("order", order).build(), orders.get(id))) {
            System.out.println("Thread Name:" + Thread.currentThread().getName() + " Receipt failed, abnormal status, order No.:" + id);
        }
        return orders.get(id);
    }
 

    public Map<Integer, Order> getOrders() {
        return orders;
    }
 
 
    /**
     * Send order status conversion event
     *
     * @param message
     * @param order
     * @return
     */
    private synchronized boolean sendEvent(Message<OrderStatusChangeEvent> message, Order order) {
        boolean result = false;
        try {
            orderStateMachine.start();
            //Attempt to restore state machine state
            persister.restore(orderStateMachine, order);
            //Add latency for thread safety testing
            Thread.sleep(1000);
            result = orderStateMachine.sendEvent(message);
            //Persistent state machine state
            persister.persist(orderStateMachine, order);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            orderStateMachine.stop();
        }
        return result;
    }
}

3.8 write client test code.


@SpringBootApplication
public class Test {
    public static void main(String[] args) {

        Thread.currentThread().setName("Main thread");

        ConfigurableApplicationContext context = SpringApplication.run(Test.class,args);

        IOrderService orderService = (IOrderService)context.getBean("orderService");

        orderService.create();
        orderService.create();

        orderService.pay(1);

        new Thread("Client thread"){
            @Override
            public void run() {
                orderService.deliver(1);
                orderService.receive(1);
            }
        }.start();

        orderService.pay(2);
        orderService.deliver(2);
        orderService.receive(2);

        System.out.println("All order status:" + orderService.getOrders());

    }
}

Through this real business case, I believe the partners have a very deep understanding of the state mode.

Focus on WeChat's official account of Tom architecture and reply to the "design pattern" to get the complete source code.

[recommendation] Tom bomb architecture: 30 real cases of design patterns (with source code attached). Challenging the annual salary of 60W is not a dream

This article is the original of "Tom bomb architecture". Please indicate the source for reprint. Technology lies in sharing, I share my happiness!
If this article is helpful to you, you are welcome to pay attention and praise; If you have any suggestions, you can also leave comments or private letters. Your support is the driving force for me to adhere to my creation. Focus on WeChat official account Tom structure, get more dry cargo!

Posted by LucidParody on Tue, 16 Nov 2021 00:17:19 -0800