Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AE-157 Documentation for Strategy Resolver #3047

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
4 changes: 3 additions & 1 deletion _data/sidebars/dg_dev_sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -910,7 +910,7 @@ entries:
- title: Internationalization and multi-store
nested:
- title: Adding stores in a multi-database setup
url: /docs/dg/dev/internationalization-and-multi-store/adding-stores-in-a-multi-database-setup.html
url: /docs/dg/dev/internationalization-and-multi-store/adding-stores-in-a-multi-database-setup.html
- title: Handling internationalization
url: /docs/dg/dev/internationalization-and-multi-store/handling-internationalization.html
- title: Managing glossary keys
Expand Down Expand Up @@ -1670,6 +1670,8 @@ entries:
nested:
- title: Simulating deployments locally
url: /docs/dg/dev/miscellaneous-guides/simulating-deployments-locally.html
- title: Strategy resolvers
url: /docs/dg/dev/miscellaneous-guides/strategy-resolvers.html
- title: "Tutorial: Hello world"
url: /docs/dg/dev/miscellaneous-guides/tutorial-hello-world.html
- title: Using database transactions
Expand Down
4 changes: 3 additions & 1 deletion docs/dg/dev/architecture/programming-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ related:
link: docs/dg/dev/architecture/modules-and-application-layers.html
- title: Code buckets
link: docs/dg/dev/architecture/code-buckets.html
- title: Strategy resolvers
link: docs/dg/dev/miscellaneous-guides/strategy-resolvers.html
---

Having covered the main architectural concepts of the Spryker Commerce OS, frontend, modularity, and the application and software layers, this document dives deeper into these approaches and explains the main software and coding concepts in Spryker. There are seven main software concepts in Spryker.
Expand Down Expand Up @@ -48,7 +50,7 @@ The Client is not needed for every module: some modules have it, and some do not

In Spryker, a *Plugin* extends certain functionality in a module, like a Calculation stack when placing an order. The Calculation module is an abstract idea and should not depend on other modules to calculate the final price. Thus, it exposes an interface with the needed method to be implemented by other modules. When there is another module involved in calculating the final price, it implements this interface and does the calculation internally. This implementation is called Plugin. This Plugin, along with others, can then be injected into the Calculation module. In the end, the Calculation module calculates the final price by running the exposed method from all the injected Plugins.

Plugins are used in many different places in Spryker. It is a great way to extend functionality and properly manage the modularity and dependencies. A module can implement many different Plugins and can have many different plugins injected into it. Plugins are located inside the Communication layer.
Plugins are used in many different places in Spryker. They're a great way to extend functionality and manage the modularity and dependencies. A module can implement many different plugins and have many different plugins injected into it. Plugins are located inside the Communication layer. In case of widely overarching functionality where multiple contexts appear, consider using [strategy resolvers](/docs/dg/dev/miscellaneous-guides/strategy-resolvers.html) in combination with plugins.

## Dependency provider

Expand Down
1 change: 1 addition & 0 deletions docs/dg/dev/miscellaneous-guides/miscellaneous-guides.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ Miscellaneous guides are a collection of guides that don't fit under any specifi
* [Simulating deployments locally](/docs/dg/dev/miscellaneous-guides/miscellaneous-guides.html)
* [Transfer big databases between environments](/docs/dg/dev/miscellaneous-guides/transfer-big-databases-between-environments.html)
* [Tutorial: Hello World](/docs/dg/dev/miscellaneous-guides/tutorial-hello-world.html)
* [Strategy resolvers](/docs/dg/dev/miscellaneous-guides/strategy-resolvers.html)
* [Using database transactions](/docs/dg/dev/miscellaneous-guides/using-database-transaction.html)
153 changes: 153 additions & 0 deletions docs/dg/dev/miscellaneous-guides/strategy-resolvers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
---
title: Strategy resolvers
description: This document
last_updated: Feb 6, 2025
template: howto-guide-template
redirect_from:
- /docs/dg/dev/architecture/programming-concepts.html

---

Context-based dependency resolution using a strategy resolver is used to manage complex workflows spanning multiple modules. This lets you define multiple dependencies and dynamically select a needed dependency at runtime based on business logic.

Strategy resolvers are introduced because some workflows require multiple plugin-stack variations that need to be switched in sync.

The approach is backward compatible. It's applied only to selected plugin-stacks that require this enhancement, and only customizations may need updates.

Use strategy resolvers in the following cases:
- To handle multiple plugin-stack configurations dynamically
- For workflows that spans multiple modules and requires synchronized behavior
- To allow context-specific behavior while keeping plugin-stack resolution flexible

Don't strategy resolvers in the following cases:
- A single plugin-stack is sufficient for your module, so consider using regular strategy plugin stack approach
- Context-based, wide-spread synchronization is not needed

## Context definition

Each module that provides a context must define it as an interface constant with a clear and verbose description of its purpose.

