mardi 7 juillet 2020

How to handle Order status changes with State Design Pattern

Currently, I'm working on the Order Microservice, where I have two methods related to order status changes store(Order order) and updateStatus(int orderId, String status), I'll explain later.

There are four states of the Order:

Waiting -> Expired

Waiting -> Canceled

Waiting -> Purchased

Purchased -> Canceled

I've provided the state flow diagram below, to make it clear (hopefully)

Order status diagram flow

When the order created then the status will be "Waiting", if the user has paid it then the status becomes "Purchased", if the buyer or product owner cancel it then the status becomes "Canceled", and if the time exceeded then the status becomes "Expired".

For every microservice I want to work on, I'll be implementing Gang Of Four design pattern if possible, and for the order status I decided to implement state design pattern since it is related and from what I refer in many blogs, like in the document status stuff (DRAFT, ON REVIEW, etc), audio player stuff (PAUSED, PLAYED, etc) and so on.

This is what I've done:

Base State

public interface OrderStatus {
    void updateStatus(OrderContext orderContext);
}

Waiting state

public class WaitingState implements OrderStatus {
    // omited for brevity    
    
    @Override
    public void updateStatus(OrderContext orderContext) {
        orderContext.getOrder().setStatus("Waiting");
    }
}

Purchased State

public class PurchasedState implements OrderStatus {
    // omited for brevity

    @Override
    public void updateStatus(OrderContext orderContext) {
        orderContext.getOrder().setStatus("Purchased");
    }
}

Other states

..

Context:

public class OrderContext {
    private OrderStatus currentStatus;
    private Order order;

    public OrderContext(OrderStatus currentStatus, Order order) {
        this.currentStatus = currentStatus;
        this.order = order;
    }

    public void updateState() {
        currentStatus.updateStatus(this);
    }

    public OrderStatus getCurrentStatus() {
        return currentStatus;
    }

    public void setCurrentStatus(OrderStatus currentStatus) {
        this.currentStatus = currentStatus;
    }

    public Order getOrder() {
        return order;
    }

    public void setOrder(Order order) {
        this.order = order;
    }
}

The client is the OrderServiceImpl which I called from OrderController.

public class OrderServiceImpl implements OrderService {
    // omited for brevity
    
    @Override
    public Order store(Order order) {
        WaitingState state = WaitingState.getInstance();
        OrderContext context = new OrderContext(state, order);
        context.updateState();
    
        // do other stuff
    }

    @Override    
    public void updateStatus(int orderId, String status) {
        Order order = orderRepository.findById(id);
        
        // but how about this?? this still requires me to use if/else or switch
    }
}

As you can see, I can do it while creating the Order in the store(Order order) method, but I have no idea to do it in updateStatus(int orderId, String status) since it is still required to check status value to use the right state.

switch (status) {
    case "EXPIRED": {
        ExpiredState state = ExpiredState.getInstance();
        OrderContext context = new OrderContext(state, order);
        context.updateState();

        // do something
        break;
    }
    case "CANCELED": {
        CanceledState state = CanceledState.getInstance();
        OrderContext context = new OrderContext(state, order);
        context.updateState();

        // do something
        break;
    }
    // other case
    default:
        // do something
        break;
}

The exact reason to implement the state design pattern is to minimize "the switch stuff/hardcoded checking" and the flexibility for adding more state without breaking current code (Open/Close principle), but maybe I'm wrong, maybe I'm lack of knowledge, maybe I'm too naive to decide to use this pattern. But at the end of the day, I found out that I still need to use the switch stuff to use the state pattern.

Then, what's the right way to handle the order status changes?

Aucun commentaire:

Enregistrer un commentaire