diff --git a/code_samples/background_tasks/src/Dispatcher/SomeClassThatSchedulesExecutionInTheBackground.php b/code_samples/background_tasks/src/Dispatcher/SomeClassThatSchedulesExecutionInTheBackground.php new file mode 100644 index 0000000000..56d4099d54 --- /dev/null +++ b/code_samples/background_tasks/src/Dispatcher/SomeClassThatSchedulesExecutionInTheBackground.php @@ -0,0 +1,33 @@ +bus = $bus; + } + + public function schedule(object $message): void + { + // Dispatch directly. Message is wrapped with envelope without any stamps. + $this->bus->dispatch($message); + + // Alternatively, wrap with stamps. In this case, DeduplicateStamp ensures + // that if similar command exists in the queue (or is being processed) + // it will not be queued again. + $envelope = Envelope::wrap( + $message, + [new DeduplicateStamp('command-name-1')] + ); + + $this->bus->dispatch($envelope); + } +} diff --git a/code_samples/background_tasks/src/Message/SomeMessage.php b/code_samples/background_tasks/src/Message/SomeMessage.php new file mode 100644 index 0000000000..8f906c23b3 --- /dev/null +++ b/code_samples/background_tasks/src/Message/SomeMessage.php @@ -0,0 +1,8 @@ +.create_form_data`| -|[MapCreateDataToStructEvent](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-Step-MapCreateDataToStructEvent.html)|`ibexa.discounts.form_mapper..map_create_data_to_struct`| -|[MapDiscountToFormDataEvent](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-Step-MapDiscountToFormDataEvent.html)| `ibexa.discounts.form_mapper..map_discount_to_form_data`| -|[MapUpdateDataToStructEvent](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-Step-MapUpdateDataToStructEvent.html)|`ibexa.discounts.form_mapper..map_update_data_to_struct `| +|[`CreateFormDataEvent`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-Step-CreateFormDataEvent.html)| `ibexa.discounts.form_mapper..create_form_data`| +|[`MapCreateDataToStructEvent`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-Step-MapCreateDataToStructEvent.html)|`ibexa.discounts.form_mapper..map_create_data_to_struct`| +|[`MapDiscountToFormDataEvent`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-Step-MapDiscountToFormDataEvent.html)| `ibexa.discounts.form_mapper..map_discount_to_form_data`| +|[`MapUpdateDataToStructEvent`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-Step-MapUpdateDataToStructEvent.html)|`ibexa.discounts.form_mapper..map_update_data_to_struct `| The event classes are shared between steps, but they are dispatched with different names. Each step form mapper dispatches its own set of events. | Form mapper | Step identifier | |---|---| -| [ConditionsMapperInterface](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-FormMapper-ConditionsMapperInterface.html)| [`conditions`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Data-ConditionsInterface.html#constant_IDENTIFIER) | -| [GeneralPropertiesMapperInterface](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-FormMapper-GeneralPropertiesMapperInterface.html)| [`general_properties`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Data-GeneralPropertiesInterface.html#constant_IDENTIFIER) | -| [ProductConditionsMapperInterface](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-FormMapper-ProductConditionsMapperInterface.html)| [`products`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Data-ProductConditionInterface.html#constant_IDENTIFIER) | -| [UserConditionsMapperInterface](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-FormMapper-UserConditionsMapperInterface.html)| [`target_group`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Data-UserConditionInterface.html#constant_IDENTIFIER) | +| [`ConditionsMapperInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-FormMapper-ConditionsMapperInterface.html)| [`conditions`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Data-ConditionsInterface.html#constant_IDENTIFIER) | +| [`GeneralPropertiesMapperInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-FormMapper-GeneralPropertiesMapperInterface.html)| [`general_properties`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Data-GeneralPropertiesInterface.html#constant_IDENTIFIER) | +| [`ProductConditionsMapperInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-FormMapper-ProductConditionsMapperInterface.html)| [`products`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Data-ProductConditionInterface.html#constant_IDENTIFIER) | +| [`UserConditionsMapperInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-FormMapper-UserConditionsMapperInterface.html)| [`target_group`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Data-UserConditionInterface.html#constant_IDENTIFIER) | ### Back office @@ -61,8 +63,8 @@ These events are dispatched by the back office controllers after user chooses th | Event | Dispatched by | Description | |---|---|---| -[PreDiscountCreateEvent](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Event-PreDiscountCreateEvent.html) | `Ibexa\Bundle\Discounts\Controller\DiscountCreateController` | Dispatched when the discount creation is finished in the back office form | -[PreDiscountUpdateEvent](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Event-PreDiscountUpdateEvent.html) | `Ibexa\Bundle\Discounts\Controller\DiscountEditController` | Dispatched when the discount modifications is finished in the back office form | +|[`PreDiscountCreateEvent`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Event-PreDiscountCreateEvent.html) | `Ibexa\Bundle\Discounts\Controller\DiscountCreateController` | Dispatched when the discount creation is finished in the back office form | +|[`PreDiscountUpdateEvent`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Admin-Form-Event-PreDiscountUpdateEvent.html) | `Ibexa\Bundle\Discounts\Controller\DiscountEditController` | Dispatched when the discount modifications is finished in the back office form | ## Discount codes @@ -70,4 +72,4 @@ The event below allows you to inject your custom logic before the discount code | Event | Dispatched by | Description | |---|---|---| -|[BeforeDiscountCodeApplyEvent](/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Event-BeforeDiscountCodeApplyEvent.html)|`Ibexa\Bundle\DiscountsCodes\Controller\REST\DiscountCodeController`| Dispatched before a discount code is applied in the cart | +|[`BeforeDiscountCodeApplyEvent`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Event-BeforeDiscountCodeApplyEvent.html)|`Ibexa\Bundle\DiscountsCodes\Controller\REST\DiscountCodeController`| Dispatched before a discount code is applied in the cart | diff --git a/docs/discounts/configure_discounts.md b/docs/discounts/configure_discounts.md index 46c6e2ab77..067d3b4bc0 100644 --- a/docs/discounts/configure_discounts.md +++ b/docs/discounts/configure_discounts.md @@ -30,6 +30,35 @@ ibexa: products_list_per_page_limit: 15 ``` +## Discount re-indexing + +Discounts feature uses [[= product_name_base =]] Messenger to reindex discounts and product prices as [background tasks](background_tasks.md). +This way changes are processed efficiently without slowing down the system and disrupting the user experience. + +When triggered periodically, the `ibexa:discounts:reindex` command identifies discounts that require re-indexing, ensuring prices always remain up-to-date. +If there are edits to discounts that should result in changed product catalog prices, messages are dispatched to the [[= product_name_base =]] Messenger's queue and consumed by a background worker. +The worker passes the messages to the handler, which then starts the re-indexing process at the most convenient moment. + +To run discount re-indexing in the background: + +1\. Make sure that the transport layer is [defined properly](background_tasks.md#configure-package) in [[= product_name_base =]] Messenger configuration. + +2\. Make sure that the [worker starts](background_tasks.md#start-worker) together with the application to watch the transport bus: + +``` bash +php bin/console messenger:consume ibexa.messenger.transport --bus=ibexa.messenger.bus +``` + +3\. Use a scheduler of your choice, for example, [cron](https://en.wikipedia.org/wiki/Cron), to periodically run the following command: + +``` bash +php bin/console ibexa:discounts:reindex +``` + +!!! note "Deploying Symfony Messenger" + + For more information about deploying the Messenger to production, see [Symfony documentation]([[= symfony_doc =]]/messenger.html#deploying-to-production). + ## Rate limiting To prevent malicious actors from trying all the possible discount code combinations using brute-force attacks, the [`/discounts_codes/{cartIdentifier}/apply` endpoint](/api/rest_api/rest_api_reference/rest_api_reference.html#discount-codes-apply-discount-to-cart) is rate limited using the [Rate Limiter Symfony component]([[= symfony_doc =]]/rate_limiter.html). diff --git a/docs/discounts/discounts_guide.md b/docs/discounts/discounts_guide.md index eae3c4b581..99e58c17f9 100644 --- a/docs/discounts/discounts_guide.md +++ b/docs/discounts/discounts_guide.md @@ -113,6 +113,15 @@ You can also limit the usage per customer: - limited use: every customer can use the code a specified number of times - unlimited +### Discount re-indexing + +Discounts affect the prices shown in the product catalog. +When a discount is created, updated, or expires, the product catalog must be re-indexed to ensure that the search results and product listings display correct prices. + +To prevent performance disruptions which could occur if re-indexing occurred immediately, [[= product_name =]] uses the [[= product_name_base =]] Messenger's [background queue](background_tasks.md) to process re-indexing tasks in the background. + +By [configuring the process](configure_discounts.md#discount-re-indexing), you ensure that re-indexing is performed at the most convenient time to maintain your application's overall stability. + ## Capabilities ### Management diff --git a/docs/getting_started/install_ibexa_dxp.md b/docs/getting_started/install_ibexa_dxp.md index 411284c41e..54529d20bc 100644 --- a/docs/getting_started/install_ibexa_dxp.md +++ b/docs/getting_started/install_ibexa_dxp.md @@ -407,6 +407,10 @@ Finally, remove the temporary file: To make use of the [Link Manager](url_management.md#enabling-automatic-url-validation). +### Enable discount re-indexing [[% include 'snippets/commerce_badge.md' %]] + +Enable [discount re-indexing in the background](configure_discounts.md#discount-re-indexing). + ## [[= product_name_cloud =]] If you want to host your application on [[= product_name_cloud =]], follow the [Ibexa Cloud](install_on_ibexa_cloud.md) procedure. diff --git a/docs/infrastructure_and_maintenance/background_tasks.md b/docs/infrastructure_and_maintenance/background_tasks.md new file mode 100644 index 0000000000..12c854a324 --- /dev/null +++ b/docs/infrastructure_and_maintenance/background_tasks.md @@ -0,0 +1,102 @@ +--- +description: Use Ibexa Messenger to run processes in the background and conserve system resources. +month_change: true +--- + +# Background tasks + +Some operations in [[= product_name =]] don’t have to run immediately when a user clicks a button, for example, re-indexing product prices or processing bulk data. +Running such operations in real time could slow down the system and disrupt the user experience. + +To solve this, [[= product_name =]] provides a package called [[= product_name_base =]] Messenger, which is an overlay to [Symfony Messenger]([[= symfony_doc =]]/messenger.html), and it's job is to queue tasks and run them in the background. +[[= product_name =]] sends messages (or commands) that represent the work to be done later. +These messages are stored in a queue and picked up by a background worker, which ensures that resource-heavy tasks are executed at a convenient time, without putting excessive load on the system. + +[[= product_name_base =]] Messenger supports multiple storage backends, such as Doctrine, Redis, and PostgreSQL, and gives developers the flexibility to create their own message handlers for custom use cases. + +## How it works + +[[= product_name_base =]] Messenger uses a command bus as a queue that stores messages, or commands, which tell the system what you want to happen, and separates them from the handler, which is the code that actually performs the task. + +The process works as follows: + +1. A message PHP object is dispatched, for example, `ProductPriceReindex`. +2. The message is wrapped in an envelope, which may contain additional metadata, called stamps, for example, `DeduplicateStamp`. +3. The message is placed in the transport queue. +It can be a Doctrine table, a Redis queue, and so on. +4. A worker process continuously reads messages from the queue, pulls them into the default bus `ibexa.messenger.bus` and assigns them to the right handler. +5. A handler service processes the message (executes the command). +You can register multiple handlers for different jobs. + +Here is an example of how you can extend your code and use [[= product_name_base =]] Messenger to process your tasks: + +### Configure package + +Create a config file, for example, `config/packages/ibexa_messenger.yaml` and define your transport: + +``` yaml +ibexa_messenger: + + # The DSN of the transport, as expected by Symfony Messenger transport factory. + transport_dsn: 'doctrine://default?table_name=ibexa_messenger_messages&auto_setup=false' + deduplication_lock_storage: + enabled: true + + # Doctrine DBAL primary connection or custom service + type: doctrine # One of "doctrine"; "custom"; "service" + + # The service ID of a custom Lock Store, if "service" type is selected + service: null + + # The DSN of the lock store, if "custom" type is selected + dsn: null +``` + +!!! note "Supported transports" + + You can define different transports: [[= product_name_base =]] Messenger has been tested to work with Redis, MySQL, PostgreSQL. + For more information, see [Symfony Messenger documentation](https://symfony.com/doc/current/messenger.html#transports-async-queued-messages) or [Symfony Messenger tutorial](https://symfonycasts.com/screencast/messenger/install#installing-messenger). + +### Start worker + +Use a process manager of your choice to run the following command, or make it start together with the server: + +``` bash +php bin/console messenger:consume ibexa.messenger.transport --bus=ibexa.messenger.bus --siteaccess=` +``` + +In [multi-repository setups](repository_configuration.md), the worker process always works for a [SiteAccess](multisite_configuration.md#siteaccess-configuration) that you indicate by using the `--siteaccess` option, therefore you may need to run multiple workers, one for each SiteAccess. + +!!! warning "Multi-repository setups" + + Doctrine transport works across multiple repositories without issues, but other transports may need to be adjusted, so that queues across different repositories are not accidentally shared. + +!!! note "Deploying [[= product_name_base =]] Messenger" + + Additional considerations regarding the deployment of Symfony Messenger to production, which you can find in [Symfony documentation](https://symfony.com/doc/current/messenger.html#deploying-to-production) apply to [[= product_name_base =]] Messenger as well. + +### Dispatch message + +Dispatch a message from your code like in the following example: + +``` php +[[= include_file("code_samples/background_tasks/src/Dispatcher/SomeClassThatSchedulesExecutionInTheBackground.php") =]] +``` + +### Register handler + +Create the handler class: + +``` php +[[= include_file("code_samples/background_tasks/src/MessageHandler/SomeHandler.php") =]] +``` + +Add a service definition to `config/services.yaml`: + +``` yaml +services: + App\MessageHandler\SomeHandler: + tags: + - name: messenger.message_handler + bus: ibexa.messenger.bus +``` diff --git a/docs/infrastructure_and_maintenance/infrastructure_and_maintenance.md b/docs/infrastructure_and_maintenance/infrastructure_and_maintenance.md index e78c231344..d4b263d23b 100644 --- a/docs/infrastructure_and_maintenance/infrastructure_and_maintenance.md +++ b/docs/infrastructure_and_maintenance/infrastructure_and_maintenance.md @@ -9,6 +9,7 @@ page_type: landing_page "infrastructure_and_maintenance/cache/cache", "infrastructure_and_maintenance/clustering/clustering", "infrastructure_and_maintenance/performance", + "infrastructure_and_maintenance/background_tasks", "infrastructure_and_maintenance/databases", "infrastructure_and_maintenance/environments", "infrastructure_and_maintenance/support_and_maintenance_faq", diff --git a/docs/search/discounts_search_reference/discounts_criteria.md b/docs/search/discounts_search_reference/discounts_criteria.md index 6ca525ca6a..56ad1da499 100644 --- a/docs/search/discounts_search_reference/discounts_criteria.md +++ b/docs/search/discounts_search_reference/discounts_criteria.md @@ -10,17 +10,19 @@ Search Criteria are found in the `Ibexa\Contracts\Discounts\Value\Query\Criterio | Criterion | Description | |---|---| -| [CreatedAtCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-CreatedAtCriterion.html) | Find discounts with given creation date| -| [CreatorCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-CreatorCriterion.html) | Find discounts created by specific users| +| [CreatedAtCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-CreatedAtCriterion.html) | Find discounts with given creation date | +| [CreatorCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-CreatorCriterion.html) | Find discounts created by specific users | | [EndDateCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-EndDateCriterion.html) | Find discounts by their end date. For permanent discounts, the end date is set to `null` | +| [IndexedAtCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-IndexedAtCriterion.html) | Find discounts based on the date and time when they were indexed | | [IdentifierCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-IdentifierCriterion.html) | Find discounts by their identifier | -| [IsEnabledCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-IsEnabledCriterion.html) | Find discounts by their status| +| [IsEnabledCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-IsEnabledCriterion.html) | Find discounts by their status | | [LogicalAnd](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-LogicalAnd.html) | Composite criterion to group multiple criterions using the AND condition | | [LogicalOr](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-LogicalOr.html) | Composite criterion to group multiple criterions using the OR condition | | [NameCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-NameCriterion.html) | Find discounts by their name | | [PriorityCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-PriorityCriterion.html) | Find discounts by their priority | -| [StartDateCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-StartDateCriterion.html) | Find discounts with given start date| -| [TypeCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-TypeCriterion.html) | Find cart or catalog discounts by using constants from the [DiscountType](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountType.html) class| +| [StartDateCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-StartDateCriterion.html) | Find discounts with given start date | +| [TypeCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-TypeCriterion.html) | Find cart or catalog discounts by using constants from the [DiscountType](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountType.html) class | +| [UpdatedAtCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-UpdatedAtCriterion.html) | Find discounts based on the date and time when they were updated | You can use the [FieldValueCriterion's constants](/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-Criterion-FieldValueCriterion.html#constants) like `FieldValueCriterion::COMPARISON_CONTAINS` or `FieldValueCriterion::COMPARISON_STARTS_WITH` to specify the operator for the condition. diff --git a/docs/search/discounts_search_reference/discounts_sort_clauses.md b/docs/search/discounts_search_reference/discounts_sort_clauses.md index 7b0ee81f64..b2d4432bbc 100644 --- a/docs/search/discounts_search_reference/discounts_sort_clauses.md +++ b/docs/search/discounts_search_reference/discounts_sort_clauses.md @@ -14,6 +14,7 @@ Sort Clauses are found in the [`Ibexa\Contracts\Discounts\Value\Query\SortClause | [EndDate](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-EndDate.html)| Sort by discount's end date | | [Id](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-Id.html)| Sort by discount's database ID | | [Identifier](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-Identifier.html)| Sort by discount identifier | +| [OverridePrioritization](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-OverridePrioritization.html)| Sort prioritizing discounts with discount code over automatic ones | | [Priority](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-Priority.html)| Sort by discount priority | | [StartDate](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-StartDate.html)| Sort by discount start date | | [Type](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-Type.html)| Sort by the place where the discount activates: catalog or cart. When sorting with ascending order, cart discounts are returned first. | diff --git a/mkdocs.yml b/mkdocs.yml index 89b6ff2ecc..eaf33d6eda 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -814,6 +814,7 @@ nav: - DevOps: infrastructure_and_maintenance/devops.md - Backup: infrastructure_and_maintenance/backup.md - Performance: infrastructure_and_maintenance/performance.md + - Background tasks: infrastructure_and_maintenance/background_tasks.md - Environments: infrastructure_and_maintenance/environments.md - Sessions: infrastructure_and_maintenance/sessions.md - Logging: infrastructure_and_maintenance/logging.md