Skip to content

Source Selection Algorithm

Igor Miniailo edited this page Feb 28, 2018 · 36 revisions

When the system needs to determine from which Sources the delivery should be done - Source Selection Algorithm (SSA) has to be launched.

The main goal of SSA is to receive the total number of requested SKUs and their quantities, as well as the shipping address (the destination where the products should be shipped to), and based on particular business need which could be configured by merchant (organize delivery from Sources with Highest priority first or Minimal delivery cost etc.) algorithm should provide a list of Source Items with Quantities to deduct per each Source Item.

Based on results provided by SSA, Magento makes a deduction for each Source Item with specified quantity. A merchant can modify provided results adjusting quantities for deduction or even re-assigning sources from which delivery happen. That's why we never save results of Source Selection Algorithm as the results make sense just for given point of time, and can get invalid after some time (when stock data changed on source basis).

Minimal Viable Product (MVP) of MSI will provide Source Deduction at the time when Order has already been placed and Merchant creates Shipping for the given Order. That's why current implementation is dealing with Order object from which all needed data is retrieved - ShippingAlgorithmInterface.

namespace Magento\InventoryShipping\Model;

/**
 * Returns shipping algorithm result for the order (extension point, SPI)
 *
 * @api
 */
interface ShippingAlgorithmInterface
{
    /**
     * @param OrderInterface $order
     * @return ShippingAlgorithmResultInterface
     */
    public function execute(OrderInterface $order): ShippingAlgorithmResultInterface;
}

But that's not flexible as potentially some could want to introduce customization and launch algorithm at the time when Order is being placed. For example, on fronted when customer proceeds to checkout. The goal of this move is to provide more accurate shipping cost to charge a customer. In this case Order object is not created yet, thus we can't use it as input data for SSA, the system should deal with Quote object instead.

Taking into account that there could be at least two valid business cases when SSA launched, and the source of data is different (Order and Quote) - it makes sense to introduce a new layer of Abstraction and make the algorithm use abstract data container, but not particular Magento entity.

/**
 * Request products in a given Qty to be delivered to Shipping Address
 */
interface InventoryRequestInterface
{
    /**
     * @return \Magento\InventoryShipping\Api\Data\ShippingAddressInterface
     */
    public function getShippingAddress();

    /**
     * @return \Magento\InventoryShipping\Api\Data\ItemRequestInterface[]
     */
    public function getItems();
}

/**
 * Represents requested quantity for particular product
 */
interface ItemRequestInterface
{
    /**
     * Requested SKU
     *
     * @return string
     */
    public function getSku();

    /**
     * Requested Product Quantity
     *
     * @return float
     */
    public function getQty();
}

After these changes Source Selection Algorithm service should be adjusted like this:

/**
 * Returns source selection algorithm result for given Inventory Request 
 *
 * @api
 */
interface SourceSelectionServiceInterface
{
    /**
     * @param InventoryRequestInterface $inventoryRequest
     * @param string $algorithmCode    BY_PRIORITY | MINIMAL_COST | etc
     * @return SourceSelectionAlgorithmResultInterface
     */
    public function execute(
       InventoryRequestInterface $inventoryRequest,
       string $algorithmCode
    ): SourceSelectionAlgorithmResultInterface;
}

SSA Provider should be adjusted accordingly to return all the registered algorithms

/**
 * Returns the list of Data Interfaces which represent registered SSA in the system
 *
 * @api
 */
interface SourceSelectionAlgorithmProviderInterface
{
    /**
     * @return SourceSelectionAlgorithm[]
     */
    public function execute(): ShippingAlgorithmInterface;
}

/**
 * Data Interface representing particular Source Selection Algorithm
 *
 * @api
 */
interface SourceSelectionAlgorithm
{
    /**
     * @return string
     */
    public function getCode(): string;

    /**
     * @return string
     */
    public function getTitle(): string;
}

To add new Algorithm 3PD should register it in the system. To do so, he needs to provide own implementation for the interface.

/**
 * Returns source selection algorithm result for given Inventory Request 
 * Current interface should be implemented in order to add own Source Selection Method
 *
 * @spi
 */
interface SourceSelectionInterface
{
    /**
     * @param InventoryRequestInterface $inventoryRequest
     * @return SourceSelectionAlgorithmResultInterface
     */
    public function execute(
       InventoryRequestInterface $inventoryRequest
    ): SourceSelectionAlgorithmResultInterface;
}

like this:

namespace Some\Vendor\Namespace\SourceSelection;

/**
 * Minimal Delivery Cost for Merchant algorithm
 *
 * @api
 */
class MinimalDeliveryCost implements SourceSelectionInterface
{
    public function execute(
       InventoryRequestInterface $inventoryRequest
    ): SourceSelectionAlgorithmResultInterface;
    {
        // TODO: Implement execute() method.
    }
}

After that 3PD suppose to provide new SSA to the SourceSelectionServiceInterface implementation to make it aware of all the possible methods. This should be done via DI configuration.

    <type name="Magento\InventoryShipping\Model\SourceSelectionService">
        <arguments>
            <argument name="sourceSelectionMethods" xsi:type="array">
                <item name="priority" xsi:type="string">Magento\InventoryShipping\Model\PriorityBasedAlgorithm</item>
                <item name="minimalDeliveryCost" xsi:type="string">Some\Vendor\Namespace\SourceSelection\MinimalDeliveryCost</item>
            </argument>
        </arguments>
    </type>
class SourceSelectionService implements SourceSelectionServiceInterface
{
    /**
     * @param array
     */
    public function __construct(
        array $sourceSelectionMethods,
        \Magento\Framework\ObjectManagerInterface $objectManager
    ) {
        $this->sourceSelectionMethods = $sourceSelectionMethods;
        $this->objectManager = $objectManager;
    }


    public function execute(
        InventoryRequestInterface $inventoryRequest,
        string $algorithmCode
    ): SourceSelectionAlgorithmResultInterface
    {
       if (!isset($this->sourceSelectionMethods[$algorithmCode])) {
            throw new \LogicException(
                'There is no such Source Selection Algorithm implemented: ' . $algorithmCode
            );
        }
        $sourceSelectionClassName = $this->sourceSelectionMethods[$algorithmCode];

        $sourceSelectionAlgorithm = $this->objectManager->create($sourceSelectionClassName);
        if (false === $sourceSelectionAlgorithm instanceof SourceSelectionInterface) {
            throw new \LogicException(
                $sourceSelectionClassName . ' doesn\'t implement SourceSelectionInterface'
            );
        }
        return $sourceSelectionAlgorithm->execute($inventoryRequest);
    }
}

MSI Documentation:

  1. Technical Vision. Catalog Inventory
  2. Installation Guide
  3. List of Inventory APIs and their legacy analogs
  4. MSI Roadmap
  5. Known Issues in Order Lifecycle
  6. MSI User Guide
  7. DevDocs Documentation
  8. User Stories
  9. User Scenarios:
  10. Technical Designs:
  11. Admin UI
  12. MFTF Extension Tests
  13. Weekly MSI Demos
  14. Tutorials
Clone this wiki locally