Broadleaf Microservices

Adding a Checkout Workflow Activity

Getting to Know the Out-of-box Checkout Workflow

Broadleaf’s checkout workflow is primarily responsible for verifying that a cart is ready for checkout and completing any additional steps to finalize the checkout. The workflow is made up of activities that implement the CheckoutWorkflowActivity interface and are registered as ordered Spring beans. The following activities make up our out-of-box checkout workflow:

When writing checkout workflow activities, you need to be very aware of any work that will need to be rolled-back/undone when a failure is encountered. This is why we prefer to register all validation activities at the beginning of the workflow and execute payment transactions at the end of the workflow. This ordering allows us to get through most of the workflow without worrying about any work that needs to be rolled back.

You may notice that the workflow doesn’t include activities like an "OrderConfirmationEmailActivity". This is very intentional, because the checkout workflow should contain only the minimum work required to finalize a checkout. Any extra activities just introduce more potential points of failure, which means less completed checkouts. Whenever possible, create a listener for the CheckoutCompletionEvent (sent after checkout is complete) instead of a workflow activity to reduce risk of failed checkouts.

Adding a New Activity

To introduce a new workflow activity, you just need to create a class extending the CheckoutWorkflowActivity interface and register a bean for the class.

package com.mycompany.cartops;

public class MyCheckoutWorkflowActivity implements CheckoutWorkflowActivity {

    @Override
    public Cart execute(@lombok.NonNull Cart cart,
            @lombok.NonNull String requestId,
            @Nullable ContextInfo contextInfo) {

        // Do your work here, throwing a CheckoutWorkflowActivityException to initiate a rollback if something went wrong

        return cart;
    }

    @Override
    public Cart rollback(@lombok.NonNull Cart cart,
            @lombok.NonNull String requestId,
            @Nullable ContextInfo contextInfo) {
        return cart; // If nothing needs to be rolled back, simply return the cart
    }
}
package com.mycompany.cartops;

@Configuration
public class CartOperationsServiceAutoConfiguration {

        @Bean
        @Order(7000)
        CheckoutWorkflowActivity myCheckoutWorkflowActivity() {
            return new MyCheckoutWorkflowActivity();
        }

}
Note
Make sure to consider this bean’s order compared to the existing activities. In general, the ordering of your activities doesn’t matter too much for a successful execution, but it’s worth considering how the activity’s ordering may affect a rollback.

Handling a Workflow Rollback

When a workflow failure is encountered (usually cased by a CheckoutWorkflowActivityException), a rollback is initiated by sending a CheckoutRollbackEvent.

If your activity included work that needs to be rolled back due to a failure in the workflow, then you’ll need to listen for this event and handle the rollback via the CheckoutWorkflowActivity#rollback(…​) method. For an example, see the PaymentConfirmationRollbackEventListener.

Note

Make sure to be mindful of the cart’s versioning when handling rollbacks. When the checkout fails, a version of the cart is returned from the API and can be acted against. Therefore, if you need update the cart as a part of the rollback, then you may have an out-of-date version, or the rollback may cause the API-returned version become out-of-date. There are two ways around this issue:

  1. Store state changes else where so that the cart doesn’t have to be updated (this is done for inventory reservations).

  2. Leverage a syncronous rollback so that all rollback activities are completed before the cart is returned via the API.