```php
<?php

namespace Spryker\Shared\CheckoutExtension;

interface CheckoutExtensionContextsInterface
{
/**
* Specification:
* - Defines the Checkout Context, which applies when a customer initiates a new order.
* - Enables workflows to distinguish new order processing from other order-related operations.
* - The order is being created for the first time, not modified.
* - Includes standard checkout steps such as cart review, payment, and confirmation.
* - Enables plugins to execute context-specific logic related to new order creation.
*
* @api
*
* @var string
*/
public const CONTEXT_CHECKOUT = 'checkout';
```

The following section defines how to configure a fallback in case context is missing.

## Strategy resolver configuration in the factory

The factory is responsible for the following:
* Creating the strategy resolver
* Mapping all supported contexts to plugin stacks
* Choosing fallbacks

To ensure type safety, the generic type of the strategy resolver must be well-defined.

The following example defines plugin stacks for `Checkout` and `Order Amendment` contexts while keeping `Checkout` context as fallback for backward compatibility.

```php
/**
* @return \Spryker\Shared\Kernel\StrategyResolverInterface<list<\Spryker\Zed\CheckoutExtension\Dependency\Plugin\CheckoutPreConditionPluginInterface>>
*/
public function createCheckoutPreConditionPluginStrategyResolver(): StrategyResolverInterface
{
return new StrategyResolver(
[
CheckoutExtensionContextsInterface::CONTEXT_CHECKOUT => $this->getProvidedDependency(CheckoutDependencyProvider::CHECKOUT_PRE_CONDITIONS, static::LOADING_LAZY),
SalesOrderAmendmentExtensionContextsInterface::CONTEXT_ORDER_AMENDMENT => $this->getProvidedDependency(CheckoutDependencyProvider::CHECKOUT_PRE_CONDITIONS_FOR_ORDER_AMENDMENT, static::LOADING_LAZY),
],
CheckoutExtensionContextsInterface::CONTEXT_CHECKOUT, // fallback context
);
}
```

Instead of injecting a single plugin-stack, inject the strategy resolver into the model.

```php
/**
* @return \Spryker\Zed\Checkout\Business\Workflow\CheckoutWorkflowInterface
*/
public function createCheckoutWorkflow()
{
return new CheckoutWorkflow(
$this->getOmsFacade(),
$this->createCheckoutPreConditionPluginStrategyResolver(),
$this->createCheckoutSaveOrderPluginStrategyResolver(),
$this->createCheckoutPostSavePluginStrategyResolver(),
$this->createCheckoutPreSavePluginStrategyResolver(),
);
}
```

## Context-based resolution in models

When a model executes logic, it must explicitly specify the context when requesting a plugin stack.

```php
class CheckoutWorkflow implements CheckoutWorkflowInterface
{
/**
* @var \Spryker\Shared\Kernel\StrategyResolverInterface<list<\Spryker\Zed\CheckoutExtension\Dependency\Plugin\CheckoutPreConditionPluginInterface>>
*/
protected $preConditionPluginStrategyResolver;

/**
* ...
* @param \Spryker\Shared\Kernel\StrategyResolverInterface<list<\Spryker\Zed\CheckoutExtension\Dependency\Plugin\CheckoutPreConditionPluginInterface>> $preConditionPluginStrategyResolver
* ...
*/
public function __construct( ... StrategyResolverInterface $preConditionPluginStrategyResolver ...) {
...
$this->preConditionPluginStrategyResolver = $preConditionPluginStrategyResolver;
...
}

protected function checkPreConditions(QuoteTransfer $quoteTransfer, CheckoutResponseTransfer $checkoutResponse)
{
$isPassed = true;

$quoteProcessFlowName = $quoteTransfer->getQuoteProcessFlow()?->getNameOrFail(); // the context resolution depends on the needed business logic; this is just an example
$preConditionPlugins = $this->preConditionPluginStrategyResolver->get($quoteProcessFlowName);

foreach ($preConditionPlugins as $preConditionPlugin) {
$isPassed &= $preConditionPlugin->checkCondition($quoteTransfer, $checkoutResponse);
}

return (bool)$isPassed;
}
}

```

### Lazy plugin-stack resolution and performance

When there're multiple plugin-stacks with a big number (10+) of plugins, performance may be an issue. To solve this, lazy loading is used in the container system.

The strategy resolver solves lazy loading in the background, ensuring that the correct plugin-stack is resolved only when requested.

The lazy loading can be used on demand even without strategy resolver, however fetching needs to be triggered on demand.

```php
$this->getProvidedDependency(CheckoutDependencyProvider::CHECKOUT_PRE_CONDITIONS, static::LOADING_LAZY);
```

## Debugging and logging

The strategy resolver uses a generic template directive, allowing developers to inspect available plug-in stacks. Everything remains typed, ensuring full compatibility with tools like Xdebug.
Loading