Understanding Commerce Pipelines

6/15/2020 Sitecore Commerce Sitecore Commerce Engine Pipelines

This blog goes over the basics of a Sitecore Pipeline and how they are used within the Sitecore Commerce Engine

OMG look how many ppl have commented

OMG, I made another blog! That is 1 more than my average! Okay, down to business. Let's talk Sitecore Engine and creating a custom pipeline to suit your Commerce needs.

What is the purpose of a pipeline?

Pipelines within the Sitecore Commerce engine are basically how things get done. Generally, the Storefront solution hits the Engine through the Microsoft OData RESTful API. The endpoint configured on the Engine side will interpret the call and eventually hit some sort of pipeline. I gather Sitecore configured the pipeline setup for extensibility reasons so we can easily modify existing pipelines, or create our own. So if there is any sort of custom functionality you need to implement, you will generally create yourself a pipeline to suit your needs.

A pipeline contains an opening argument and the expected return object. Think of it as you are executing a method. You have a parameter you want to pass to this method and the return variable you expect to get back. Below is an example of the writeup of a pipeline:

  public class GetGiftCardPipeline : CommercePipeline<string, GiftCard>, IGetGiftCardPipeline
    public GetGiftCardPipeline(
      IPipelineConfiguration configuration,
      ILoggerFactory loggerFactory)
      : base((IPipelineConfiguration) configuration, loggerFactory)

So this particular pipeline expects a string as its opening argument and will return a GiftCard object

Pipeline Components

Each pipeline is comprised of a set of blocks, or classes, containing a small (hopefully) set of code to suit a small function within the pipeline. They are the building "blocks" to a pipeline.

Here is an example of what a Pipeline block may look like:

  public class CreateGiftCardBlock : PipelineBlock<CreateGiftCardArgument, GiftCard>
    public override Task<GiftCard> Run(
      CreateGiftCardArgument arg,
      IPipelineExecutionContext context)
      Condition.Requires(arg).IsNotNull(this.Name + ": The block's argument can not be null");
      Condition.Requires(arg.OrderId).IsNotNullOrEmpty(this.Name + ": The order id can not be null");
      string str = Guid.NewGuid().ToString("N", (IFormatProvider) CultureInfo.InvariantCulture);
      GiftCard giftCard = new GiftCard();
      giftCard.Id = CommerceEntity.IdPrefix() + str;
      giftCard.Name = string.Format("{0:c} GiftCard", (object) arg.Amount.Amount);
      giftCard.Balance = arg.Amount;
      giftCard.ActivationDate = DateTimeOffset.UtcNow;
      giftCard.OriginalAmount = arg.Amount;
      giftCard.FriendlyId = str;
      giftCard.GiftCardCode = str;
      giftCard.Order = new EntityReference(arg.OrderId, "");
      GiftCard result = giftCard;
      if (!string.IsNullOrEmpty(arg.CustomerId))
        result.Customer = new EntityReference(arg.CustomerId, "");
      result.SetComponent((Component) new ListMembershipsComponent()
        Memberships = (IList) new List()
          CommerceEntity.ListName() ?? "",
          CommerceEntity.ListName() ?? ""
      return Task.FromResult(result);

So there are a few items in this snippet of code to consider that makeup the buildup of this block. First, each block must inherit from PipelineBlock which takes in two generic parameter. The first generic parameter is what will be the first argument in the Run function while the other is the pipeline context. The second Generic Parameter is what is to be returned from the function.

NOTE: In order for the pipeline structure to work, the pipeline block to follow the current block MUST accept the returned object from the current block in its signature. For example, This Run method in this block returns a GiftCard object. If there is a pipeline block to follow, it must inherit from PipelineBlock<GiftCard, WhateverYouWantToReturn> allowing a nice flow for the pipeline. If this is the last block in the pipeline, the return object (in this case, GiftCard) MUST be the expected return type the overall pipeline provides.

From an abstract view, this is what a pipeline could look like

Pipeline Foo accepts argumentA to be passed and returns argumentB

  • Pipeline Foo is called with argumentA
  • Block1 accepts argumentA and returns argumentC
  • Block2 accepts argumentC and returns argumentD
  • Block3 accepts argumentD and returns argumentB
  • Pipeline Foo returns argumentB

So, it technically does not matter what the blocks are returning as long as the following block opening argument matches the previous blocks return argument, and the last block returns the same argument as the pipeline. In fact, you can even have another pipeline called within an existing pipeline so long as that pipelines opening argument matches the previous blocks returned argument and the block to follow this sub-pipeline has an opening argument that matches the return argument from the pipeline. Here is an example of how this could work:

Pipeline Foo accepts argumentA to be passed and returns argumentB

Pipeline Bar accepts argumentD to be passed and returns argumentF

  • Pipeline Foo is called with argumentA
  • Block1 accepts argumentA and returns argumentC
  • Block2 accepts argumentC and returns argumentD
  • Pipeline Bar is called with argumentD
    • Block4 accepts argumentD and returns argumentE
    • Block5 accepts argumentE and returns argumentF
    • Pipeline Bar returns argumentF
  • Block3 accepts argumentF and returns argumentB
  • Pipeline Foo returns argumentB