Elements of a Commerce Engine plugin

Abstract

Overview of key object classes that Commerce micro-service plugins contribute to.

The Commerce Core infrastructure defines the various core classes that compose a Commerce plugin.

This topic introduces some of the key objects classes that Commerce micro-service plugins commonly contribute (the Commerce Core overview topic provides a more comprehensive list of Commerce core objects and concepts).

Note

When you install the Visual Studio extension packaged as part of the Commerce.Engine.SDK, the Sitecore.Commerce.Plugin.Sample project template provides samples for most of these common classes.

Commerce plugins can contribute entities. Commerce entities are based on a core CommerceEntity class, and are designed to directly represent key business concepts inherent to e-commerce. For example, the Sitecore.Commerce.Plugin.Orders plugin contributes the Orders entity and the SalesActivity entity; the Sitecore.Commerce.Plugin.Carts plugin contributes the carts entity, and so on.

An entity class is stored as a single unit in a persistent storage. There is no need to manage any database schema to store your custom defined entity.

A commerce entity has a unique identifier that you use to retrieve it using the Service API.

The Commerce Core defines a component class which is a basic structure that enables compositional extensibility . Commerce plugins can contribute components as a way to extend commerce entities. For example, the availability plugin Sitecore.Commerce.Plugin.Availability contributes the itemAvailabilityComponent that extends the SellableItem entity, which is part of the Sitecore.Commerce.Plugin.Catalog. You typically associate a component to an entity by hooking into a pipeline.

The following shows an example of the itemAvailabilityComponent:

namespace Sitecore.Commerce.Plugin.Availability
{
    using System;
    using Sitecore.Commerce.Core;
    /// <summary>
    /// The item availability component.
    /// </summary>
    public class ItemAvailabilityComponent : Component
    {
        /// <summary>
        /// Gets or sets the item identifier.
        /// </summary>
        /// <value>
        /// The item identifier.
        /// </value>
        public string ItemId { get; set; }
        /// <summary>
        /// Gets or sets the available quantity.
        /// </summary> 
       /// <value>
        /// The available quantity.
        /// </value>
        public decimal AvailableQuantity { get; set; }
        /// <summary>
        /// Gets or sets the available date.
        /// </summary>
        /// <value>
        /// The available date.
        /// </value>
        public DateTimeOffset AvailableDate { get; set; }
        /// <summary>
        /// Gets or sets a value indicating whether this instance is available.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is available; otherwise, <c>false</c>.
        /// </value>
        public bool IsAvailable { get; set; }
        /// <summary>
        /// Gets or sets the availability expires.
        /// </summary>
        /// <value>
        /// The availability expires.
        /// </value>
        public DateTimeOffset AvailabilityExpires { get; set; }
    }
}

Commerce plugins can contribute models, which are POCO classes that are reusable inside entities and components. You can use models to form and present data that is returned as part of a command response or API request response.

The following shows an example of the CreateOrder model class:

namespace Sitecore.Commerce.Plugin.Orders
{    using Sitecore.Commerce.Core;
    /// <summary>
    /// Defines the created order model.
    /// </summary>
    /// <seealso cref="Sitecore.Commerce.Core.Model" />
    public class CreatedOrder : Model
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="CreatedOrder"/> class.
        /// </summary>
        public CreatedOrder()
        {
            this.OrderId = string.Empty;
        }
        /// <summary>
        /// Gets or sets the order identifier.
        /// </summary>
        /// <value>
        /// The order identifier.
        /// </value>
        public string OrderId { get; set; }
    }
}

Controllers expose Commerce functionality implemented as commands in plugins. For example, if you define a custom command, and you want to expose this command through a REST API, you must define a custom controller action. Controller classes are ODATA-compliant and based on a standard ASP.NET Core model-view-controller (MVC) pattern.

Plugins define commands, which act like the outward facing API of Sitecore XC. A command class defines and encapsulates all information required to perform an action or trigger an event when invoked.

Commands can also trigger actions internally, within the Commerce Engine, to run other pipelines or to take other actions. For example, a call to get a sellable item using a controller could invoke the GetSellableItemCommand, which then runs the GetSellableItem pipeline.

The Commerce Core defines a pipeline framework that supports extensibility. In a Commerce plugin, pipelines act as containers for business logic. The pipeline class itself is typically lightweight and mainly expresses the definition of the pipeline itself. For example, the following shows the ICreateOrderPipeline class, as defined by the Orders plugin:

namespace Sitecore.Commerce.Plugin.Orders
{
    using Sitecore.Commerce.Core;
    using Sitecore.Framework.Pipelines;
    /// <summary>
    /// Defines the create order pipeline.
    /// </summary>
    [PipelineDisplayName(OrdersConstants.Pipelines.CreateOrder)]
    public interface ICreateOrderPipeline :IPipeline<CartEmailArgument, Order, CommercePipelineExecutionContext>
    {
    }
}

Commerce pipelines are composed of blocks. Pipeline blocks are responsible for implementing behaviors, actions and business logic.

You use code to work and extend Commerce Engine pipelines and blocks to meet your specific commerce solution requirements.

Following is an example of the Commerce pipeline block OrderPlacedAssignConfirmationIdBlock, which is responsible to generate the unique ID that is assigned to the order when it is placed.

