top of page
Search
Writer's pictureYassine Alahyane

Sitecore Commerce - Avoiding Duplicated Code with Commanders



How to avoid duplicating code, using Commerce Commanders

October 31st 2021


I've seen many people asking what pattern to use in Sitecore Commerce to avoid duplicating code across different blocks and pipelines.


When the said code involves Business Logic, I would recommend creating a new Pipeline, but in some scenarios, especially for things like error handling and non-business logic type of code, a new Pipeline might seem a bit overkill.


That's where the Commanders come to the rescue.

In this previous Blog Post, I shared how we can use OOB Sitecore Commerce Commander to reduce the number of dependencies we need to inject and simplify our code:


In this post, we'll discuss another aspect where the Commerce Commander can becomes very useful: Common and reusable code.


If you take a closer look at the OOB Commerce Commander you'll notice it comes with some most common methods such as: GetEntity, PersistEntity, UpdateEntity, SetEntityCacheEntry and so on. However since this commander lives in Sitecore.Commerce.Core plugin, it can only manipulate base CommerceEntity objects. You can't use to perform actions on Orders, Carts or Customers for example, unless you're manipulating base properties.


Let's use the example below to illustrate how we can create custom commanders to avoid code duplication.


In this example, we have an export order minion responsible for sending the pending orders to the CRM.

This minion invokes several pipelines and several blocks.

At anytime during this minion lifecycle, if any handled or unhandled exception occurs we need to perform these exact same steps:

  1. Add the error message to the order Messages Component

  2. Set the order status to Problem

  3. Add the order to 'ProblemOrders' List

  4. Persist the order

  5. Remove the order from 'PendingOrders' List

The goal is to avoid duplicating this code over and over.


To achieve this goal we need to:


1) Create a 'CustomOrdersCommander' in our custom Orders Plugin. This commander inherits from the OOB 'CommerceCommander' and comes with a 'HandleOrderExportFailureAsync' method to perform the above repetitive steps:



public class CustomOrderCommander : CommerceCommander
    {
        public CustomOrderCommander(IServiceProvider serviceProvider) : base(serviceProvider)
        {
        }

        public async Task HandleOrderExportFailureAsync(CommercePipelineExecutionContext context, Order order, string errorCode, string errorMessage)
        {
            order.GetComponent<MessagesComponent>().AddMessage(errorCode, errorMessage);
            order.Status = context.GetPolicy<KnownOrderStatusPolicy>().Problem;
            var transientComponent = order.GetComponent<TransientListMembershipsComponent>();
            transientComponent?.Memberships.Add(context.GetPolicy<KnownOrderListsPolicy>().ProblemOrders);
            await this.Pipeline<IPersistEntityPipeline>()
                .Run(new PersistEntityArgument(order), context);
            await this.Pipeline<IRemoveListEntitiesPipeline>()
                .Run(new ListEntitiesArgument(new[] { order.Id }, context.GetPolicy<KnownOrderListsPolicy>().PendingOrders), context);
        }
    }


2) In ConfigureSitecore.cs of our custom Orders Plugin, we need to make sure services.RegisterAllCommands(assembly) is invoked. This is how the custom commander is registered. (CommerceCommander is also a Command as it inherits from CommerceCommand)



public class ConfigureSitecore : IConfigureSitecore { public void ConfigureServices(IServiceCollection services) { var assembly = Assembly.GetExecutingAssembly(); services.RegisterAllCommands(assembly); } }



3) Inject a CustomOrdersCommander instance in any block's constructor that needs it.

Usage example:


public class CustomOrderBlock : PipelineBlock<Order, Order, CommercePipelineExecutionContext>
    {
        private readonly CustomOrderCommander _customOrderCommander;

        public CustomOrderBlock(CustomOrderCommander commerceCommander)
        {
            this._customOrderCommander = commerceCommander;
        }

        public override async Task<Order> Run(Order order, CommercePipelineExecutionContext context)
        {
            try
            {
                //Some custom logic
            }
            catch (Exception ex)
            {
                //Call the Commander method to handle errors
                await this._customOrderCommander.HandleOrderExportFailureAsync(context, order, "500", ex.Message);
            }

            return order;
        }
    }

As you can see, Commerce Commanders can be very helpful keeping your code clean and maintainable.


I hope you find this post helpful.

Feel free to leave comments or questions.





52 views0 comments

Recent Posts

See All

Comments


bottom of page