namespace Sitecore.Commerce.Plugin.Orders
{
    using System.Threading.Tasks;
    using Sitecore.Commerce.Core;
    using Sitecore.Framework.Conditions;
    using Sitecore.Framework.Pipelines;
    using System.Linq;
    using Core.Commands;
    using System;
    using Microsoft.Extensions.Logging;                    
        /// </summary>
        public OrderPlacedAssignConfirmationIdBlock(GenerateUniqueCodeCommand generateUniqueCodeCommand;
        {
           _generateUniqueCodeCommand = generateUniqueCodeCommand;
        }
        public async override Task<Order> Run(Order order, CommercePipelineExecutionContext context)
        {
          Condition.Requires(order).IsNotNull("The order can not be null");
          string uniqueCode = Guid.NewGuid().ToString("N");
          try
          {
            uniqueCode = await _generateUniqueCodeCommand.Process(context.CommerceContext);
          }
          catch(Exception ex)       
          {              
             context.CommerceContext.LogException($"{Name}-UniqueCodeException", ex);
          }
          order.OrderConfirmationId = uniqueCode;
          return order;
       }
    }       
}

The configure.sitecore class is responsible to list, orchestrate, and register block definitions from all pipelines that a plugin defines. The pipeline configuration determines the sequence in which to run each pipeline blocks. When a pipeline starts up, it uses the information within the configure.sitecore class to know which block to run. When you add a new custom block to an existing pipeline or when you create a new pipeline, you must insert your custom pipeline and blocks in the configure.sitecore class so that it is processed in the required sequence.

The following is an example of the configure.sitecore class that is provided with the sample Braintree payment provider plugin (included as part of the Commerce Engine SDK).

namespace Plugin.Sample.Payments.Braintree
{
    using System.Reflection;

    using Microsoft.Extensions.DependencyInjection;

    using Sitecore.Commerce.Core;
    using Sitecore.Commerce.Plugin.Orders;
    using Sitecore.Commerce.Plugin.Payments;
    using Sitecore.Framework.Configuration;
    using Sitecore.Framework.Pipelines.Definitions.Extensions;

    /// <summary>
    /// The payments braintree configure sitecore class
    /// </summary>
    /// <seealso cref="Sitecore.Framework.Configuration.IConfigureSitecore" />
    public class ConfigureSitecore : IConfigureSitecore
    {
        /// <summary>
        /// The configure services.
        /// </summary>
        /// <param name="services">
        /// The services.
        /// </param>
        /// 
        public void ConfigureServices(IServiceCollection services)
        {
            var assembly = Assembly.GetExecutingAssembly();
            services.RegisterAllPipelineBlocks(assembly);

            services.Sitecore().Pipelines(config => config
                .ConfigurePipeline<IGetClientTokenPipeline>(d =>
                {
                    d.Add<GetClientTokenBlock>().After<Sitecore.Commerce.Plugin.Payments.GetClientTokenBlock>();
                })
                .ConfigurePipeline<ICreateOrderPipeline>(d =>
                {
                    d.Add<CreateFederatedPaymentBlock>().Before<CreateOrderBlock>();
                })
                .ConfigurePipeline<IReleaseOnHoldOrderPipeline>(d =>
                {
                    d.Add<UpdateFederatedPaymentBlock>().After<ValidateOnHoldOrderBlock>();
                })
                .ConfigurePipeline<ISettleSalesActivityPipeline>(d =>
                {
                    d.Add<SettleFederatedPaymentBlock>().After<ValidateSalesActivityBlock>()
                     .Add<UpdateOrderAfterFederatedPaymentSettlementBlock>().After<SettleFederatedPaymentBlock>();
                })
                .ConfigurePipeline<IRefundPaymentsPipeline>(d =>
                {
                    d.Add<RefundFederatedPaymentBlock>().Before<PersistOrderBlock>();
                })
                .ConfigurePipeline<ICancelOrderPipeline>(d =>
                {
                    d.Add<VoidCancelOrderFederatedPaymentBlock>().After<GetPendingOrderBlock>();
                })

                .ConfigurePipeline<IRunningPluginsPipeline>(c => { c.Add<RegisteredPluginBlock>().After<RunningPluginsBlock>(); }));

            services.RegisterAllCommands(assembly);
        }
    }
}

A Commerce Engine policy is a group of settings that affects the behavior or functionality of a feature, or a Commerce micro-service, within the Commerce Engine. Commerce policies are typically driven by business needs and often affect storefront behaviors. Policies are heavily cached and rarely change their values outside of a deployment scenario. You configure policy settings using a .JSON configuration file. A bootstrapping process initializes the configuration to a centralized policy store.

The following shows an example of a policy definition class (CartCouponsPolicy):

using Sitecore.Commerce.Core;
namespace Sitecore.Commerce.Plugin.Coupons
{
    /// <summary>
    /// The CartCouponsPolicy.
    /// </summary>
    public class CartCouponsPolicy : QualifyPolicy
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="CartCouponsPolicy"/> class.
        /// </summary>
        public CartCouponsPolicy()
        {            MaxCouponsInCart = 5;
        }
        /// <summary>
        /// Gets or sets the maximum coupons in cart.
        /// </summary>
        /// <value>
        /// The maximum coupons in cart.
        /// </value>
        public int MaxCouponsInCart { get; set; }
    }
}

Commerce plugins can contribute minions. Commerce minions are worker processes, implemented as basic units of work to perform specific tasks in a process.