<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: run_as_root GmbH</title>
    <description>The latest articles on Forem by run_as_root GmbH (@run_as_root).</description>
    <link>https://forem.com/run_as_root</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F7239%2Fb0f2275c-a4cc-4a15-8ad1-0f1105ee097b.jpg</url>
      <title>Forem: run_as_root GmbH</title>
      <link>https://forem.com/run_as_root</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/run_as_root"/>
    <language>en</language>
    <item>
      <title>Good-bye Docker, Hello Nix: Configuring a Magento 2 Development Environment with Rooter</title>
      <dc:creator>Cristiano Pacheco</dc:creator>
      <pubDate>Tue, 20 Feb 2024 10:50:29 +0000</pubDate>
      <link>https://forem.com/run_as_root/good-bye-docker-hello-nix-configuring-a-magento-2-development-environment-with-rooter-11in</link>
      <guid>https://forem.com/run_as_root/good-bye-docker-hello-nix-configuring-a-magento-2-development-environment-with-rooter-11in</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Requirements&lt;/li&gt;
&lt;li&gt;
Creating a Magento 2 dev environment

&lt;ul&gt;
&lt;li&gt;1. Creating the project directory&lt;/li&gt;
&lt;li&gt;2. Entering in the created directory&lt;/li&gt;
&lt;li&gt;3. Creating the environment with rooter&lt;/li&gt;
&lt;li&gt;4. Allowing direnv to execute&lt;/li&gt;
&lt;li&gt;5. Cloning the Magento 2 repository&lt;/li&gt;
&lt;li&gt;6. Starting the environment stack&lt;/li&gt;
&lt;li&gt;7. Setting up the database&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Rooter Appendix

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;.devenv&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;Versioning&lt;/li&gt;
&lt;li&gt;
Diving deeper into the most important files

&lt;ul&gt;
&lt;li&gt;.env&lt;/li&gt;
&lt;li&gt;.envrc&lt;/li&gt;
&lt;li&gt;devenv.nix&lt;/li&gt;
&lt;li&gt;devenv.yaml&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Accessing the traefik dashboard&lt;/li&gt;

&lt;li&gt;Accessing the mailpit UI&lt;/li&gt;

&lt;li&gt;Rooter commands&lt;/li&gt;

&lt;li&gt;Xdebug&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction:
&lt;/h2&gt;

&lt;p&gt;In 2023, I switched from Linux to macOS. I used Linux for years, mainly because it supported Docker natively. However, during a system update, I encountered a problem with the video driver that cost me an entire Saturday searching for solutions, tweaking files I didn't even know existed until finally, at the end of the day, I found a workaround to get the darn Nvidia driver working.&lt;/p&gt;

&lt;p&gt;Come Monday morning, I shared my tech nightmare with a friend at work, who promptly asked:&lt;/p&gt;

&lt;p&gt;"Why don't you switch to macOS?"&lt;/p&gt;

&lt;p&gt;I replied: "Because it doesn't have native Docker support, and I don't want to use a virtual machine."&lt;/p&gt;

&lt;p&gt;With a virtual machine, you have to allocate a good chunk of RAM, not to mention the slower disk processing. While disk speed might not matter much for many applications, it makes a huge difference for a Magento 2 environment.&lt;/p&gt;

&lt;p&gt;"Why do you even need Docker?" he asked.&lt;/p&gt;

&lt;p&gt;That sparked a quick conversation where he introduced me to &lt;a href="https://nixos.org/" rel="noopener noreferrer"&gt;Nix&lt;/a&gt; and &lt;a href="https://devenv.sh/" rel="noopener noreferrer"&gt;devenv&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With these tools, it's possible to create a development environment that's entirely reproducible and native to macOS!&lt;/p&gt;

&lt;p&gt;It was love at first sight! When I saw it in action, my mind was blown!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu07m8ivwe6owetxkkxce.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu07m8ivwe6owetxkkxce.gif" alt="shocked" width="168" height="168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During that conversation, I first contacted one of the early versions of &lt;a href="https://github.com/run-as-root/rooter" rel="noopener noreferrer"&gt;rooter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Rooter is a powerful command-line tool to automate repetitive tasks in creating and maintaining development environments.&lt;/p&gt;

&lt;p&gt;Shortly after, I bought a MacBook and started using &lt;code&gt;rooter&lt;/code&gt; to manage my projects. Nowadays, I don't have Docker installed on my machine, and I couldn't be happier!&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;You need macOS operating system and the softwares below installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/run-as-root/rooter" rel="noopener noreferrer"&gt;rooter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nixos.org/" rel="noopener noreferrer"&gt;nix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devenv.sh/" rel="noopener noreferrer"&gt;devenv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://direnv.net/" rel="noopener noreferrer"&gt;direnv&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just follow the instructions on the &lt;code&gt;rooter&lt;/code&gt; &lt;a href="https://github.com/run-as-root/rooter?tab=readme-ov-file#prerequisites" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Just giving a brief introduction about the these tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;rooter&lt;/strong&gt; is a CLI tool created By &lt;a href="https://www.linkedin.com/in/matthiaswalter/" rel="noopener noreferrer"&gt;Matthias Walter&lt;/a&gt;. This tool will be your best friend during your development journey. It is similar to &lt;a href="https://docs.warden.dev/" rel="noopener noreferrer"&gt;Warden&lt;/a&gt; or &lt;a href="https://ddev.readthedocs.io/" rel="noopener noreferrer"&gt;DDEV&lt;/a&gt;. It takes care of things like automatic DNS resolution using dnsmasq, managing project routes with &lt;a href="https://traefik.io/traefik/" rel="noopener noreferrer"&gt;Traefik&lt;/a&gt;, creating SSL certificates, and even includes handy commands for importing and exporting databases, plus a whole lot more.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;nix&lt;/strong&gt; has been around since 2003 and it's super reliable. It's like a toolbox with lots of useful tools inside, such as a programming language, an operating system, and a package manager. It helps you make software systems that are consistent and easy to recreate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;devenv&lt;/strong&gt; is kind of like &lt;a href="https://docs.docker.com/compose/" rel="noopener noreferrer"&gt;docker-compose&lt;/a&gt;, but way stronger. It's like being able to use a programming language to set up your environment, giving you even more control and flexibility.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;direnv&lt;/strong&gt; is a tool that figures out and loads all the stuff you need for your current project whenever you step into its folder in your terminal. We'll get to see how it works soon.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating a Magento 2 dev environment
&lt;/h2&gt;

&lt;p&gt;So getting straight to the point, let's set up a development environment to run Magento 2.&lt;/p&gt;

&lt;p&gt;At this point, I'm assuming you've got &lt;code&gt;rooter&lt;/code&gt; and all the necessary tools it relies on installed and ready to roll. Let's dive in!&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Creating the project directory
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/Sites/magento2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Entering in the created directory
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/Sites/magento2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Creating the environment with &lt;code&gt;rooter&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rooter &lt;span class="nb"&gt;env&lt;/span&gt;:create
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rooter will ask what type of environment:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbakfqszz19t45juvrro8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbakfqszz19t45juvrro8.png" alt="rooter env type" width="653" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Type 2 and enter for Magento 2&lt;/p&gt;

&lt;p&gt;It generated 4 files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.env&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.envrc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;devenv.nix&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;devenv.yaml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Allowing direnv to execute
&lt;/h3&gt;

&lt;p&gt;You probably saw this message in your terminal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4hhq7txudw2fglyg61cj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4hhq7txudw2fglyg61cj.png" alt="dir env allow" width="718" height="89"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You need to allow direnv to execute.&lt;/p&gt;

&lt;p&gt;Run the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;direnv allow &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you'll see something like this in your terminal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fml35oz8pj3a8kgh57xo2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fml35oz8pj3a8kgh57xo2.png" alt="dir env steup" width="564" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this moment, your dev stack declared in the &lt;code&gt;devenv.nix&lt;/code&gt; file will be downloaded and configured. This is the longest step. Wait until the installation process finishes.&lt;/p&gt;

&lt;p&gt;You will see something like this when the process finishes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flp9vn7h1yv5tb4oqvmi4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flp9vn7h1yv5tb4oqvmi4.png" alt="direnv installation finished" width="566" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, please take a look into this image:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu7lcwijvehopogposj8s.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu7lcwijvehopogposj8s.gif" alt="direnv loading" width="586" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've noticed, when I'm not in the project folder, the PHP version in the terminal is different, not 8.2. But as soon as I step into the project folder, direnv works its magic and loads the entire setup into the terminal. Pretty neat, isn't it?&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Cloning the Magento 2 repository
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer create-project &lt;span class="nt"&gt;--repository-url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://repo.magento.com/ magento/project-community-edition:2.4.6-p4 magento2-codebase
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It'll clone the Magento 2 repository into a directory named magento2-codebase. Once it's done, just copy everything from the magento2-codebase folder to the current directory, and then you can delete the magento2-codebase directory with the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; magento2-codebase/&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-Rf&lt;/span&gt; magento2-codebase
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Starting the environment stack
&lt;/h3&gt;

&lt;p&gt;I like to start the environment in the debug mode in the first attempt, because this shows detailed output about the initialization and in case there is an issue, it is visible right away:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rooter &lt;span class="nb"&gt;env&lt;/span&gt;:start &lt;span class="nt"&gt;--debug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically devenv will download all services upon first start. That could also be nginx, opensearch, rabbitmq etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbpubgow1dybsmlpudu82.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbpubgow1dybsmlpudu82.png" alt="opensearch lib download" width="570" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the first start I like to stop the environment (CMD + c) and start it again without the debug flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rooter &lt;span class="nb"&gt;env&lt;/span&gt;:start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason being that now it is running in the background rather than the foreground which it was with &lt;code&gt;--debug&lt;/code&gt; flag passed.&lt;/p&gt;

&lt;p&gt;To see what is the status of the environment, run the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rooter &lt;span class="nb"&gt;env&lt;/span&gt;:status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will show something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Febtuodopzhtw5635jxna.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Febtuodopzhtw5635jxna.png" alt="env status" width="800" height="696"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As mentioned earlier, it uses &lt;a href="https://github.com/F1bonacc1/process-compose" rel="noopener noreferrer"&gt;process-compose&lt;/a&gt; in the background. There is a command to attach to the process-compose and get a control panel to check the various processed that got launched.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rooter &lt;span class="nb"&gt;env&lt;/span&gt;:process-compose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see in the image below, you can easily switch between the different services and watch their logs update live.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo1k75okbrwd7anw5dcp4.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo1k75okbrwd7anw5dcp4.gif" alt="Process compose" width="582" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Setting up the database
&lt;/h3&gt;

&lt;p&gt;Run the command below to setup the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rooter magento2:db-install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rooter will drop and recreate the database and by default run the commands below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bin/magento setup:install&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bin/magento setup:upgrade&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bin/magento indexer:reindex&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An admin user will be created with the username &lt;code&gt;admin&lt;/code&gt; and password &lt;code&gt;admin123&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you pass the option &lt;code&gt;--config-data-import&lt;/code&gt; it will run the command &lt;code&gt;bin/magento config:data:import config/store dev/rooter&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The &lt;code&gt;config:data:import&lt;/code&gt; is provided by the &lt;a href="https://github.com/semaio/Magento2-ConfigImportExport" rel="noopener noreferrer"&gt;Magento2-ConfigImportExport&lt;/a&gt; module. You need to install it to use the &lt;code&gt;--config-data-import&lt;/code&gt; option flag.&lt;/p&gt;

&lt;p&gt;You can skip the reindex by passing the option &lt;code&gt;--skip-reindex&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Run the commands below to set some extra configs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/magento config:set web/unsecure/base_url https://magento2.rooter.test/ &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
bin/magento config:set web/secure/base_url https://magento2.rooter.test/ &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
bin/magento config:set web/secure/offloader_header X-Forwarded-Proto &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
bin/magento config:set web/secure/use_in_frontend 1 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
bin/magento config:set web/secure/use_in_adminhtml 1 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
bin/magento config:set web/seo/use_rewrites 1 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
bin/magento config:set catalog/search/enable_eav_indexer 1 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
bin/magento config:set dev/static/sign 0 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
bin/magento deploy:mode:set &lt;span class="nt"&gt;-s&lt;/span&gt; developer &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
bin/magento cache:clean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are the Store front and admin URLs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://magento2.rooter.test" rel="noopener noreferrer"&gt;https://magento2.rooter.test&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://magento2.rooter.test/admin" rel="noopener noreferrer"&gt;https://magento2.rooter.test/admin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tcharaaam!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8a59fln3vusfjdvy9pma.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8a59fln3vusfjdvy9pma.gif" alt="home" width="800" height="673"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What really blows me away is the speed. Running Magento natively on Mac makes everything super fast and smooth.&lt;/p&gt;




&lt;h3&gt;
  
  
  Rooter Appendix
&lt;/h3&gt;

&lt;h4&gt;
  
  
  The &lt;code&gt;.devenv&lt;/code&gt; directory
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;.devenv&lt;/code&gt; directory contains the data about the profile, nix garbage collector, the stack state, etc.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;.devenv/state&lt;/code&gt; directory is where some of the stack code lives, as for example, &lt;code&gt;elasticsearch&lt;/code&gt;, &lt;code&gt;mysql&lt;/code&gt;, &lt;code&gt;php-fpm&lt;/code&gt;, &lt;code&gt;nginx&lt;/code&gt;, &lt;code&gt;rabbitmq&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;You don't need to touch these folders or the files on it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvrgvbrdcu62raduxrgoj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvrgvbrdcu62raduxrgoj.png" alt=".devenv directory" width="236" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Versioning
&lt;/h4&gt;

&lt;p&gt;Which files and directories should you version or remove from versioning:&lt;/p&gt;

&lt;p&gt;You don't need to version the files and folders below, so add them to your &lt;code&gt;.gitignore&lt;/code&gt; file.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.devenv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.direnv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.env&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You need to version the files below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.devenv.flake.nix&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.envrc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;devenv.lock&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;devenv.nix&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;devenv.yaml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; None of this files is required by rooter. They are required from &lt;code&gt;devenv&lt;/code&gt; and &lt;code&gt;direnv&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Diving deeper into the most important files
&lt;/h4&gt;

&lt;h5&gt;
  
  
  &lt;code&gt;.env&lt;/code&gt; file:
&lt;/h5&gt;

&lt;p&gt;Contains all variable environments that is used in your project stack&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ey115h4spwg2ygm5n8f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ey115h4spwg2ygm5n8f.png" alt="dot env file" width="800" height="725"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ROOTER_ENV_TYPE&lt;/code&gt; defines the type of the environment, it can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;magento1&lt;/li&gt;
&lt;li&gt;magento2&lt;/li&gt;
&lt;li&gt;php-plan&lt;/li&gt;
&lt;li&gt;shopware6&lt;/li&gt;
&lt;li&gt;symfony&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;DEVENV_HTTP_PORT&lt;/code&gt; defines the http port used from the Nginx webserver&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DEVENV_HTTPS_PORT&lt;/code&gt; defines the https (SSL) port used from the Nginx webserver&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DEVENV_DB_PORT&lt;/code&gt; defines the Mysql/MariaDB/Percona database port&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In the current version of &lt;code&gt;rooter&lt;/code&gt;, only Mysql* database is supported.&lt;/p&gt;

&lt;p&gt;Mysql* I mean any of the versions or forks of it, for instance MariaDB, Percona, etc.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DEVENV_MAIL_SMTP_PORT&lt;/code&gt; defines the &lt;a href="https://github.com/axllent/mailpit" rel="noopener noreferrer"&gt;mailpit&lt;/a&gt; SMTP port&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DEVENV_MAIL_UI_PORT&lt;/code&gt; defines the &lt;a href="https://github.com/axllent/mailpit" rel="noopener noreferrer"&gt;mailpit&lt;/a&gt; user interface port&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DEVENV_REDIS_PORT&lt;/code&gt; the name already explains it :)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DEVENV_AMQP_PORT&lt;/code&gt; defines the &lt;a href="https://rabbitmq.com/" rel="noopener noreferrer"&gt;RabbitMQ&lt;/a&gt; port that will be used in your application&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DEVENV_AMQP_MANAGEMENT_PORT&lt;/code&gt; defines the RabbitMQ admin panel&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DEVENV_ELASTICSEARCH_PORT&lt;/code&gt; defines the &lt;a href="https://www.elastic.co/elasticsearch" rel="noopener noreferrer"&gt;ElasticSearch&lt;/a&gt; port that will be used from your application&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DEVENV_ELASTICSEARCH_TCP_PORT&lt;/code&gt; the name already explains it :)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DEVENV_PROCESS_COMPOSE_PORT&lt;/code&gt; the name already explains it :)&lt;/p&gt;

&lt;p&gt;We'll see more about the process compose later.&lt;/p&gt;

&lt;p&gt;In case you noticed and are wondering, where the heck did the values of the ports defined in these variables come from?&lt;/p&gt;

&lt;p&gt;Rooter has a mechanism that will get the available ports in your environment, taking into account the existing environments that were created by rooter.&lt;/p&gt;

&lt;p&gt;It will assign ports using certain ranges, you can see how it works in &lt;a href="https://github.com/run-as-root/rooter/blob/main/src/Manager/PortManager.php" rel="noopener noreferrer"&gt;this&lt;/a&gt; file, please look:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3wya2zvrod8cuniuqkis.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3wya2zvrod8cuniuqkis.png" alt="rooter ports manager" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  &lt;code&gt;.envrc&lt;/code&gt; file:
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy557ojp5jr7v8uu5btp0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy557ojp5jr7v8uu5btp0.png" alt="envrc file" width="800" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This file is used by the &lt;code&gt;direnv&lt;/code&gt; tool.&lt;/p&gt;

&lt;p&gt;It will load all the declared stack to the current terminal context.&lt;/p&gt;

&lt;h5&gt;
  
  
  &lt;code&gt;devenv.nix&lt;/code&gt; file:
&lt;/h5&gt;

&lt;p&gt;This file is used by &lt;code&gt;devenv&lt;/code&gt;, it uses the &lt;a href="https://nixos.org/manual/nix/stable/language/index.html" rel="noopener noreferrer"&gt;Nix language&lt;/a&gt; to declare the environment.&lt;/p&gt;

&lt;p&gt;You can find more information about it &lt;a href="https://devenv.sh/basics/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's get more details of the most important parts of this file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Nix, when you see this line, it's like setting up a template for a function. It says that the function will at least get these four things: pkgs, inputs, lib, and config. But it's cool because it can also get more stuff using the &lt;code&gt;...&lt;/code&gt; thingy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt;
    &lt;span class="nv"&gt;rooterBin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="kr"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;getEnv&lt;/span&gt; &lt;span class="s2"&gt;"ROOTER_BIN"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="kr"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;getEnv&lt;/span&gt; &lt;span class="s2"&gt;"ROOTER_BIN"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;"rooter"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;composerPhar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;fetchurl&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/composer/composer/releases/download/2.2.22/composer.phar"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1lmibmdlk2rsrf4zr7xk4yi5rhlmmi8f2g8h2izb8x4sik600dbx"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nv"&gt;magerun2Phar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;fetchurl&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/netz98/n98-magerun2/releases/download/7.2.0/n98-magerun2.phar"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0z1dkxz69r9r9gf8xm458zysa51f1592iymcp478wjx87i6prvn3"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kn"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;let...in&lt;/code&gt; block in the provided &lt;code&gt;devenv.nix&lt;/code&gt; file defines a scope for local definitions that can be used later in the expression. This construct allows for declaring variables and their values before using them in the main body of the Nix expression. &lt;/p&gt;

&lt;p&gt;Here's a breakdown of the let block and its components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;rooterBin&lt;/strong&gt;: Defined based on the ROOTER_BIN environment variable. If set, rooterBin uses its value; if not, it defaults to "rooter". Enables flexible configuration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;composerPhar/magerun2Phar&lt;/strong&gt;: Downloads the &lt;code&gt;Composer PHAR&lt;/code&gt; and &lt;code&gt;n98-magerun2&lt;/code&gt; PHAR file from GitHub using a URL and a sha256 hash for security.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;    &lt;span class="nv"&gt;dotenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;PROJECT_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"magento2"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;PROJECT_HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"magento2.rooter.test"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nv"&gt;NGINX_PKG_ROOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;nginx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;DEVENV_STATE_NGINX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;DEVENV_STATE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/nginx"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nv"&gt;DEVENV_PHPFPM_SOCKET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;DEVENV_STATE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/php-fpm.sock"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nv"&gt;DEVENV_DB_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;DEVENV_DB_USER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;DEVENV_DB_PASS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nv"&gt;DEVENV_AMQP_USER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"guest"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;DEVENV_AMQP_PASS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"guest"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;env&lt;/code&gt; block declares the environment variables.&lt;/p&gt;

&lt;p&gt;The project's domain is determined by the &lt;code&gt;PROJECT_HOST&lt;/code&gt; variable. So, you can access the project using the address: &lt;code&gt;https://magento2.rooter.test&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;If you need to change the &lt;code&gt;PROJECT_HOST&lt;/code&gt; variable value, follow the instructions &lt;a href="https://github.com/run-as-root/rooter/blob/main/docs/CONFIGURATION.md#custom-tld" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is possible to have custom subdomains, you can see more info about it &lt;a href="https://github.com/run-as-root/rooter/blob/main/docs/CONFIGURATION.md#domains-and-subdomains" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Just a heads up, the database will use as name, user, and password, all set to app by default. Feel free to tweak these settings whenever you need.&lt;/p&gt;

&lt;p&gt;Similarly, for RabbitMQ, the default user and password are both guest. You're welcome to adjust these as necessary.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3slh6c1t0cmxe1oeblyg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3slh6c1t0cmxe1oeblyg.png" alt="php declaration" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is where we define and set up PHP. If you ever need to switch the PHP version, simply make the change in the highlighted section shown in the image.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fva57nptpoybqwh1o6ba1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fva57nptpoybqwh1o6ba1.png" alt="Mysql" width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same for MariaDb version. If you need a different version just change it.&lt;/p&gt;

&lt;p&gt;To know what are the supported versions, you can search in directly in the &lt;a href="https://search.nixos.org/packages" rel="noopener noreferrer"&gt;nix packages page&lt;/a&gt;, for example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8avwfu6izo8ss22c9g1f.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8avwfu6izo8ss22c9g1f.gif" alt="Mysql search" width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; please note that we're using the &lt;code&gt;unstable&lt;/code&gt; channel, it is the default channel for devenv, because it has more packages and with newer versions on it. So when you'll search the package, don't forget to select the unstable channel. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl6ggck7a05y7oq60cs9a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl6ggck7a05y7oq60cs9a.png" alt="unstable channel" width="800" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last we have RabbitMQ declaration, if your project does not use it, just remove it from the &lt;code&gt;devenv.nix&lt;/code&gt; file. We'll maintain it :)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1b5g72lioenu2harwdm2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1b5g72lioenu2harwdm2.png" alt="Rabbit MQ" width="794" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  &lt;code&gt;devenv.yaml&lt;/code&gt; file:
&lt;/h5&gt;

&lt;p&gt;This file is used by &lt;code&gt;devenv&lt;/code&gt;. It will serve to declare what is the source of the packages. You can have multiple &lt;a href="https://devenv.sh/inputs/" rel="noopener noreferrer"&gt;inputs&lt;/a&gt;, using different &lt;a href="https://nixos.wiki/wiki/Nix_channels" rel="noopener noreferrer"&gt;channels&lt;/a&gt;, for example.&lt;/p&gt;

&lt;h4&gt;
  
  
  Accessing the &lt;code&gt;traefik&lt;/code&gt; dashboard
&lt;/h4&gt;

&lt;p&gt;To access the traefik dashboard, just type &lt;code&gt;rooter traefik:dashboard&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqe0yeslhhge29a9loe7i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqe0yeslhhge29a9loe7i.png" alt="traefik" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Accessing the &lt;code&gt;mailpit&lt;/code&gt; UI
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://mailpit.axllent.org/" rel="noopener noreferrer"&gt;Mailpit&lt;/a&gt; is a fork of &lt;a href="https://github.com/mailhog/MailHog" rel="noopener noreferrer"&gt;mailhog&lt;/a&gt;, It is newer and constantly receives updates.&lt;/p&gt;

&lt;p&gt;It is per environment it will have always the URL with this pattern:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://PROJECT_NAME-mail.rooter.test&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In our case it will be:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://magento2-mail.rooter.test" rel="noopener noreferrer"&gt;http://magento2-mail.rooter.test&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fatcmny7lexvfqlnoyr0j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fatcmny7lexvfqlnoyr0j.png" alt="mailpit" width="800" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;code&gt;PROJECT_NAME&lt;/code&gt; variable is defined in the &lt;code&gt;devenv.nix&lt;/code&gt; file&lt;/p&gt;

&lt;h4&gt;
  
  
  Rooter commands
&lt;/h4&gt;

&lt;p&gt;Just a heads up, to explore the existing rooter commands, you can simply run the command &lt;code&gt;rooter commands&lt;/code&gt;. This will give you a list of all available commands. Happy exploring!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzx8gty5onb8b2rbo5wgq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzx8gty5onb8b2rbo5wgq.png" alt="rooter commands" width="800" height="970"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Xdebug
&lt;/h4&gt;

&lt;p&gt;The only thing action you need to configure Xdebug is to set this environment env to your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export XDEBUG_CONFIG="idekey=PHPSTORM-XDEBUG"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can append it with the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export XDEBUG_CONFIG="idekey=PHPSTORM-XDEBUG"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you're able to debug your Magento 2 store on CLI or throughout web without any configuration.&lt;/p&gt;

&lt;p&gt;On PHPStorm for instance, you just have to click on the "Bug" button to start listening to Xdebug connections:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1dvth5rg2tfhn3ng8dd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1dvth5rg2tfhn3ng8dd.png" alt="Xdebug" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;If you have any specific scenarios you'd like guidance on, like setting up Magento with multiple websites or store views, feel free to leave your question in the comments below. I'll do my best to assist you!&lt;/p&gt;

&lt;p&gt;I hope you found this helpful! 👋 &lt;/p&gt;

</description>
      <category>magento2</category>
      <category>tutorial</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Frontend Performance Optimization tips | Magento 2 | Adobe Commerce</title>
      <dc:creator>Denys Sliepnov</dc:creator>
      <pubDate>Mon, 20 Nov 2023 17:41:57 +0000</pubDate>
      <link>https://forem.com/run_as_root/frontend-performance-optimization-tips-magento-2-adobe-commerce-a73</link>
      <guid>https://forem.com/run_as_root/frontend-performance-optimization-tips-magento-2-adobe-commerce-a73</guid>
      <description>&lt;h2&gt;
  
  
  Some backstory
&lt;/h2&gt;

&lt;p&gt;Everybody knows that the performance is important. Many of us have already done many things to improve our performance.&lt;br&gt;
Performance optimization is a complex process that requires a lot of time and effort.&lt;br&gt;
It's a never-ending process, and you should always keep in mind that you can do better.&lt;br&gt;
I'm also kept the performance optimization challenge and spent a lot of time improving the performance of the Magento 2 store.&lt;br&gt;
You know what is a headache to speed up a big project.&lt;/p&gt;
&lt;h2&gt;
  
  
  The beginning of the journey
&lt;/h2&gt;

&lt;p&gt;I started with the &lt;a href="https://developers.google.com/web/tools/lighthouse"&gt;Google Lighthouse&lt;/a&gt; tool.&lt;br&gt;
It's a great tool that helps you to find the most common problems with the performance.&lt;br&gt;
I read a lot of articles about performance optimization and tried to apply the best practices.&lt;br&gt;
I would suggest this &lt;a href="https://magefan.com/blog/speed-up-magento-2"&gt;article&lt;/a&gt;, combining most practices.&lt;br&gt;
I don't want to repeat the same things, you are already familiar with them, I guess.&lt;br&gt;
Most of them are well-known and described in the official documentation.&lt;br&gt;
They are really helpful, but I still had many problems with the performance.&lt;/p&gt;
&lt;h2&gt;
  
  
  The next step - the deep dive into the performance by myself
&lt;/h2&gt;

&lt;p&gt;After I applied all the best practices, I still had a lot of problems with the performance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The DOM size was too big&lt;/li&gt;
&lt;li&gt;The JS main-thread work was too long&lt;/li&gt;
&lt;li&gt;The Largest Contentful Paint (LCP) score was too high&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I can't remove required blocks from the page, I can't remove required JS code, I can't remove required images.&lt;br&gt;
I decided to go deeper and started to investigate the performance by myself.&lt;br&gt;
There are no resources how to decrease a DOM size, how to decrease a JS main-thread work, how to decrease a LCP score. Or I didn't find them, ha-ha.&lt;br&gt;
So, let me introduce the insights that I found during my investigation.&lt;/p&gt;
&lt;h3&gt;
  
  
  Decrease the DOM size whenever it's possible
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Don't create unnecessary DOM elements
&lt;/h4&gt;

&lt;p&gt;Each DOM element has a cost. The more elements you have, the more memory is used. &lt;br&gt;
The more memory is used, the slower the page is. So, don't create unnecessary DOM elements.&lt;/p&gt;
&lt;h4&gt;
  
  
  Try to avoid rendering elements that are not visible
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;If you have some visible component in the viewport that has some part of visible elements, like a button or input,
and a tree of elements that are not visible, try to avoid rendering them. It makes sense to use some Visual Fasade.
It's a component that renders only visible elements and hides the rest, rendering of the hidden elements will be started after the user interaction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;Your store uses the Search Autocomplete component. It renders a list of suggestions. The list is hidden by default.&lt;br&gt;
When the user starts typing, the list is shown. The list is rendered only when the user starts typing.&lt;br&gt;
It has a visible input element and hidden templates for suggestions.&lt;br&gt;
Visible elements are rendered immediately, and the rest of the list is rendered after the user interaction.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you have elements that are not visible, but they are rendered, try to prevent this. It's possible to render the specific part
of the content after the user interaction. To do this, you can use the following steps:

&lt;ul&gt;
&lt;li&gt;assign a hidden content to the JS variable:
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;// We use the $escaper variable to make sure that the JS content inside the child block is escaped properly.
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hiddenContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;?= $escaper-&amp;gt;escapeJs($block-&amp;gt;getChildHtml('block_identifier')) ?&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;use wrapper element to render the content:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"wrapper"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;inject a few helper js functions:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The 'init-external-scripts event' is triggered when any of the standard &lt;/span&gt;
&lt;span class="c1"&gt;// interaction events (touchstart, mouseover, wheel, scroll, keydown) are fired, &lt;/span&gt;
&lt;span class="c1"&gt;// and it also handles the cleanup, so the event is only fired once.&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dispatchUserInteractionEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dispatchUserInteractionEvent&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;init-external-scripts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dispatchUserInteractionEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;once&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;passive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="p"&gt;})([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;touchstart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouseover&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wheel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scroll&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;// This function renders the content inside the wrapper element. &lt;/span&gt;
&lt;span class="c1"&gt;// And it also handles the script execution when the content is rendered inside the wrapper element.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;setInnerHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;elm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oldScriptEl&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newScriptEl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oldScriptEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attr&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;newScriptEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scriptText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createTextNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oldScriptEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;newScriptEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scriptText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="nx"&gt;oldScriptEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replaceChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newScriptEl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;oldScriptEl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;render the content after the user interaction:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;init-external-scripts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wrapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wrapper&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;setInnerHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hiddenContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Full example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"wrapper"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dispatchUserInteractionEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dispatchUserInteractionEvent&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;init-external-scripts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dispatchUserInteractionEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;once&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;passive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="p"&gt;})([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;touchstart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouseover&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wheel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scroll&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;setInnerHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;elm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oldScriptEl&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newScriptEl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oldScriptEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attr&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;newScriptEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;});&lt;/span&gt;

                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scriptText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createTextNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oldScriptEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nx"&gt;newScriptEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scriptText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="nx"&gt;oldScriptEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replaceChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newScriptEl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;oldScriptEl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hiddenContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;?= $escaper-&amp;gt;escapeJs($block-&amp;gt;getChildHtml('block_identifier')) ?&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;init-external-scripts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wrapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wrapper&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;setInnerHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hiddenContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach minimizes the DOM size and JS main-thread work (script execution). &lt;br&gt;
Also, if hidden content contains some images, the loading of the images will be started after the user interaction.&lt;/p&gt;
&lt;h3&gt;
  
  
  Defer non-critical JS initialization and execution
&lt;/h3&gt;

&lt;p&gt;If you have some custom JS code that is not required for the initial page load, try to defer its execution.&lt;br&gt;
Imagine that you have some custom JS code that pushes page data to external system.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;pushPageDataToExternalSystem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Get page data&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pageData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Push page data to external system&lt;/span&gt;
    &lt;span class="nx"&gt;externalSystem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Some other code&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Some other code&lt;/span&gt;
&lt;span class="nx"&gt;pushPageDataToExternalSystem&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, you can defer the initialization and execution of the &lt;code&gt;pushPageDataToExternalSystem()&lt;/code&gt; function:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;insert your custom JS code into PHP variable&lt;/li&gt;
&lt;li&gt;assign the variable to the JS variable using the &lt;code&gt;$escaper-&amp;gt;escapeJs()&lt;/code&gt; function&lt;/li&gt;
&lt;li&gt;use the &lt;code&gt;init-external-scripts&lt;/code&gt; event to initialize and execute the &lt;code&gt;pushPageDataToExternalSystem()&lt;/code&gt; function
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="nv"&gt;$scriptString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;&amp;lt;&amp;lt;&amp;lt;script
    &amp;lt;script&amp;gt;
        function pushPageDataToExternalSystem() {
            // Get page data
            const pageData = {
                url: window.location.href,
                title: document.title,
                // ...
            };

            // Push page data to external system
            externalSystem.push(pageData);

            // Some other code
        }

        pushPageDataToExternalSystem();
    &amp;lt;/script&amp;gt;
script;&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uniq_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dispatchUserInteractionEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dispatchUserInteractionEvent&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;init-external-scripts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
            &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dispatchUserInteractionEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;once&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;passive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
        &lt;span class="p"&gt;})([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;touchstart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouseover&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wheel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scroll&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;init-external-scripts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;?= $escaper-&amp;gt;escapeJs($scriptString) ?&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uniq_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;setInnerHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;once&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;passive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function &lt;code&gt;pushPageDataToExternalSystem()&lt;/code&gt; will be executed after the user interaction, which contributes to minimizing the DOM size and JS main-thread work.&lt;br&gt;
Obviously, this function is not critical for the performance, but when you apply this approach for tons of JS code, the profit will surprise you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Largest Contentful Paint (LCP) optimization
&lt;/h3&gt;

&lt;p&gt;The common problem with LCP is the loading of the images. In most cases, it's some Banner image on the Home page or Product image on the Product page.&lt;br&gt;
You can find a lot of posts about LCP optimization: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/docs/lighthouse/performance/uses-responsive-images/"&gt;Serve the optimal image size&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/docs/lighthouse/performance/uses-webp-images/"&gt;Use modern image formats&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/docs/lighthouse/performance/uses-optimized-images/"&gt;Compress images&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;etc... , and it's really helpful. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do if you have already done all of this and still have a problem with LCP?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here I would like to share with you one tip that is not so common, but beneficial.&lt;br&gt;
You should load the image ASAP. It means that you should load the image with the highest priority.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LCP scores&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;LCP phase&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time to first byte (TTFB)&lt;/td&gt;
&lt;td&gt;The time from when the user initiates loading the page &lt;br&gt;until when the browser receives the first byte of the HTML document response.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load delay&lt;/td&gt;
&lt;td&gt;The delta between TTFB and when the browser starts loading the LCP resource.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load time&lt;/td&gt;
&lt;td&gt;The time it takes to load the LCP resource itself.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Render delay&lt;/td&gt;
&lt;td&gt;The delta between when the LCP resource finishes loading until the LCP element is fully rendered.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In most cases, the LCP score is high because of the long &lt;code&gt;load delay&lt;/code&gt; and &lt;code&gt;load time&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;These two metrics are more important than &lt;code&gt;TTFB&lt;/code&gt; and &lt;code&gt;render delay&lt;/code&gt;.&lt;br&gt;
So, you should focus on them. To determine the LCP resource, you can use the Chrome DevTools. Open the Network tab and check the order of the LCP resource loading.&lt;br&gt;
The LCP resource should be loaded as soon as possible. If you have some other resources loaded before the LCP resource, you should change the loading order.&lt;br&gt;
As faster LCP resources will be loaded, as better &lt;code&gt;load delay&lt;/code&gt; and &lt;code&gt;load time&lt;/code&gt; metrics will be.&lt;/p&gt;

&lt;p&gt;I can't provide you with the exact solution because it depends on your project, but I hope this tip will help you improve the LCP score.&lt;br&gt;
It's better than just a solution, I give you an idea. Do your magic!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;There are a lot of things that you can do to improve your performance. I hope this article will help you improve your store's performance.&lt;br&gt;
I will be glad to hear your feedback and ideas. Feel free to contact me if you have any questions.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Should you upgrade your Magento store at all? Developer view</title>
      <dc:creator>Vladyslav Podorozhnyi 🇺🇦 🌻</dc:creator>
      <pubDate>Fri, 27 Oct 2023 15:45:13 +0000</pubDate>
      <link>https://forem.com/run_as_root/should-you-upgrade-your-magento-store-at-all-developer-view-k8h</link>
      <guid>https://forem.com/run_as_root/should-you-upgrade-your-magento-store-at-all-developer-view-k8h</guid>
      <description>&lt;p&gt;Hello everyone. 👋 &lt;/p&gt;

&lt;p&gt;This article will be a bit less technical and more about the value of the upgrade for store owners. However, all of my thoughts would be described from the view of the developer that design, implement, and maintain such stores - so expect at least some level of technical expertise and judgments here.&lt;br&gt;&lt;br&gt;
Obviously, we would speak in contexts of Magento 2 / Adobe Commerce / Mage-OS - but I believe that findings and ideas described here would be useful for Shopware too, as well as for any other e-commerce system.&lt;br&gt;&lt;br&gt;
So - lets move on and have a small walkthrough of this topic. 🚶‍♀️🚶‍♂️&lt;/p&gt;




&lt;h2&gt;
  
  
  What does any upgrade bring us:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;new features&lt;/li&gt;
&lt;li&gt;new possibilities for developers&lt;/li&gt;
&lt;li&gt;bugfixes&lt;/li&gt;
&lt;li&gt;security fixes&lt;/li&gt;
&lt;li&gt;agility - possibility to upgrade faster to all future New versions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s check each point and ask next questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What value does it bring to the project?&lt;/li&gt;
&lt;li&gt;Price?&lt;/li&gt;
&lt;li&gt;Do we need this value?&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  New features
&lt;/h3&gt;

&lt;h4&gt;
  
  
  What value does it bring to the project?
&lt;/h4&gt;

&lt;p&gt;New business possibilities for store owners. Or improved UX in case it is an upgrade of Hyvä.&lt;br&gt;&lt;br&gt;
Or improved code execution performance ( yes - I think it is a feature 😄 ).&lt;/p&gt;

&lt;h4&gt;
  
  
  Price?
&lt;/h4&gt;

&lt;p&gt;Is it free? - YES. Then we need it!  [ actually, it is not free - see my explanation below ]&lt;br&gt;&lt;br&gt;
That is what the client says 😄. For him, it looks like a FREE fancy addition to all the hard work that is going to be done.&lt;br&gt;&lt;br&gt;
Not all features from the upgrade can be easily adapted if we take into account all customization project already has - the minimum you should consider is styling, maximum - is rebuilding business logic from top to bottom.&lt;/p&gt;

&lt;h4&gt;
  
  
  Do we need this value?
&lt;/h4&gt;

&lt;p&gt;If the store owner needs a new feature - the store owner would ask for it explicitly. 😄&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Answer: more NO than yes.&lt;/strong&gt; At least in scope of the upgrade.&lt;/p&gt;




&lt;h3&gt;
  
  
  New Possibilities for Developers
&lt;/h3&gt;

&lt;h4&gt;
  
  
  What value does it bring to the project?
&lt;/h4&gt;

&lt;p&gt;We have some variations here, but here is a compilation of the benefits it gives us:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;build code faster&lt;/li&gt;
&lt;li&gt;Make code less shaky &amp;amp; buggy&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Price?
&lt;/h4&gt;

&lt;p&gt;Is it free? - YES. Then we need it!&lt;br&gt;&lt;br&gt;
That is what the developer says 😄 (déjà vu). And again - it looks free, but there could be a clown that eats you alive. 🤡  &lt;/p&gt;

&lt;h4&gt;
  
  
  Do we need this value?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Answer: It depends - but more NO than yes.&lt;/strong&gt; Unless it is a painless upgrade from php 5 to php 7 - then totally yes, yep?   😄&lt;br&gt;
Don't get me wrong - I love new technologies and new versions of old ones, and I wish to work on the edge of tech!  However, is it worth it here and now? 🤷&lt;/p&gt;

&lt;p&gt;I would plan tech stack upgrade not as an additional point to the system/extension upgrade - but as quite a big chunk of work. So plan carefully and do not think it will be magically transferred without any effort.&lt;/p&gt;




&lt;h3&gt;
  
  
  Bugfixes
&lt;/h3&gt;

&lt;h4&gt;
  
  
  What value does it bring to the project?
&lt;/h4&gt;

&lt;p&gt;less bugs 😅&lt;/p&gt;

&lt;h4&gt;
  
  
  Price?
&lt;/h4&gt;

&lt;p&gt;Is it a pain in the ass? - sure.&lt;br&gt;&lt;br&gt;
Is it more expensive than just fixing it by yourself? - usually, no.&lt;/p&gt;

&lt;h4&gt;
  
  
  Do we need this value?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Answer: Yes in 99% of the cases. Other 1% - Developer decides.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Upgrade is a tool to achieve a goal - in this case to fix the bug/bugs. But monkeypatch is also a tool 🙉.&lt;br&gt;&lt;br&gt;
Choose wisely!&lt;/p&gt;




&lt;h3&gt;
  
  
  Security Fixes
&lt;/h3&gt;

&lt;h4&gt;
  
  
  What value does it bring to the project?
&lt;/h4&gt;

&lt;p&gt;Shop won’t be spammed / hacked / Arbitrary code execution won't happen.&lt;br&gt;&lt;br&gt;
e.g. - no disappointed buyers attack you 😸&lt;/p&gt;

&lt;h4&gt;
  
  
  Price?
&lt;/h4&gt;

&lt;p&gt;Well - security is priceless 😎&lt;br&gt;&lt;br&gt;
However, sometimes it is cheaper to just transfer security fix changes - if you know what and where to transfer, which is not usually the case.&lt;br&gt;
Let’s say it this way - I would not risk the security of my client to save another couple of hours on an upgrade.&lt;/p&gt;

&lt;h4&gt;
  
  
  Do we need this value?
&lt;/h4&gt;

&lt;p&gt;Yes - we need it. Always.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Answer: Yes.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Agility - you can upgrade faster to all future NEW versions
&lt;/h3&gt;

&lt;h4&gt;
  
  
  What value does it bring to the project?
&lt;/h4&gt;

&lt;p&gt;Constant upgrades -&amp;gt; minimise time spent on one particular upgrade.&lt;br&gt;&lt;br&gt;
e.g. - clean your home once a week and it takes you half an hour, do it once a year - you would cry for help and won’t finish in a days 🙉&lt;/p&gt;

&lt;h4&gt;
  
  
  Price?
&lt;/h4&gt;

&lt;p&gt;Do upgrades constantly - usually, once a quarter you have a patch upgrade, and some small p1, p2, p… versions during the quarter. Also, once 1,5 years, you do the bigger upgrade (Minor by semantic ver and somewhat Major based on Magento lifecycle ).&lt;br&gt;
In return, you will get a healthy webshop with a high level of agility to get new versions. Recent security patches once again reminded us how important it is to be on the edge (see &lt;a href="https://helpx.adobe.com/security/products/magento/apsb23-50.html" rel="noopener noreferrer"&gt;APSB23-50&lt;/a&gt; ).&lt;/p&gt;

&lt;h4&gt;
  
  
  Do we need this value?
&lt;/h4&gt;

&lt;p&gt;I afraid so.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Answer: Yes.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Sum Up
&lt;/h3&gt;

&lt;p&gt;We need upgrades. As a minimum for security and agility needs. But also to keep the shop healthy and without technical debt.&lt;br&gt;&lt;br&gt;
It also gets in use for other points from our list and can be useful or harmful for different points.&lt;br&gt;&lt;br&gt;
Upgrade, as an ability, needs to be used mindfully but not blindly 🙈 Build correct expectations from your upgrade, estimate, and plan your work accordingly.&lt;br&gt;
Last but not least - explain why upgrades are needed and what is going to be delivered with this specific release.&lt;/p&gt;

&lt;p&gt;Hope my a bit structured thought flow will gonna help you to understand upgrades, what we need to take out of them, and how to communicate it right.  &lt;/p&gt;

&lt;p&gt;Chao Kako ☕ &lt;/p&gt;

</description>
      <category>adobecommerce</category>
      <category>magento2</category>
      <category>mageos</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Cron Jobs in Magento 2: How to Adjust Schedules and Cron Groups Seamlessly</title>
      <dc:creator>Vladyslav Podorozhnyi 🇺🇦 🌻</dc:creator>
      <pubDate>Fri, 22 Sep 2023 18:53:56 +0000</pubDate>
      <link>https://forem.com/run_as_root/cron-jobs-in-magento-2-how-to-adjust-schedules-and-cron-groups-seamlessly-4noj</link>
      <guid>https://forem.com/run_as_root/cron-jobs-in-magento-2-how-to-adjust-schedules-and-cron-groups-seamlessly-4noj</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello!&lt;/p&gt;

&lt;p&gt;In today's article, I'll cover a topic that's not too tricky, but can be a bit annoying without some useful tips. Specifically, we'll dive into Magento 2 ( Mage-OS / Adobe Commerce ) to discuss cron jobs, their configurations, and the process of rescheduling them and altering their execution group.&lt;/p&gt;

&lt;p&gt;You might wonder, why bother changing the schedule or group of a cron job? Let's find out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Adjust the Cron Schedule?
&lt;/h2&gt;

&lt;p&gt;Occasionally, a cron job might execute too frequently, leading to server load issues or even deadlocks. On the other hand, if it doesn't run often enough, you could be left waiting for emails that take an hour to send. This scenario becomes particularly tricky when these cron jobs are associated with third-party extensions.&lt;/p&gt;

&lt;p&gt;Let's imagine we are using 3rd party Feed and Mailing extensions, and we are not OK with their cron jobs schedule.&lt;br&gt;&lt;br&gt;
How can you modify the execution patterns of these cron jobs without tampering with the third-party code directly? By implementing changes in your own codebase – your module. Let's explore this next.&lt;/p&gt;
&lt;h2&gt;
  
  
  Rescheduling Cron Jobs
&lt;/h2&gt;

&lt;p&gt;Let's have a look at the cron job of the Feed extension &lt;code&gt;feed_generation&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;Here is its definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;config&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt; &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"urn:magento:module:Magento_Cron:etc/crontab.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;group&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"default"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      ...
        &lt;span class="nt"&gt;&amp;lt;job&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"feed_generation"&lt;/span&gt; &lt;span class="na"&gt;instance=&lt;/span&gt;&lt;span class="s"&gt;"\Feed\Cron\FeedGenerator"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"execute"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;schedule&amp;gt;&lt;/span&gt;* * * * *&lt;span class="nt"&gt;&amp;lt;/schedule&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/job&amp;gt;&lt;/span&gt;
      ...
    &lt;span class="nt"&gt;&amp;lt;/group&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Executing every minute, this job can impose a significant server load. We certainly don't need a new feed every 60 seconds!&lt;/p&gt;

&lt;p&gt;To modify its frequency, we'll craft a module, &lt;code&gt;Devto_ChangeFeedSchedule&lt;/code&gt;, and incorporate an &lt;code&gt;etc/config.xml&lt;/code&gt; file within.&lt;br&gt;&lt;br&gt;
So, the structure would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Devto_ChangeFeedSchedule/
│
├── etc/
│   ├── config.xml      # our config file
│   └── module.xml      
│
├── registration.php    
│
└── composer.json       
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;etc/config.xml&lt;/code&gt; should contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;config&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
        &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"urn:magento:module:Magento_Store:etc/config.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;default&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;crontab&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;default&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;lt;!-- cron group --&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;jobs&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;feed_generation&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;lt;!-- job name --&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;schedule&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;cron_expr&amp;gt;&lt;/span&gt;0 * * * *&lt;span class="nt"&gt;&amp;lt;/cron_expr&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;lt;!-- new cron execution schedule --&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/schedule&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/feed_generation&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/jobs&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/default&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/crontab&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/default&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And last but not least - add sequence into &lt;code&gt;etc/module.xml&lt;/code&gt; to declare dependency over Feed module and configs apply order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"&amp;gt;
    &amp;lt;module name="Devto_ChangeFeedSchedule" &amp;gt;
        &amp;lt;sequence&amp;gt;
            &amp;lt;!-- makes Devto_ChangeFeedSchedule configs be applied after Feed extension configs --&amp;gt;
            &amp;lt;module name="Vendor_FeedExtesnion"/&amp;gt;
        &amp;lt;/sequence&amp;gt;
    &amp;lt;/module&amp;gt;
&amp;lt;/config&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the module &lt;code&gt;Devto_ChangeFeedSchedule&lt;/code&gt; is activated and the cache cleared, your new job schedule will take effect - run every hour but not a minute.&lt;/p&gt;

&lt;p&gt;But what about cron groups? Let's delve into that next.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Need to Change Cron Group
&lt;/h2&gt;

&lt;p&gt;Imagine we're still working with the Feed and Mailing third-party extensions. These extensions utilize distinct cron groups, each operating as an isolated process. Thanks to this, cron groups can run concurrently/parallely, using individual processes.&lt;/p&gt;

&lt;p&gt;Now, suppose these two cron jobs conflict, resulting in data inconsistencies in the Feed and Emails. Worse yet, &lt;code&gt;deadlocks&lt;/code&gt; can crop up. You might ask, &lt;strong&gt;"Why the deadlocks?"&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Well, imagine these extensions read &amp;amp; write the same table, say &lt;code&gt;sales_order_item&lt;/code&gt;. When they run simultaneously, the stage is set for potential deadlocks. 🤷‍♂️&lt;/p&gt;

&lt;p&gt;If only these jobs were executed sequentially! By relocating them to a shared group, you can ensure they no longer run in parallel.&lt;/p&gt;
&lt;h2&gt;
  
  
  Altering the Cron Job's Group
&lt;/h2&gt;

&lt;p&gt;A straightforward (though not perfect) solution would be to transfer the Mailing extension's cron job to the &lt;code&gt;default&lt;/code&gt; group, where the Feed extension's job resides. Here's the Mailing cron job definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;config&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt; &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"urn:magento:module:Magento_Cron:etc/crontab.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;group&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"mailing_extension"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;lt;!-- mailing cron group --&amp;gt;&lt;/span&gt;
      ...
        &lt;span class="nt"&gt;&amp;lt;job&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"send_mails_from_mailing_extension"&lt;/span&gt; &lt;span class="na"&gt;instance=&lt;/span&gt;&lt;span class="s"&gt;"\Mailing\Cron\SendMailsCron"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"execute"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;schedule&amp;gt;&lt;/span&gt;*/20 * * * *&lt;span class="nt"&gt;&amp;lt;/schedule&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/job&amp;gt;&lt;/span&gt;
      ...
    &lt;span class="nt"&gt;&amp;lt;/group&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, a small, &lt;strong&gt;non-intuitive trick&lt;/strong&gt; is essential here: Simply changing the group by redefining &lt;code&gt;crontab.xml&lt;/code&gt; won't work. Instead, you need to craft a new cron job while deactivating the old one. This step is necessary since &lt;code&gt;crontab.xml&lt;/code&gt; configurations (especially groups) can't be overridden, only augmented.&lt;/p&gt;

&lt;p&gt;For instance, merely defining the &lt;code&gt;send_mails_from_mailing_extension&lt;/code&gt; cron job in the &lt;code&gt;default&lt;/code&gt; group would make it run in both the &lt;code&gt;default&lt;/code&gt; and &lt;code&gt;mailing_extension&lt;/code&gt; groups.&lt;/p&gt;

&lt;p&gt;To disable &lt;code&gt;send_mails_from_mailing_extension&lt;/code&gt;, an easy trick is to schedule it for February 30th—a date that doesn't exist!&lt;/p&gt;

&lt;p&gt;&lt;u&gt;nerd fact 🤓:&lt;/u&gt; &lt;em&gt;actually February 30 happened in Sweden in 1712&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Following our earlier approach, we'd add the following content to the &lt;code&gt;etc/config.xml&lt;/code&gt; in our module, &lt;code&gt;Devto_ChangeMailingJobGroup&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;config&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
        &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"urn:magento:module:Magento_Store:etc/config.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;default&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;crontab&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;default&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;jobs&amp;gt;&lt;/span&gt;
                    &lt;span class="c"&gt;&amp;lt;!-- Disable cron execution by scheduling it to Feb 30th. --&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;send_mails_from_mailing_extension&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;schedule&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;cron_expr&amp;gt;&lt;/span&gt;0 0 30 2 *&lt;span class="nt"&gt;&amp;lt;/cron_expr&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/schedule&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/send_mails_from_mailing_extension&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/jobs&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/default&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/crontab&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/default&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, define a &lt;strong&gt;NEW&lt;/strong&gt; cron job in the &lt;code&gt;default&lt;/code&gt; group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;config&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt; &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"urn:magento:module:Magento_Cron:etc/crontab.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;group&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"default"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;lt;!-- default cron group --&amp;gt;&lt;/span&gt;
      ...
        &lt;span class="c"&gt;&amp;lt;!-- added devto prefix to cron name --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;job&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"devto_send_mails_from_mailing_extension"&lt;/span&gt; &lt;span class="na"&gt;instance=&lt;/span&gt;&lt;span class="s"&gt;"\Mailing\Cron\SendMailsCron"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"execute"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;schedule&amp;gt;&lt;/span&gt;*/20 * * * *&lt;span class="nt"&gt;&amp;lt;/schedule&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/job&amp;gt;&lt;/span&gt;
      ...
    &lt;span class="nt"&gt;&amp;lt;/group&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And do not forget to add Mailing module sequence into &lt;code&gt;etc/module.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"&amp;gt;
    &amp;lt;module name="Devto_ChangeMailingJobGroup" &amp;gt;
        &amp;lt;sequence&amp;gt;
            &amp;lt;!-- makes Devto_ChangeMailingJobGroup configs be applied after Mailing extension configs --&amp;gt;
            &amp;lt;module name="Vendor_MailingExtesnion"/&amp;gt;
        &amp;lt;/sequence&amp;gt;
    &amp;lt;/module&amp;gt;
&amp;lt;/config&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After all changes, the module structure should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Devto_ChangeMailingJobGroup/
│
├── etc/
│   ├── config.xml      # our config file
|   ├── crontab.xml     # our crontab definition file  
│   └── module.xml      
│
├── registration.php    
│
└── composer.json       
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once &lt;code&gt;Devto_ChangeMailingJobGroup&lt;/code&gt; is active and the cache refreshed, the &lt;code&gt;send_mails_from_mailing_extension&lt;/code&gt; job will be disabled. Meanwhile, its counterpart, &lt;code&gt;devto_send_mails_from_mailing_extension&lt;/code&gt;, will operate in the &lt;code&gt;default&lt;/code&gt; cron group alongside the Feed cron job.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Word of Caution on the Default Group
&lt;/h2&gt;

&lt;p&gt;It's vital to recognize that Magento's native cron jobs already densely populate the &lt;code&gt;default&lt;/code&gt; cron group. Overloading it might cause "traffic jams" if:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Jobs have lengthy runtimes:&lt;/strong&gt; If your tasks take too long to finish, they could end up waiting in line, slowing things down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overfrequent Scheduling:&lt;/strong&gt; Setting jobs to run too often can result in backlogs and potential overlaps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overpopulation:&lt;/strong&gt; Filling the group with too many tasks, even if they are quick, might overwhelm the system.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, while the default group is convenient, treat it like a city's main highway during rush hour. It can handle a lot, but there's a limit before things get clogged. If you have too many heavy tasks or they're scheduled too frequently, you'll likely end up with delays.   &lt;/p&gt;

&lt;p&gt;In short, be mindful of the workload you're placing on the &lt;code&gt;default&lt;/code&gt; group and consider creating your own cron groups to avoid conflict of processes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;And that's the lowdown on Magento's cron jobs, how to shuffle their schedules, and adjust their groups without diving into third-party code directly. With the knowledge you've gathered here, tweaking those pesky cron jobs should be much easier 😊&lt;/p&gt;

&lt;p&gt;Thank you for reading!&lt;/p&gt;

</description>
      <category>magento2</category>
      <category>adobecommerce</category>
      <category>webdev</category>
      <category>mageos</category>
    </item>
    <item>
      <title>Preventing Transaction Loss: Unleashing the Power of Resilient Transactions with RabbitMQ dead-letter Exchanges in Magento 2</title>
      <dc:creator>Cristiano Pacheco</dc:creator>
      <pubDate>Mon, 28 Aug 2023 10:57:40 +0000</pubDate>
      <link>https://forem.com/run_as_root/preventing-transaction-loss-unleashing-the-power-of-resilient-transactions-with-rabbitmq-dead-letter-exchanges-in-magento-2-8h0</link>
      <guid>https://forem.com/run_as_root/preventing-transaction-loss-unleashing-the-power-of-resilient-transactions-with-rabbitmq-dead-letter-exchanges-in-magento-2-8h0</guid>
      <description>&lt;p&gt;This post will guide Magento 2 developers through implementing a message queue retry mechanism using the RabbitMQ dead-letter exchange feature.&lt;/p&gt;

&lt;p&gt;To assist us in this endeavor, we will leverage the &lt;a href="https://github.com/run-as-root/magento2-message-queue-retry" rel="noopener noreferrer"&gt;Message Queue Retry&lt;/a&gt; module by &lt;a href="https://run-as-root.sh" rel="noopener noreferrer"&gt;run-as-root&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What is RabbitMQ?&lt;/li&gt;
&lt;li&gt;What is a dead-letter exchange?&lt;/li&gt;
&lt;li&gt;Examples of transaction loss scenarios&lt;/li&gt;
&lt;li&gt;Practical case scenario&lt;/li&gt;
&lt;li&gt;Crafting an effective retry strategy for queue messages&lt;/li&gt;
&lt;li&gt;Practical case scenario with queue retry&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What is RabbitMQ?
&lt;/h3&gt;

&lt;p&gt;RabbitMQ is a widely embraced open-source Message Queue software, facilitating seamless interaction and scalability across numerous applications. As a proficient messaging broker, RabbitMQ furnishes a unified platform catering to Asynchronous messaging needs. Within this platform, applications gain the capability to transmit and receive messages securely, with RabbitMQ diligently ensuring the sanctuary of your messages until they are consumed.&lt;/p&gt;

&lt;p&gt;Since version 2.1.0, Magento 2 has offered native RabbitMQ support, enabling effortless queue configuration through XML settings. For further details, you can explore additional information &lt;a href="https://developer.adobe.com/commerce/php/development/components/message-queues/configuration/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a dead-letter exchange?
&lt;/h3&gt;

&lt;p&gt;This feature functions as a safety net within the messaging architecture, intended to manage messages that encounter unsuccessful processing by the consumer. &lt;/p&gt;

&lt;p&gt;The dead-letter exchange operates as a mechanism for directing these problematic messages to a designated queue referred to as a dead-letter queue. This facilitates developers in isolating and inspecting troublesome messages without interrupting the primary processing flow, enabling them to analyze these messages, identify the underlying reasons for processing failures, and implement essential adjustments to ensure smooth operation. &lt;/p&gt;

&lt;p&gt;It's particularly recommended for scenarios where upholding message integrity, diagnosing processing errors, and averting message loss is paramount.&lt;/p&gt;

&lt;h4&gt;
  
  
  Examples of transaction loss scenarios:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Exporting the customers' data to a CRM&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Possible issue&lt;/strong&gt;: The CRM credentials were modified, but the changes were not reflected in the Magento integration settings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consequence&lt;/strong&gt;: This mismatch in credentials will result in failed API calls due to the usage of incorrect or invalid authentication information.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Exporting the orders to an ERP&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Possible issue&lt;/strong&gt;: During a marketing campaign, the store's throughput surged by 200%, leading to a substantial rise in incoming orders; however, the ERP system could only accommodate its standard order import capacity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consequence&lt;/strong&gt;: Only a fraction of the increased order influx will be successfully exported to the ERP system, potentially causing delays and incomplete order processing.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Scheduled price uplift&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine having the capability to schedule a comprehensive price uplift by 10% for the entire catalog of your store.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Possible issue&lt;/strong&gt;: Deadlock encountered while saving product and price information.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consequence&lt;/strong&gt;: The product prices may not be accurate.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Practical case scenario
&lt;/h3&gt;

&lt;p&gt;A typical case in e-commerce is exporting orders to an ERP or OMS. We will implement an asynchronous order export architecture, where for each placed order, a message is created with the order id and sent to a queue. So that a consumer can later read this message and export it to the external system.&lt;/p&gt;

&lt;p&gt;To simplify this example, let's declare the topic's name, exchange, queue, and bindings with the same name: &lt;code&gt;erp_order_export&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To handle this messaging, we'll utilize RabbitMQ as the broker.&lt;/p&gt;

&lt;p&gt;You can find the complete code for the module on &lt;a href="https://github.com/cristiano-pacheco/magento2-order-export" rel="noopener noreferrer"&gt;this&lt;/a&gt; repository.&lt;/p&gt;

&lt;p&gt;We have to create four files to declare our queue.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;etc/communication.xml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;config&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
    &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"urn:magento:framework:Communication/etc/communication.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;topic&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export"&lt;/span&gt; &lt;span class="na"&gt;request=&lt;/span&gt;&lt;span class="s"&gt;"CristianoPacheco\OrderExport\Api\Data\OrderExportDataInterface"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;etc/queue_consumer.xml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;config&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
        &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"urn:magento:framework-message-queue:etc/consumer.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;consumer&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export"&lt;/span&gt; &lt;span class="na"&gt;queue=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export"&lt;/span&gt; &lt;span class="na"&gt;connection=&lt;/span&gt;&lt;span class="s"&gt;"amqp"&lt;/span&gt;
              &lt;span class="na"&gt;handler=&lt;/span&gt;&lt;span class="s"&gt;"CristianoPacheco\OrderExport\Queue\Consumer\ErpOrderExportConsumer::execute"&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;etc/queue_publisher.xml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;config&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
        &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"urn:magento:framework-message-queue:etc/publisher.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;publisher&lt;/span&gt; &lt;span class="na"&gt;topic=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;connection&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"amqp"&lt;/span&gt; &lt;span class="na"&gt;exchange=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/publisher&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;etc/queue_topology.xml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;config&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
        &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"urn:magento:framework-message-queue:etc/topology.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;exchange&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export"&lt;/span&gt; &lt;span class="na"&gt;connection=&lt;/span&gt;&lt;span class="s"&gt;"amqp"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"topic"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;binding&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export"&lt;/span&gt; &lt;span class="na"&gt;topic=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export"&lt;/span&gt; &lt;span class="na"&gt;destinationType=&lt;/span&gt;&lt;span class="s"&gt;"queue"&lt;/span&gt; &lt;span class="na"&gt;destination=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/exchange&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the command below in order to Magento send the command to create the exchange, queue and bindings on RabbitMQ:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/magento setup:upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will look like this on RabbitMQ admin:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5cn2as7fygwibk5y8s5d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5cn2as7fygwibk5y8s5d.png" alt="exchange" width="723" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcoztkd9mjxxnctkt20ga.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcoztkd9mjxxnctkt20ga.png" alt="Queue" width="800" height="566"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Visually, our configs will generate something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fma4lo5xr9xyjqzat4ymf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fma4lo5xr9xyjqzat4ymf.png" alt="queue" width="800" height="847"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: It would make more sense to configure a &lt;a href="https://www.rabbitmq.com/tutorials/amqp-concepts.html#exchange-direct" rel="noopener noreferrer"&gt;direct exchange&lt;/a&gt;. However, up to the current version of Magento (2.4.6), only &lt;a href="https://www.rabbitmq.com/tutorials/amqp-concepts.html#exchange-topic" rel="noopener noreferrer"&gt;topic exchanges&lt;/a&gt; are supported.&lt;/p&gt;

&lt;p&gt;Now that the queue is set up, we must send the order ID to the &lt;code&gt;erp_order_export&lt;/code&gt; queue every time a successful order is placed.&lt;/p&gt;

&lt;p&gt;We can easily do this by listening to the event &lt;code&gt;sales_model_service_quote_submit_success&lt;/code&gt; via an observer:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;etc/events.xml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;config&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
        &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"urn:magento:framework:Event/etc/events.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;event&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"sales_model_service_quote_submit_success"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;observer&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"erp_export_order"&lt;/span&gt; &lt;span class="na"&gt;instance=&lt;/span&gt;&lt;span class="s"&gt;"CristianoPacheco\OrderExport\Observer\SendOrderToQueueObserver"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/event&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I won't post here the Observer code or the entire implementation. The complete working code is in the &lt;a href="https://github.com/cristiano-pacheco/magento2-order-export" rel="noopener noreferrer"&gt;repository&lt;/a&gt;. If you want to simulate the scenarios, please install the module.&lt;/p&gt;

&lt;p&gt;Our consumer will use a service to perform our "fake" order export. To simulate an error, our service will always throw an exception:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;CristianoPacheco\OrderExport\Service&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;CristianoPacheco\OrderExport\Api\Data\OrderExportDataInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExportOrderToErpService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;OrderExportDataInterface&lt;/span&gt; &lt;span class="nv"&gt;$orderExportData&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$orderId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$orderExportData&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getOrderId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"The order &lt;/span&gt;&lt;span class="nv"&gt;$orderId&lt;/span&gt;&lt;span class="s2"&gt; id is invalid"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the current configuration, orders will enter the &lt;code&gt;erp_order_export&lt;/code&gt; queue and can be "exported" during the execution of our consumer.&lt;/p&gt;

&lt;p&gt;To run our consumer and export only one order per execution, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/magento queue:consumer:start erp_order_export &lt;span class="nt"&gt;--single-thread&lt;/span&gt; &lt;span class="nt"&gt;--max-messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What will happen at the end of the execution?&lt;/p&gt;

&lt;p&gt;The service will throw an exception, and it will be handled here:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vendor/magento/framework-message-queue/Consumer.php:261&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Famgo3g8dis9jnkmn36s4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Famgo3g8dis9jnkmn36s4.png" alt="exception-handle" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the message will be rejected, and the requeue flag will have &lt;code&gt;false&lt;/code&gt; as the value. In other words, the message will be removed from the queue, and the order will not be exported to the ERP.&lt;/p&gt;

&lt;p&gt;Well done! We managed to simulate a transaction loss.&lt;/p&gt;

&lt;p&gt;In day-to-day scenarios, probably (I hope so), exceptions are handled at the service or the consumer's execution entry point, and the message would be sent back to the original queue or even taken manually in cases of error, such as sending an alert, or persisting the message in a database, etc.&lt;/p&gt;

&lt;p&gt;What if there was a way to automatically identify without manual work when there are errors in message processing, allowing the possibility to optimize and standardize this process?&lt;/p&gt;

&lt;p&gt;Yes, there is. We can delegate this hard work to RabbitMQ via a dead-letter exchange.&lt;/p&gt;

&lt;h3&gt;
  
  
  Crafting an Effective Retry Strategy for Queue Messages
&lt;/h3&gt;

&lt;p&gt;So, how do we ensure that our messages reach their destination in the face of these intermittent failures? This is where the concept of a retry strategy comes into play. A retry strategy outlines how we handle failed operations by making subsequent attempts at sending the same message. When exporting an order to ERP, adopting a well-defined retry strategy can significantly enhance the reliability of our application.&lt;/p&gt;

&lt;p&gt;For this specific case, three attempts are reasonable.&lt;/p&gt;

&lt;p&gt;So let's define our requirements, it should be possible to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reprocess the messages&lt;/li&gt;
&lt;li&gt;Set processing retry limit&lt;/li&gt;
&lt;li&gt;Set an interval in minutes for each retry attempt&lt;/li&gt;
&lt;li&gt;Find out what error was generated during message processing&lt;/li&gt;
&lt;li&gt;See what the message payload is&lt;/li&gt;
&lt;li&gt;If the limit of retries is reached, we must provide a way to manage message processing manually

&lt;ul&gt;
&lt;li&gt;Resend the message to the queue&lt;/li&gt;
&lt;li&gt;Discard the message&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Steps to achieve our goal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;We will add a second queue (&lt;code&gt;erp_order_export_delay&lt;/code&gt;) in RabbitMQ to store the message for a specific lifetime. This queue will not have a consumer. It will only serve to "delay" processing the message in the source queue (&lt;code&gt;erp_order_export&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When the time to live is reached, RabbitMQ will automatically send the message to the source queue (&lt;code&gt;erp_order_export&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the retry limit is reached, the message will be stored in a database table for manual management.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maybe the ERP server is down, or some order data is incorrect, so we have to reprocess the messages later.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Steps 1 and 2 can be configured in the queue declaration, and RabbitMQ natively supports it.&lt;/p&gt;

&lt;p&gt;Step 3 is a capability provided by the run as root &lt;a href="https://github.com/run-as-root/magento2-message-queue-retry" rel="noopener noreferrer"&gt;Message Queue Retry&lt;/a&gt; module.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practical case scenario with queue retry
&lt;/h3&gt;

&lt;p&gt;Firstly we need to &lt;a href="https://github.com/run-as-root/magento2-message-queue-retry#installation" rel="noopener noreferrer"&gt;install&lt;/a&gt; the message queue retry module and &lt;a href="https://github.com/run-as-root/magento2-message-queue-retry#3-enabling-the-message-queue-retry-admin-configuration" rel="noopener noreferrer"&gt;enable&lt;/a&gt; the behavior to persist the messages on the database after the retry limit is reached.&lt;/p&gt;

&lt;p&gt;No we have to configure our dead-letter exchange. The complete implementation of code below can be found &lt;a href="https://github.com/cristiano-pacheco/magento2-order-export/tree/dead-letter-exchange" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We'll use the &lt;code&gt;exp_order_export_delay&lt;/code&gt; as the name for the topic, exchange, queue and bindings.&lt;/p&gt;

&lt;p&gt;Declaring the topic:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;etc/communication.xml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;config&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
        &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"urn:magento:framework:Communication/etc/communication.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;topic&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export"&lt;/span&gt; &lt;span class="na"&gt;request=&lt;/span&gt;&lt;span class="s"&gt;"CristianoPacheco\OrderExport\Api\Data\OrderExportDataInterface"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;topic&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export_delay"&lt;/span&gt; &lt;span class="na"&gt;request=&lt;/span&gt;&lt;span class="s"&gt;"CristianoPacheco\OrderExport\Api\Data\OrderExportDataInterface"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Declaring the retry limit:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;etc/queue_retry.xml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;config&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
        &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"urn:RunAsRoot:module:RunAsRoot_MessageQueueRetry:/etc/queue_retry.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;topic&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export"&lt;/span&gt; &lt;span class="na"&gt;retryLimit=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Declaring the dead-letter exchange:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;etc/queue_topology.xml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;config&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
        &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"urn:magento:framework-message-queue:etc/topology.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;exchange&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export"&lt;/span&gt; &lt;span class="na"&gt;connection=&lt;/span&gt;&lt;span class="s"&gt;"amqp"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"topic"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;binding&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export"&lt;/span&gt; &lt;span class="na"&gt;topic=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export"&lt;/span&gt; &lt;span class="na"&gt;destinationType=&lt;/span&gt;&lt;span class="s"&gt;"queue"&lt;/span&gt; &lt;span class="na"&gt;destination=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;arguments&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"x-dead-letter-exchange"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;erp_order_export_delay&lt;span class="nt"&gt;&amp;lt;/argument&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"x-dead-letter-routing-key"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;erp_order_export_delay&lt;span class="nt"&gt;&amp;lt;/argument&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/arguments&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/binding&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/exchange&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;exchange&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export_delay"&lt;/span&gt; &lt;span class="na"&gt;connection=&lt;/span&gt;&lt;span class="s"&gt;"amqp"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"topic"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;binding&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export_delay"&lt;/span&gt; &lt;span class="na"&gt;topic=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export_delay"&lt;/span&gt; &lt;span class="na"&gt;destinationType=&lt;/span&gt;&lt;span class="s"&gt;"queue"&lt;/span&gt;
                 &lt;span class="na"&gt;destination=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export_delay"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;arguments&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"x-dead-letter-exchange"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;erp_order_export&lt;span class="nt"&gt;&amp;lt;/argument&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"x-dead-letter-routing-key"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;erp_order_export&lt;span class="nt"&gt;&amp;lt;/argument&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"x-message-ttl"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;60000&lt;span class="nt"&gt;&amp;lt;/argument&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;&amp;lt;!-- 1 minute in  milliseconds --&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/arguments&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/binding&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/exchange&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's take a closer look at each change in the file above.&lt;/p&gt;

&lt;p&gt;1- the arguments &lt;code&gt;x-dead-letter-exchange&lt;/code&gt; and &lt;code&gt;x-dead-letter-routing-key&lt;/code&gt; will tell RabbitMQ to automatically send the messages to the &lt;code&gt;erp_order_export_delay&lt;/code&gt; exchange in case the message is &lt;a href="https://www.rabbitmq.com/confirms.html" rel="noopener noreferrer"&gt;negatively acknowledged&lt;/a&gt; (rejected or nack with requeue parameter set to false).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;...
&lt;span class="nt"&gt;&amp;lt;arguments&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"x-dead-letter-exchange"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;erp_order_export_delay&lt;span class="nt"&gt;&amp;lt;/argument&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"x-dead-letter-routing-key"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;erp_order_export_delay&lt;span class="nt"&gt;&amp;lt;/argument&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/arguments&amp;gt;&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2- We declared a the &lt;code&gt;erp_order_export_delay&lt;/code&gt; exchange and bound it to the &lt;code&gt;erp_order_export_delay&lt;/code&gt; queue&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;...
&lt;span class="nt"&gt;&amp;lt;exchange&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export_delay"&lt;/span&gt; &lt;span class="na"&gt;connection=&lt;/span&gt;&lt;span class="s"&gt;"amqp"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"topic"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;binding&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export_delay"&lt;/span&gt; &lt;span class="na"&gt;topic=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export_delay"&lt;/span&gt; &lt;span class="na"&gt;destinationType=&lt;/span&gt;&lt;span class="s"&gt;"queue"&lt;/span&gt; &lt;span class="na"&gt;destination=&lt;/span&gt;&lt;span class="s"&gt;"erp_order_export_delay"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3- The arguments &lt;code&gt;x-dead-letter-exchange&lt;/code&gt; and &lt;code&gt;x-dead-letter-routing-key&lt;/code&gt; defines the return path for the message and the &lt;code&gt;x-message-ttl&lt;/code&gt; has an integer number in milliseconds that defines when the message will return to the exchanged defined in the previous two arguments.&lt;/p&gt;

&lt;p&gt;Remembering that this queue does not have a consumer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;...
&lt;span class="nt"&gt;&amp;lt;arguments&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"x-dead-letter-exchange"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;erp_order_export&lt;span class="nt"&gt;&amp;lt;/argument&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"x-dead-letter-routing-key"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;erp_order_export&lt;span class="nt"&gt;&amp;lt;/argument&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"x-message-ttl"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;60000&lt;span class="nt"&gt;&amp;lt;/argument&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;&amp;lt;!-- 1 minute in  milliseconds --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/arguments&amp;gt;&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visually, our new configs will generate something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb6qtv7ttuiotgttgufem.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb6qtv7ttuiotgttgufem.png" alt="queue-after-config" width="800" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: We always send the messages to an exchange and the exchange will forward the messages to one or more queues. It is the same with dead-letter exchanges.&lt;/p&gt;

&lt;p&gt;Now we have to manually delete the &lt;code&gt;erp_order_export&lt;/code&gt; exchange and queue in RabbitMQ, because it won't be updated. You can do it in the RabbitMQ admin panel or via CLI.&lt;/p&gt;

&lt;p&gt;After you did it, you have to run the command below to reflect our changes in RabbitMQ:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/magento setup:upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if we navigate again in the RabbitMQ panel we should see:&lt;/p&gt;

&lt;p&gt;Exchanges:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhazsls98p4699zaeyviv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhazsls98p4699zaeyviv.png" alt="dead-letter-exchanges" width="718" height="626"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we open our exchanges an expand the bindings, is it possible to see the arguments we have declared before:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frxj1znm0kj9sc3rd8srx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frxj1znm0kj9sc3rd8srx.png" alt="exchange-1" width="782" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhm5xak26y98lffgxzoao.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhm5xak26y98lffgxzoao.png" alt="exchange-2" width="771" height="596"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Queues:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftv3m304jvaxfyhmv3zon.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftv3m304jvaxfyhmv3zon.png" alt="Queues" width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Did you notice that queues now have a few more features?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcipfzbmcrupa7ltc662.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcipfzbmcrupa7ltc662.png" alt="queue-features" width="143" height="94"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A homework for you, hover the mouse over each feature and wait a few seconds, a caption will be displayed informing what it is about. Also access each queue and expand the bindings section, see the values contained there.&lt;/p&gt;

&lt;p&gt;Now that we finished our RabbitMQ configuration, let's place a new order and start the &lt;code&gt;erp_order_export&lt;/code&gt; consumer to see what will occur.&lt;/p&gt;

&lt;p&gt;After place an order a new massage is in the &lt;code&gt;erp_order_export&lt;/code&gt; queue:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0atw5nkdp37imz7qk3j8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0atw5nkdp37imz7qk3j8.png" alt="first-execution" width="800" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's start our consumer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/magento queue:consumer:start erp_order_export &lt;span class="nt"&gt;--single-thread&lt;/span&gt; &lt;span class="nt"&gt;--max-messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if we take a look at the queues we'll see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftn5gyb3c2fn6o47el2ci.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftn5gyb3c2fn6o47el2ci.png" alt="second-execution" width="800" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During message consumption, our service threw an exception, simulating a problem and the message was &lt;code&gt;rejected&lt;/code&gt;. RabbitMQ sent the message to the &lt;code&gt;erp_order_expor_delay&lt;/code&gt; queue.&lt;/p&gt;

&lt;p&gt;After one minute the message was automatically sent to the &lt;code&gt;erp_order_export&lt;/code&gt; queue.&lt;/p&gt;

&lt;p&gt;Let's inspect the message headers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4b48c0l9u4wernb1a7s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4b48c0l9u4wernb1a7s.png" alt="first-rejection" width="701" height="762"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;x-death&lt;/code&gt; header has interesting information about the retries.&lt;/p&gt;

&lt;p&gt;Relevant attribute for us to analyse are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;count&lt;/code&gt; - it is the retry number&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reason&lt;/code&gt; - In our case, can be rejected or expired&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;time&lt;/code&gt; - The timestamp of the occurrence&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;exchange&lt;/code&gt;, &lt;code&gt;queue&lt;/code&gt;, &lt;code&gt;routing-keys&lt;/code&gt; - Where the message was at the time of occurrence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, what that image says is:&lt;/p&gt;

&lt;p&gt;Reading from bottom to top&lt;/p&gt;

&lt;p&gt;First occurrence&lt;/p&gt;

&lt;p&gt;The message was in the &lt;code&gt;erp_order_export&lt;/code&gt; queue and while processing it, it was rejected on Saturday, August 26, 2023 9:12:33 PM GMT-03:00&lt;/p&gt;

&lt;p&gt;Second occurrence&lt;/p&gt;

&lt;p&gt;The message was in the &lt;code&gt;erp_order_export_delay&lt;/code&gt; queue, and it was rejected on Saturday, August 26, 2023 9:12:53 PM GMT-03:00&lt;/p&gt;

&lt;p&gt;It has 20 seconds difference because I configured the TTL for 20 seconds :)&lt;/p&gt;

&lt;p&gt;This is the message headers after the second retry:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftbctsaq1bd3sahp81t0u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftbctsaq1bd3sahp81t0u.png" alt="second-execution" width="549" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the third retry:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff7pfvhmopq29ulmxkom9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff7pfvhmopq29ulmxkom9.png" alt="third-execution" width="515" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What will occur in the next consumer execution?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ini3y2myn7y3uyen9u0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ini3y2myn7y3uyen9u0.png" alt="fourth-execution" width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, the run-as-root module intercepted the message processing failure, verified that the message has reached the retry limit and then persisted the message in the Magento database.&lt;/p&gt;

&lt;p&gt;Now it is possible to manage the message through the admin panel, in the path:&lt;/p&gt;

&lt;p&gt;Run as Root &amp;gt; Manage Messages&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa1wdd6349xlm39mqiurs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa1wdd6349xlm39mqiurs.png" alt="magento-admin" width="800" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now to can decide if you'll discard the message or send it back to the &lt;code&gt;erp_order_export&lt;/code&gt; queue.&lt;/p&gt;

&lt;p&gt;In summary, while multiple approaches exist to tackle this issue, my aim has been to show a straightforward method for to implement a retry mechanism. &lt;/p&gt;

&lt;p&gt;That's it for today, happy coding!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>magento2</category>
      <category>php</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Adobe Commerce CLI commands - or how to create a problem out of nothing</title>
      <dc:creator>Vladyslav Podorozhnyi 🇺🇦 🌻</dc:creator>
      <pubDate>Tue, 15 Aug 2023 19:45:38 +0000</pubDate>
      <link>https://forem.com/run_as_root/adobe-commerce-cli-commands-or-how-to-create-a-problem-out-of-nothing-eda</link>
      <guid>https://forem.com/run_as_root/adobe-commerce-cli-commands-or-how-to-create-a-problem-out-of-nothing-eda</guid>
      <description>&lt;h3&gt;
  
  
  Table Of Contents
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Intro&lt;/li&gt;
&lt;li&gt;The Underlying Problem &amp;amp; Its Roots&lt;/li&gt;
&lt;li&gt;The Classic "Chicken or the Egg" Dilemma&lt;/li&gt;
&lt;li&gt;The Pitfall of CLI Command Constructors&lt;/li&gt;
&lt;li&gt;Strategies to Prevent These Errors&lt;/li&gt;
&lt;li&gt;Simplify with Automated Proxy Injection&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Hello everyone,  &lt;/p&gt;

&lt;p&gt;In this article, I'll provide practical examples showcasing how unexpected challenges can arise in Magento 2 &amp;amp; Adobe Commerce projects while building and/or using custom CLI commands from 3rd parties. This becomes especially significant while working having CI/CD and performing project setups and test execution in pipelines.&lt;/p&gt;

&lt;p&gt;Introducing the main "hero" of our article: &lt;a href="https://github.com/alankent/magento2devbox-skeleton/issues/4" rel="noopener noreferrer"&gt;ISSUE: magento setup command is throwing magento.flag does't exist.&lt;/a&gt;. We'll take a deeper look into this issue, understanding its roots and finding ways to address it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; &lt;em&gt;Just a quick heads-up on naming - both Adobe Commerce and Magento 2 are covered in this article. After all, Adobe Commerce is Magento. Long Live Magento!&lt;/em&gt; 😁&lt;/p&gt;

&lt;h2&gt;
  
  
  The Underlying Problem &amp;amp; Its Roots
&lt;/h2&gt;

&lt;p&gt;While working on various Magento 2 projects, I frequently encountered snags when performing a &lt;code&gt;clean&lt;/code&gt; setup of a project using a freshly created database. This usually happens during integration or functional test setups in pipelines &amp;amp; local environments. Still, this issue isn't exclusive to that scenario and can pop up during upgrades, migrations, or deployments.&lt;/p&gt;

&lt;p&gt;The culprits? Here are two pesky errors you might recognize:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

The default website isn't defined. Set the website and try again.


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For Commerce editions, there's also:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

SQLSTATE[42S02]: Base table or view not found: 1146 Table
 'magento2.flag' doesn't exist, query was: 
SELECT flag.* FROM flag WHERE (flag.flag_code='staging')


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;When you see these during the &lt;code&gt;php bin/magento setup:install&lt;/code&gt; command execution, they offer little insight into the real issue. After a few hair-pulling hours of research, you'd discover these errors stem from the premature invocation of the DB resource, triggering the initialization of config and state entities from the Magento DB.&lt;/p&gt;

&lt;p&gt;But here's the conundrum: You can't fetch data from the DB at this point because there aren't any tables present yet. They only appear and populate during the installation process, not beforehand.  ¯_(ツ)_/¯&lt;/p&gt;

&lt;h2&gt;
  
  
  The Classic "Chicken or the Egg" Dilemma
&lt;/h2&gt;

&lt;p&gt;So, we're caught in a circular problem: trying to install the system but getting stopped because it's not installed. Confusing, right? 🤪&lt;/p&gt;

&lt;p&gt;This happens because someone, somewhere, is prematurely calling the DB connection resource. It is obvious, and I hope you would be as grateful as Sam Eliot is for this revelation insight.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft77vk8glx0375v3ge19x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft77vk8glx0375v3ge19x.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This &lt;strong&gt;"someone"&lt;/strong&gt; should be inside &lt;code&gt;bin/magento&lt;/code&gt; and called before actual command execution. Usually, CLI command constructors are guilty here. Let me explain why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pitfall of CLI Command Constructors
&lt;/h2&gt;

&lt;p&gt;For clarity, take a look at this code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DoSmthing&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;?string&lt;/span&gt; &lt;span class="nv"&gt;$dummyConfigValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ScopeConfigInterface&lt;/span&gt; &lt;span class="nv"&gt;$storeManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;dummyConfigValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$storeManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'some/dummy/path'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ScopeInterface&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SCOPE_STORE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;?string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;dummyConfigValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DummyCommand&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="mf"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;DoSmthing&lt;/span&gt; &lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="mf"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The root of the problem lies in the constructors of both the command and model classes.&lt;br&gt;&lt;br&gt;
In the &lt;code&gt;DoSmthing&lt;/code&gt; class, the system attempts to retrieve a config value. This action calls the &lt;code&gt;store&lt;/code&gt;, &lt;code&gt;store_website&lt;/code&gt;, and &lt;code&gt;core_config_data&lt;/code&gt; tables when creating the &lt;code&gt;DummyCommand&lt;/code&gt; object.&lt;br&gt;&lt;br&gt;
And guess what happens when this code runs before the tables are ready? Yep, errors appear!&lt;/p&gt;

&lt;h4&gt;
  
  
  Lets repeat one more time:
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;We execute &lt;code&gt;php bin/magento setup:install&lt;/code&gt; ;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bin/magento&lt;/code&gt; application creates all instances of CLI commands;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DummyCommand&lt;/code&gt; instance depends on the &lt;code&gt;DoSmthing&lt;/code&gt; model. So &lt;code&gt;DoSmthing&lt;/code&gt; should be created before &lt;code&gt;DummyCommand&lt;/code&gt; object creation;&lt;/li&gt;
&lt;li&gt;Application performs creation of &lt;code&gt;DoSmthing&lt;/code&gt; model  and executes &lt;code&gt;DoSmthing::__construct&lt;/code&gt; method;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DoSmthing::__construct&lt;/code&gt; method calls to the config manager;&lt;/li&gt;
&lt;li&gt;Config manager calls to a chain of tables that are not created yet;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ERROR&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Strategies to Prevent These Errors
&lt;/h2&gt;

&lt;p&gt;Remember this golden rule: Every CLI command class initializes every time you invoke &lt;code&gt;php bin/magento&lt;/code&gt;. So, here's how to save yourself future headaches:&lt;/p&gt;

&lt;h3&gt;
  
  
  Rule 1 - Be Wise
&lt;/h3&gt;

&lt;p&gt;It is not the most wise solution to call for potential resource consuming operations like DB/API calls in &lt;code&gt;__construct&lt;/code&gt; methods of CLI command classes. Those operations will be constantly executed even once you'd have a wish to get only a list of available commands.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Rule 2 - Be Ready
&lt;/h3&gt;

&lt;p&gt;Steer clear of resources in &lt;code&gt;__construct&lt;/code&gt; that might NOT be available during runtime, especially since Magento tables aren't present during &lt;code&gt;setup:install&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rule 3 - Fix The Problem
&lt;/h3&gt;

&lt;p&gt;If you're not in control of the troublesome code, inject a &lt;code&gt;Proxy&lt;/code&gt; using di configs.&lt;br&gt;&lt;br&gt;
We can easily solve our problem from the example with &lt;code&gt;DummyCommand&lt;/code&gt; by just adding those lines in &lt;code&gt;di.xml&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;

    &lt;span class="nt"&gt;&amp;lt;type&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"AbcTest\DummyConsoleCommand\Console\Command\DummyCommand"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;arguments&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"model"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"object"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;\AbcTest\DummyConsoleCommand\Model\DoSmthing\Proxy&lt;span class="nt"&gt;&amp;lt;/argument&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/arguments&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/type&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Proxy is a well-known pattern, and in Magento, it is widely used to avoid unneeded code execution before we actually request it. In our example - &lt;code&gt;DoSmthing&lt;/code&gt; class object will not be instantiated until we do call for any method of this class.&lt;br&gt;&lt;br&gt;
No config retrieving while CLI class object creation - No error.&lt;br&gt;&lt;br&gt;
More details about proxy here: &lt;a href="https://developer.adobe.com/commerce/php/development/components/proxies/" rel="noopener noreferrer"&gt;Proxies&lt;/a&gt; .&lt;/p&gt;

&lt;h2&gt;
  
  
  Simplify with Automated Proxy Injection
&lt;/h2&gt;

&lt;p&gt;For those with a small number of extensions on their project, pinpointing the rogue CLI commands isn't too challenging. However, for more extensive, legacy projects or ones brimming with third-party extensions, identifying the culprits becomes an ordeal.&lt;/p&gt;

&lt;p&gt;To save time, we've crafted a solution to automate the proxy injection process. Introducing: &lt;strong&gt;&lt;a href="https://github.com/run-as-root/magento-cli-auto-proxy" rel="noopener noreferrer"&gt;run-as-root/magento-cli-auto-proxy&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This package injects &lt;code&gt;Proxy&lt;/code&gt; into all &lt;code&gt;__construct&lt;/code&gt; arguments of CLI commands, eligible for proxying. The result? No errors related to early resource access and, as a side affect, faster execution of &lt;code&gt;php bin/magento&lt;/code&gt; and php &lt;code&gt;bin/magento setup:install&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;Check out the README for more details. We always appreciate feedback and contributions!&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Let's sum it up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unthoughtfully created CLI commands can unexpectedly lead to problems, even if they appear harmless at first.&lt;/li&gt;
&lt;li&gt;The core of the problem often arises from &lt;strong&gt;premature database calls&lt;/strong&gt;, leading to a classic "chicken or the egg" scenario where system installation gets stalled.&lt;/li&gt;
&lt;li&gt;One significant pitfall identified is the &lt;strong&gt;CLI command constructors&lt;/strong&gt; and their potential to wreak havoc when invoked at the wrong time.&lt;/li&gt;
&lt;li&gt;To combat these challenges, we laid out three guiding principles:

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Be Wise&lt;/strong&gt; by avoiding resource-intensive calls in constructors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be Ready&lt;/strong&gt; by steering clear of resources that might not be available.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix The Problem&lt;/strong&gt; by utilizing proxies for problematic third-party code.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;To streamline and automate the solution, we introduced the package &lt;strong&gt;&lt;a href="https://github.com/run-as-root/magento-cli-auto-proxy" rel="noopener noreferrer"&gt;run-as-root/magento-cli-auto-proxy&lt;/a&gt;&lt;/strong&gt; which aims to automate proxy injections, offering a more efficient and error-free Magento experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you for diving deep into this article. I welcome all your questions and appreciate any feedback you might have.&lt;br&gt;
Happy coding and... &lt;strong&gt;Long Live Magento&lt;/strong&gt;! 😁&lt;/p&gt;

</description>
      <category>magento2</category>
      <category>adobecommerce</category>
      <category>webdev</category>
      <category>development</category>
    </item>
    <item>
      <title>Configuring a docker environment for a Magento 2 project</title>
      <dc:creator>Cristiano Pacheco</dc:creator>
      <pubDate>Tue, 25 Jul 2023 19:09:07 +0000</pubDate>
      <link>https://forem.com/run_as_root/configuring-a-docker-environment-for-a-magento-2-project-2bo6</link>
      <guid>https://forem.com/run_as_root/configuring-a-docker-environment-for-a-magento-2-project-2bo6</guid>
      <description>&lt;p&gt;This post will demonstrate how to configure a docker environment with the Warden tool for an existing Magento 2 project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What is Warden?&lt;/li&gt;
&lt;li&gt;Why Warden?&lt;/li&gt;
&lt;li&gt;Quickstart&lt;/li&gt;
&lt;li&gt;Requirements&lt;/li&gt;
&lt;li&gt;Project Stack&lt;/li&gt;
&lt;li&gt;
Configuring a single website/store Magento 2 project

&lt;ul&gt;
&lt;li&gt;Creating the project folder&lt;/li&gt;
&lt;li&gt;Creating the project folder&lt;/li&gt;
&lt;li&gt;Clone the Magento 2 code-base into the code-base folder&lt;/li&gt;
&lt;li&gt;Clone the Warden Magento 2 template&lt;/li&gt;
&lt;li&gt;
Update the warden .env file

&lt;ul&gt;
&lt;li&gt;A brief explanation of the keys in the .env file&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Run the docker environment&lt;/li&gt;

&lt;li&gt;Run composer install&lt;/li&gt;

&lt;li&gt;Copy and rename the env.warden.php file&lt;/li&gt;

&lt;li&gt;

Import the Magento 2 database

&lt;ul&gt;
&lt;li&gt;An Example of the content of this set-default-config.sh script&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Sign the SSL certificate&lt;/li&gt;

&lt;li&gt;Update your /etc/hosts file with the store domain&lt;/li&gt;

&lt;li&gt;Installing the sample data (Optional)&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;

Configuring a multiple website/store view Magento 2 project

&lt;ul&gt;
&lt;li&gt;Create a project folder&lt;/li&gt;
&lt;li&gt;Enter the created multisite directory&lt;/li&gt;
&lt;li&gt;Clone the Magento 2 code-base into the multisite code-base folder&lt;/li&gt;
&lt;li&gt;Clone the Warden Magento 2 template&lt;/li&gt;
&lt;li&gt;Change the warden .env file&lt;/li&gt;
&lt;li&gt;Define custom variables for the store domains&lt;/li&gt;
&lt;li&gt;Updates the the defaults config script&lt;/li&gt;
&lt;li&gt;Run the docker environment&lt;/li&gt;
&lt;li&gt;Composer install&lt;/li&gt;
&lt;li&gt;Copy and rename the env.warden.php file into the etc folder&lt;/li&gt;
&lt;li&gt;Import the Magento 2 multisite database&lt;/li&gt;
&lt;li&gt;Sign the SSL certificates for all domains&lt;/li&gt;
&lt;li&gt;Append your /etc/hosts file with the store domains&lt;/li&gt;
&lt;li&gt;Configuring the application domains&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  What is Warden?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.warden.dev/index.html" rel="noopener noreferrer"&gt;Warden&lt;/a&gt; is a CLI utility for orchestrating Docker-based developer environments that fully support Magento 1, Magento 2, Laravel, Symfony, and Shopware 6 on macOS, Linux and Windows (via WSL2).&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Warden?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Warden lets you run multiple environments simultaneously without conflicting ports.&lt;/li&gt;
&lt;li&gt;Allows you to configure stack versions easily via .env file configuration.&lt;/li&gt;
&lt;li&gt;Provides the full stack needed for a Magento 2 development environment.&lt;/li&gt;
&lt;li&gt;You can extend it and add new features according to your needs.&lt;/li&gt;
&lt;li&gt;No need to version the docker environment in the project.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Quickstart
&lt;/h3&gt;

&lt;p&gt;We will configure two Magento 2 projects, the first project will have one website and one store view, and the second project will have multiple websites and store views.&lt;/p&gt;

&lt;p&gt;The commands exposed here will work in Linux or macOS environment.&lt;/p&gt;

&lt;p&gt;We will use the &lt;code&gt;~/Sites&lt;/code&gt; directory to place the projects. Feel free to put your project in any path on your computer if you change the commands informed here.&lt;/p&gt;

&lt;p&gt;For each project, the following directory structure will be used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;📦project-name
 ┣ 📂code-base
 ┗ 📂docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;project-name/code-base&lt;/code&gt; directory will contain the Magento 2 project code&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;project-name/docker&lt;/code&gt; directory will contain the warden template for our docker environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The structure above is just a personal taste to use in my projects, you can structure as you see fit. For example using the &lt;code&gt;~/.warden&lt;/code&gt; directory&lt;/p&gt;

&lt;p&gt;Your Magento 2 project can be versioned alone in the repository, and there is no need to change the directory architecture.&lt;/p&gt;

&lt;p&gt;Here we'll use a &lt;a href="https://github.com/cristiano-pacheco/warden-env-magento2" rel="noopener noreferrer"&gt;fork&lt;/a&gt; of the &lt;a href="https://github.com/davidalger/warden-env-magento2" rel="noopener noreferrer"&gt;Warden Magento 2 Template&lt;/a&gt; that have a Makefile and a few common commands to facilitate the day-to-day work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;p&gt;All you need is &lt;a href="https://docs.docker.com/engine/install/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;, &lt;a href="https://docs.docker.com/compose/install/" rel="noopener noreferrer"&gt;docker-compose&lt;/a&gt; and &lt;a href="https://docs.warden.dev/installing.html" rel="noopener noreferrer"&gt;Warden&lt;/a&gt; installed on your computer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project Stack:
&lt;/h3&gt;

&lt;p&gt;For a practical example, we will set up the development environment for an existing Magento 2 project with the stack below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Magento 2.4.4 open source&lt;/li&gt;
&lt;li&gt;Nginx 1.16&lt;/li&gt;
&lt;li&gt;PHP 8.1

&lt;ul&gt;
&lt;li&gt;Composer 2&lt;/li&gt;
&lt;li&gt;Xdebug 3&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;MariaDB 10.4&lt;/li&gt;

&lt;li&gt;Elastic Search 7.16&lt;/li&gt;

&lt;li&gt;Redis 6.2&lt;/li&gt;

&lt;li&gt;RabbitMQ 3.9&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;More details about the required stack for Magento 2 &lt;a href="https://devdocs.magento.com/guides/v2.4/install-gde/system-requirements.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring a single website/store Magento 2 project
&lt;/h3&gt;

&lt;h5&gt;
  
  
  1 Creating the project folder
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir ~/Sites/magento244
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  2 Enter the created directory
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ~/Sites/magento244
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  3 Clone the Magento 2 code-base into the code-base folder
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone git@github.com:cristiano-pacheco/magento244.git code-base
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  4 Clone the Warden Magento 2 template
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone git@github.com:cristiano-pacheco/warden-env-magento2.git docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  5 Update the warden .env file
&lt;/h5&gt;

&lt;p&gt;Enter the docker directory&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ~/Sites/magento244/docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the .env file with the content below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WARDEN_ENV_NAME=magento244
WARDEN_ENV_TYPE=magento2
WARDEN_WEB_ROOT=/../code-base

TRAEFIK_DOMAIN=magento244.test
TRAEFIK_SUBDOMAIN=app

WARDEN_DB=1
WARDEN_ELASTICSEARCH=1
WARDEN_VARNISH=0
WARDEN_RABBITMQ=1
WARDEN_REDIS=1

ELASTICSEARCH_VERSION=7.16
MARIADB_VERSION=10.4
NODE_VERSION=10
PHP_VERSION=8.1
PHP_XDEBUG_3=1
COMPOSER_VERSION=2
RABBITMQ_VERSION=3.9
REDIS_VERSION=6.2
VARNISH_VERSION=7.0

MYSQL_USER=magento
MYSQL_PASSWORD=magento
MYSQL_DATABASE=magento

WARDEN_SYNC_IGNORE=

WARDEN_ALLURE=0
WARDEN_SELENIUM=0
WARDEN_SELENIUM_DEBUG=0
WARDEN_BLACKFIRE=0
WARDEN_SPLIT_SALES=0
WARDEN_SPLIT_CHECKOUT=0
WARDEN_TEST_DB=0
WARDEN_MAGEPACK=0

BLACKFIRE_CLIENT_ID=
BLACKFIRE_CLIENT_TOKEN=
BLACKFIRE_SERVER_ID=
BLACKFIRE_SERVER_TOKEN=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  A brief explanation of the keys in the .env file:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WARDEN_ENV_NAME&lt;/strong&gt;
It will be the prefix of the docker containers name and will also serve to &lt;a href="https://docs.warden.dev/configuration/xdebug.html" rel="noopener noreferrer"&gt;configure the xdebug&lt;/a&gt; in your IDE
Example:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fan6t9bixxsgitk8sh733.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fan6t9bixxsgitk8sh733.png" alt="warden-env-name" width="800" height="168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;WARDEN_ENV_TYPE&lt;/strong&gt;&lt;br&gt;
It will define the &lt;a href="https://github.com/davidalger/warden/tree/master/environments" rel="noopener noreferrer"&gt;docker compose yaml&lt;/a&gt; files that will be used in the &lt;a href="https://docs.warden.dev/environments.html" rel="noopener noreferrer"&gt;environment&lt;/a&gt;, which can vary according to the chosen platform.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;WARDEN_WEB_ROOT&lt;/strong&gt;&lt;br&gt;
Self explained, you have to inform the root directory of the project.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;TRAEFIK_DOMAIN&lt;/strong&gt;&lt;br&gt;
Traefik is used to &lt;a href="https://doc.traefik.io/traefik/routing/routers/" rel="noopener noreferrer"&gt;route&lt;/a&gt; the requests to the services (containers) dynamically. For example, when there is the cookie XDEBUG_SESSION with the value PHPSTORM, it will send the requests to the php-debug container that has the xdebug enabled. On the contrary, the php-fpm container will receive the request.&lt;br&gt;
You can set the domain you want, just pay attention to the .test suffix, it you configure a domain with a different value you will lost the &lt;a href="https://docs.warden.dev/configuration/dns-resolver.html" rel="noopener noreferrer"&gt;Automatic DNS Resolution&lt;/a&gt; feature.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;TRAEFIK_SUBDOMAIN&lt;/strong&gt;&lt;br&gt;
It is used to set a subdomain for your application, in our example the store can be accessed with the address &lt;code&gt;https://TRAEFIK_SUBDOMAIN.TRAEFIK_DOMAIN&lt;/code&gt; that is: &lt;code&gt;https://app.magento244.test&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;WARDEN_DB&lt;/strong&gt;, &lt;strong&gt;WARDEN_ELASTICSEARCH&lt;/strong&gt;, &lt;strong&gt;WARDEN_VARNISH&lt;/strong&gt;, &lt;strong&gt;WARDEN_RABBITMQ&lt;/strong&gt;, &lt;strong&gt;WARDEN_REDIS&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The keys above you can use 0 to disable and 1 to enable. When the value is 0 the container will not run.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;_VERSION&lt;/strong&gt;&lt;br&gt;
The keys with the suffix &lt;code&gt;_VERSION&lt;/code&gt; is to set a specific version to the service. For Magento 2.4.3, for example, you may use PHP 7.4. To do it, you can leave the key as: &lt;code&gt;PHP_VERSION=7.4&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;PHP_XDEBUG_3&lt;/strong&gt;&lt;br&gt;
If you set this key with value &lt;code&gt;1&lt;/code&gt;, a different &lt;a href="https://hub.docker.com/r/wardenenv/php-fpm/tags" rel="noopener noreferrer"&gt;tag&lt;/a&gt; of the container &lt;a href="https://hub.docker.com/r/wardenenv/php-fpm" rel="noopener noreferrer"&gt;php-fpm&lt;/a&gt; image will be used. Let's look at the docker-compose yaml generated by warden with the command &lt;code&gt;warden env config&lt;/code&gt;:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fporuttzhwqfj60tmd7rw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fporuttzhwqfj60tmd7rw.png" alt="warden-env-config" width="538" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;COMPOSER_VERSION&lt;/strong&gt;
If it has the value = 1 or you ommit this key composer in version 1 will be the default executable, however you can run composer in version 2 with the command &lt;code&gt;composer2&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;strong&gt;If you omit or set the value 1 for &lt;code&gt;COMPOSER_VERSION&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyb9lachtmlhc0acul2s2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyb9lachtmlhc0acul2s2.png" alt="composer" width="800" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you set the value 2 for &lt;code&gt;COMPOSER_VERSION&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj24n5un8pqql0x6fl4b4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj24n5un8pqql0x6fl4b4.png" alt="composer2" width="772" height="64"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I prefer to inform the value 2 and already run composer in version 2 with the &lt;code&gt;composer&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;There are more interesting things to configure such as Magento 2 tests, live reload, elastic search and more. You can see this information &lt;a href="https://docs.warden.dev/configuration.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h5&gt;
  
  
  6 Run the docker environment
&lt;/h5&gt;

&lt;p&gt;Inside the &lt;code&gt;~/Sites/magento244/docker&lt;/code&gt; directory type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make docker-up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  7 Run composer install
&lt;/h5&gt;

&lt;p&gt;Inside the &lt;code&gt;~/Sites/magento244/docker&lt;/code&gt; run the command &lt;br&gt;
below to access the PHP container&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;warden shell
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the composer install command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  8 Copy and rename the &lt;code&gt;env.warden.php&lt;/code&gt; file
&lt;/h5&gt;

&lt;p&gt;It will copy the file to the &lt;code&gt;code-base/etc&lt;/code&gt; directory with the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cp ~/Sites/magento244/code-base/app/etc/env.warden.php ~/Sites/magento244/code-base/app/etc/env.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;env.warden.php&lt;/code&gt; file has the default configuration entries with the warden hosts for the Mysql database, Redis (session and store cache), RabbitMQ, and Elastic Search.&lt;/p&gt;

&lt;h5&gt;
  
  
  9 Import the Magento 2 database
&lt;/h5&gt;

&lt;p&gt;There is a &lt;a href="https://github.com/cristiano-pacheco/magento244/blob/main/dump.sql.gz" rel="noopener noreferrer"&gt;sample database&lt;/a&gt; inside our &lt;a href="https://github.com/cristiano-pacheco/magento244" rel="noopener noreferrer"&gt;Magento&lt;/a&gt; project, so copy the &lt;code&gt;~/Sites/magento244/code-base/dump.sql.gz&lt;/code&gt; file to &lt;code&gt;~/Sites/magento244/docker/backfill/magento-db.sql.gz&lt;/code&gt; with the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cp ~/Sites/magento244/code-base/dump.sql.gz ~/Sites/magento244/docker/backfill/magento-db.sql.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open a new tab in your terminal and enter the directory &lt;code&gt;~/Sites/magento244/docker&lt;/code&gt; and type the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make db-import
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;db-import&lt;/code&gt; command is one of the custom commands inside the Makefile.&lt;/p&gt;

&lt;p&gt;This command imports the Magento-db.sql.gz database dump to the Magento database in the warden MySQL docker container.&lt;/p&gt;

&lt;p&gt;In addition to importing the database, the &lt;code&gt;~/Sites/magento244/docker/tools/set-default-config.sh&lt;/code&gt; script is then executed to define the project's base URL and create a user for the Magento administrative panel.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;~/Sites/magento244/docker/tools/set-default-config.sh&lt;/code&gt; file, I usually put all the relevant settings to the Magento 2 environment to avoid errors and also to speed up the restoration of the environment as faithful as possible to the actual project.&lt;/p&gt;

&lt;p&gt;Example of settings that are added in this file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project base URLs&lt;/li&gt;
&lt;li&gt;Media and asset URLs&lt;/li&gt;
&lt;li&gt;Payment test credentials&lt;/li&gt;
&lt;li&gt;Changing the Algolia index to the development index, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  An Example of the content of this &lt;code&gt;set-default-config.sh&lt;/code&gt; script:
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/common.sh

:: Import the config from files
warden &lt;span class="nb"&gt;env exec&lt;/span&gt; &lt;span class="nt"&gt;-T&lt;/span&gt; php-fpm bin/magento app:config:import

:: Set BASE URL Config
warden &lt;span class="nb"&gt;env exec&lt;/span&gt; &lt;span class="nt"&gt;-T&lt;/span&gt; php-fpm bin/magento config:set web/unsecure/base_url &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;URL_FRONT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
warden &lt;span class="nb"&gt;env exec&lt;/span&gt; &lt;span class="nt"&gt;-T&lt;/span&gt; php-fpm bin/magento config:set web/secure/base_url &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;URL_FRONT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
warden &lt;span class="nb"&gt;env exec&lt;/span&gt; &lt;span class="nt"&gt;-T&lt;/span&gt; php-fpm bin/magento config:set web/unsecure/base_link_url &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;URL_FRONT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
warden &lt;span class="nb"&gt;env exec&lt;/span&gt; &lt;span class="nt"&gt;-T&lt;/span&gt; php-fpm bin/magento config:set web/secure/base_link_url &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;URL_FRONT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

:: Create an admin user
warden &lt;span class="nb"&gt;env exec&lt;/span&gt; &lt;span class="nt"&gt;-T&lt;/span&gt; php-fpm bin/magento admin:user:create &lt;span class="nt"&gt;--admin-user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;admin &lt;span class="nt"&gt;--admin-password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;123123q &lt;span class="nt"&gt;--admin-email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;admin@example.com &lt;span class="nt"&gt;--admin-firstname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Admin &lt;span class="nt"&gt;--admin-lastname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Admin

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  10 Sign the SSL certificate
&lt;/h5&gt;

&lt;p&gt;Still, inside the &lt;code&gt;~/Sites/magento244/docker&lt;/code&gt; directory, type the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;warden sign-certificate app.magento244.test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  11 Update your &lt;code&gt;/etc/hosts&lt;/code&gt; file with the store domain:
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo echo '127.0.0.1 app.magento244.test' &amp;gt;&amp;gt; /etc/hosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; Do the step above only if &lt;a href="https://thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html" rel="noopener noreferrer"&gt;dnsmasq&lt;/a&gt; is not being used.&lt;/p&gt;

&lt;h5&gt;
  
  
  12 Installing the sample data (Optional)
&lt;/h5&gt;

&lt;p&gt;Access the php container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;warden shell
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the command below to install the sample data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bin/magento sampledata:deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the command below to update the project database&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bin/magento setup:upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you're able to access the store with the URLS \o/: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Store Address:&lt;/strong&gt; &lt;a href="https://app.magento244.test" rel="noopener noreferrer"&gt;https://app.magento244.test&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Backend admin panel:&lt;/strong&gt; &lt;a href="https://app.magento244.test/backend" rel="noopener noreferrer"&gt;https://app.magento244.test/backend&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foi3oyfrtnwjwt8y1l3ow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foi3oyfrtnwjwt8y1l3ow.png" alt="Store with single domain" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Configuring a multiple website/store view Magento 2 project
&lt;/h3&gt;

&lt;p&gt;This Magento 2 store has been configured with the below structure of websites/stores/store views:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fizp3b1ixmdpt3lh0k6ao.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fizp3b1ixmdpt3lh0k6ao.png" alt="store views" width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, the main difference is how to configure a local warden environment to support multiple websites/store views with different domains.&lt;/p&gt;
&lt;h5&gt;
  
  
  1 Create a project folder
&lt;/h5&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir ~/Sites/magento244-multisite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h5&gt;
  
  
  2 Enter the created multisite directory
&lt;/h5&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ~/Sites/magento244-multisite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h5&gt;
  
  
  3 Clone the Magento 2 code-base into the multisite code-base folder
&lt;/h5&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone git@github.com:cristiano-pacheco/magento244-multisite.git code-base
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h5&gt;
  
  
  4 Clone the Warden Magento 2 template
&lt;/h5&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone git@github.com:cristiano-pacheco/warden-env-magento2.git docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h5&gt;
  
  
  5 Change the warden .env file
&lt;/h5&gt;

&lt;p&gt;Enter the docker directory&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ~/Sites/magento244-multisite/docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the .env file with the content below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WARDEN_ENV_NAME=magento244-multisite
WARDEN_ENV_TYPE=magento2
WARDEN_WEB_ROOT=/../code-base

TRAEFIK_DOMAIN=magento244-multisite.test
TRAEFIK_SUBDOMAIN=app

WARDEN_DB=1
WARDEN_ELASTICSEARCH=1
WARDEN_VARNISH=0
WARDEN_RABBITMQ=1
WARDEN_REDIS=1

ELASTICSEARCH_VERSION=7.16
MARIADB_VERSION=10.4
NODE_VERSION=10
PHP_VERSION=8.1
PHP_XDEBUG_3=1
COMPOSER_VERSION=2
RABBITMQ_VERSION=3.9
REDIS_VERSION=6.2
VARNISH_VERSION=7.0

MYSQL_USER=magento
MYSQL_PASSWORD=magento
MYSQL_DATABASE=magento

WARDEN_SYNC_IGNORE=

WARDEN_ALLURE=0
WARDEN_SELENIUM=0
WARDEN_SELENIUM_DEBUG=0
WARDEN_BLACKFIRE=0
WARDEN_SPLIT_SALES=0
WARDEN_SPLIT_CHECKOUT=0
WARDEN_TEST_DB=0
WARDEN_MAGEPACK=0

BLACKFIRE_CLIENT_ID=
BLACKFIRE_CLIENT_TOKEN=
BLACKFIRE_SERVER_ID=
BLACKFIRE_SERVER_TOKEN=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More details about the configuration above was shared in the step 5 of Configuring a single website/store Magento 2 project&lt;/p&gt;

&lt;h5&gt;
  
  
  6 Define custom variables for the store domains
&lt;/h5&gt;

&lt;p&gt;We will define custom variables in &lt;code&gt;~/Sites/magento244-multisite/docker/tools/common.sh&lt;/code&gt; to be able to reuse them later in our &lt;code&gt;set-default-configs.sh&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;URL_WEBSITE_TWO_FRONT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://app.magento244-website-two.test/
&lt;span class="nv"&gt;URL_STORE_VIEW_THREE_US_FRONT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://app.magento244-store-view-three-us.test/
&lt;span class="nv"&gt;URL_STORE_VIEW_THREE_DE_FRONT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://app.magento244-store-view-three-de.test/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  7 Updates the the defaults config script
&lt;/h5&gt;

&lt;p&gt;Add the content of &lt;a href="https://github.com/cristiano-pacheco/warden-env-magento2/blob/multisite/tools/set-default-config.sh" rel="noopener noreferrer"&gt;this file&lt;/a&gt; to the &lt;code&gt;~/Sites/magento244-multisite/docker/tools/set-default-configs.sh&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;The goal of the &lt;code&gt;set-default-configs.sh&lt;/code&gt; script was explained here.&lt;/p&gt;

&lt;h5&gt;
  
  
  8 Run the docker environment
&lt;/h5&gt;

&lt;p&gt;Inside the &lt;code&gt;~/Sites/magento244-multisite/docker&lt;/code&gt; directory type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make docker-up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  9 Composer install
&lt;/h5&gt;

&lt;p&gt;Inside the &lt;code&gt;~/Sites/magento244-multisite/docker&lt;/code&gt; run the command below to access the PHP container&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;warden shell
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the composer install command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  10 Copy and rename the &lt;code&gt;env.warden.php&lt;/code&gt; file into the etc folder
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cp ~/Sites/magento244-multisite/code-base/app/etc/env.warden.php ~/Sites/magento244-multisite/code-base/app/etc/env.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  11 Import the Magento 2 multisite database
&lt;/h5&gt;

&lt;p&gt;There is a &lt;a href="https://github.com/cristiano-pacheco/magento244-multisite/blob/main/dump.sql.gz" rel="noopener noreferrer"&gt;sample database&lt;/a&gt; inside our &lt;a href="https://github.com/cristiano-pacheco/magento244-multisite" rel="noopener noreferrer"&gt;Magento&lt;/a&gt; project, so copy the &lt;code&gt;~/Sites/magento244-multisite/code-base/dump.sql.gz&lt;/code&gt; file to &lt;code&gt;~/Sites/magento244-multisite/docker/backfill/magento-db.sql.gz&lt;/code&gt; with the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cp ~/Sites/magento244-multisite/code-base/dump.sql.gz ~/Sites/magento244-multisite/docker/backfill/magento-db.sql.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open a new tab in your terminal and enter the directory &lt;code&gt;~/Sites/magento244-multisite/docker&lt;/code&gt; and type the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make DB-import
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  12 Sign the SSL certificates for all domains
&lt;/h5&gt;

&lt;p&gt;Still, type the commands below inside the &lt;code&gt;~/Sites/magento244-multisite/docker&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;To sign the certificate to the main and the admin store:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;warden sign-certificate app.magento244-multisite.test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To sign the certificate to the website-two:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;warden sign-certificate app.magento244-website-two.test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To sign the certificate to the Store View Three US:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;warden sign-certificate app.magento244-store-view-three-us.test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To sign the certificate to the Store View Three DE:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;warden sign-certificate app.magento244-store-view-three-de.test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  13 Append your &lt;code&gt;/etc/hosts&lt;/code&gt; file with the store domains
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;127.0.0.1 app.magento244-multisite.test
127.0.0.1 app.magento244-website-two.test
127.0.0.1 app.magento244-store-view-three-us.test
127.0.0.1 app.magento244-store-view-three-de.test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; Do the step above only if &lt;a href="https://thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html" rel="noopener noreferrer"&gt;dnsmasq&lt;/a&gt; is not being used.&lt;/p&gt;

&lt;h5&gt;
  
  
  14 Configuring the application domains:
&lt;/h5&gt;

&lt;p&gt;According to the &lt;a href="https://docs.warden.dev/configuration/multipledomains.html" rel="noopener noreferrer"&gt;Warden documentation&lt;/a&gt; we have to create a &lt;code&gt;warden-env.yml&lt;/code&gt; file inside the &lt;code&gt;.warden&lt;/code&gt; directory. Type the commands below:&lt;/p&gt;

&lt;p&gt;Create the &lt;code&gt;.warden-env.yml&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch ~/Sites/magento244-multisite/docker/.warden/warden-env.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put the content below to the &lt;code&gt;.warden-dev.yml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: "3.5"
services:
  nginx:
    labels:
      - traefik.http.routers.${WARDEN_ENV_NAME}-nginx.rule=
        HostRegexp(`{subdomain:.+}.${TRAEFIK_DOMAIN}`)
        || Host(`${TRAEFIK_DOMAIN}`)
        || HostRegexp(`{subdomain:.+}.magento244-multisite.test`)
        || Host(`magento244-multisite.test`)
        || HostRegexp(`{subdomain:.+}.magento244-website-two.test`)
        || Host(`magento244-website-two.test`)
        || HostRegexp(`{subdomain:.+}.magento244-store-view-three-us.test`)
        || Host(`magento244-store-view-three-us.test`)
        || HostRegexp(`{subdomain:.+}.magento244-store-view-three-de.test`)
        || Host(`magento244-store-view-three-de.test`)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can have a copy of this file &lt;a href="https://github.com/cristiano-pacheco/warden-env-magento2/blob/multisite/.warden/warden-env.yml" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When you're done with the settings, look at the &lt;a href="https://traefik.warden.test/dashboard/#/http/routers" rel="noopener noreferrer"&gt;traefik address&lt;/a&gt; to see where this mapping ended up. It will be like as:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg09o7z2mum6btyi8f6qb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg09o7z2mum6btyi8f6qb.png" alt="traefik" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;stores.php&lt;/code&gt; the Add the Magento 2 run params to the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd touch ~/Sites/magento244-multisite/code-base/app/etc/stores.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the content of &lt;a href="https://github.com/cristiano-pacheco/magento244-multisite/blob/main/app/etc/stores.php" rel="noopener noreferrer"&gt;this file&lt;/a&gt; in the file above.&lt;/p&gt;

&lt;p&gt;Add the &lt;code&gt;app/etc/stores.php&lt;/code&gt; to the &lt;code&gt;composer.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;It should look like the image below after the change:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvbk07awbxm6enr4ojm8c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvbk07awbxm6enr4ojm8c.png" alt="multi-store-config" width="602" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It will force composer to load this file on every request. the file then sets up the store for Magento to load based on the domain, passing the &lt;code&gt;MAGE_RUN_CODE&lt;/code&gt; and &lt;code&gt;MAGE_RUN_TYPE&lt;/code&gt; variables to Nginx.&lt;/p&gt;

&lt;p&gt;Now you're able to access the stores with the URLS \o/: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Default Store Address&lt;/strong&gt;:&lt;br&gt;
&lt;a href="https://app.magento244-multisite.test" rel="noopener noreferrer"&gt;https://app.magento244-multisite.test&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend admin panel&lt;/strong&gt;:&lt;br&gt;
&lt;a href="https://app.magento244-multisite.test/backend" rel="noopener noreferrer"&gt;https://app.magento244-multisite.test/backend&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Website Two Address&lt;/strong&gt;:&lt;br&gt;
&lt;a href="https://app.magento244-multisite.test" rel="noopener noreferrer"&gt;https://app.magento244-multisite.test&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Store View Three US Address&lt;/strong&gt;:&lt;br&gt;
&lt;a href="https://app.magento244-store-view-three-us.test" rel="noopener noreferrer"&gt;https://app.magento244-store-view-three-us.test&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Store View Three DE Address&lt;/strong&gt;:&lt;br&gt;
&lt;a href="https://app.magento244-store-view-three-de.test" rel="noopener noreferrer"&gt;https://app.magento244-store-view-three-de.test&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;br&gt;
Inside the directory &lt;code&gt;~/Sites/magento244-multisite/code-base&lt;/code&gt;, type the command make, and you will see an output like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fakp6pz2504cgp1jn8cy3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fakp6pz2504cgp1jn8cy3.png" alt="make-command" width="636" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it. If you have any questions, please let us know in the comments 👋&lt;/p&gt;

</description>
      <category>php</category>
      <category>magento2</category>
      <category>docker</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Composable Commerce - or: MACH Architectures in E-Commerce</title>
      <dc:creator>Rico Neitzel</dc:creator>
      <pubDate>Fri, 21 Jul 2023 10:38:12 +0000</pubDate>
      <link>https://forem.com/run_as_root/composable-commerce-or-mach-architectures-in-e-commerce-48j7</link>
      <guid>https://forem.com/run_as_root/composable-commerce-or-mach-architectures-in-e-commerce-48j7</guid>
      <description>&lt;p&gt;Gone are the days when online shops were built with one massive program. Now, one can select a distinct provider for every software segment, thus creating a unique e-commerce world. Or can they? A new trend is on everyone's lips: MACH! In German that means "Do it!" … But what exactly are we supposed to "do"? And what does it have to do with Composable Commerce?&lt;/p&gt;

&lt;p&gt;In our post, we delve into what MACH entails, its potential benefits in e-commerce, but also the drawbacks it currently carries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Composable Commerce
&lt;/h2&gt;

&lt;p&gt;Until now, a typical "online shop" project involved collaborating with a service provider to develop an extensive application (the online shop) covering all crucial aspects: product catalog, search, ordering and payment process, and order and customer data management.&lt;/p&gt;

&lt;p&gt;A disadvantage of these monolithic solutions is the dependency on a single manufacturer, whose updates affect the entire application.&lt;/p&gt;

&lt;p&gt;Composable Commerce aims to modularize this monolithic structure and make the individual parts interchangeable.&lt;/p&gt;

&lt;p&gt;Technically, it's about separating the components of an eCommerce system (such as shopping cart, payment processes, product data management, and search) from each other and connecting them via APIs so that each section can be expanded, replaced, or optimized independently, without affecting the rest of the system.&lt;/p&gt;

&lt;p&gt;The apparent advantage for the retailer is to be able to search for a specialized provider for each aspect and ultimately compile their own solution without depending on a single provider.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Foundation: MACH Architectures
&lt;/h3&gt;

&lt;p&gt;MACH architecture (Microservices, API-first, Cloud-native, Headless) is the foundation for Composable Commerce.&lt;/p&gt;

&lt;p&gt;The concept is quite simple: each part of the system ("Microservice") operates independently and communicates exclusively via defined interfaces ("APIs") with the other components. These components are designed for deployment on distributed systems and leverage their advantages like scalability, reliability, and cost-efficiency ("Cloud-native").&lt;/p&gt;

&lt;p&gt;The separation of frontend (the user interface that the customer sees) and backend (like data management and business logic) allows independent further development of each component ("Headless").&lt;/p&gt;

&lt;p&gt;The MACH architecture primarily aims at investment, a secured future, and maximum flexibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  MACH Alliance
&lt;/h3&gt;

&lt;p&gt;However, one challenge of this approach is the collaboration of various manufacturers and service providers. To coordinate this collaboration internationally, an &lt;a href="https://machalliance.org/members" rel="noopener noreferrer"&gt;alliance&lt;/a&gt; has been formed, providing educational work and certifying service providers. This is intended to make it easier for companies to commission providers who understand and can implement the MACH standards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Composable Commerce Projects
&lt;/h2&gt;

&lt;p&gt;Many service providers suggest that with Composable Commerce, all components are ready and just need to be "put together". However, implementing such a platform requires both internal and external expertise in terms of both the industry and the technology. Therefore, the application itself must be programmed — and here lies the actual work: the program code, which connects all these microservices via their APIs, must be precisely planned and developed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhhcs8boqpctw6px6ev6p.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhhcs8boqpctw6px6ev6p.jpg" alt="A diagram showing individual components (cart, search, catalog, connected to the Magento core"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is where the "composable" aspect comes fully into play, as we can decide which components from which provider we want to connect. Search? &lt;a href="https://www.algolia.com/" rel="noopener noreferrer"&gt;Algolia&lt;/a&gt;! — Product catalog? &lt;a href="https://mage-os.org/" rel="noopener noreferrer"&gt;Magento&lt;/a&gt;! — Shopping cart and payment process? &lt;a href="https://commercetools.com/" rel="noopener noreferrer"&gt;Commerce Tools&lt;/a&gt;! ... and the list of possibilities and decisions to be made does not end here.&lt;/p&gt;

&lt;p&gt;Even after this decision, the possibility remains open to exchange the components in a few years if a more suitable solution is found.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependency Hell - Fool's Gold!
&lt;/h2&gt;

&lt;p&gt;Even if all of this sounds like shining gold, closer examination reveals: it's just fool's gold. Because the interchangeability of components often praised in advertising is not currently available.&lt;/p&gt;

&lt;p&gt;Many providers already include their own components and advertise a closed system. Yes, it is composable; but no: I cannot easily combine any manufacturers at will.&lt;/p&gt;

&lt;p&gt;A crucial technical prerequisite for this interoperability is, as often: standardization. And precisely this is currently not available. There is no DIN "product catalog" defining data formats for the exchange between components. It's not even standardized which API commands (so-called endpoints) must be communicated.&lt;/p&gt;

&lt;p&gt;Thus, at the moment, each manufacturer has their own API with their own formats.&lt;/p&gt;

&lt;p&gt;And at this point, the great advantage of composable commerce is currently lost. One is trapped in provider dependencies and cannot simply replace components without ordering development work for the integration again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Costs of Composable Commerce
&lt;/h2&gt;

&lt;p&gt;It's often said that costs with modern architectures should be lower in the long term than with classic monoliths. This effect comes into play nowadays – if at all – only in rare exceptional cases.&lt;/p&gt;

&lt;p&gt;In many cases, MACH architecture means a significantly higher demand on companies and people. Additionally, integrating new functions often means working on several of these components. Usually, different teams then have to be coordinated.&lt;/p&gt;

&lt;p&gt;So, when the entire system has a bug, the search begins in distributed systems. Is it the application itself? Or one of the components? If yes, which one? Is it the sole cause of the problem, or do several issues in different components need to come together to trigger the error? The error search is thus much more difficult in these distributed systems.&lt;/p&gt;

&lt;p&gt;All these challenges also raise the required budget of the project. A cost-saving only occurs in doubt when everything works smoothly — a rare state.&lt;/p&gt;

&lt;p&gt;Thus, the difficult question remains of how to realize your project with a view to the future and what budget you want to put into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quo Vadis?
&lt;/h2&gt;

&lt;p&gt;As long as the major players of the MACH Alliance do not all sit at a table and decide that the welfare of the retailers should be placed above their own financial interests, one has to live with the sour taste of microservice-based monoliths. Only with established and certified interfaces and data formats can the full potential of Composable Commerce be exploited in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Opportunity - but with Consideration
&lt;/h2&gt;

&lt;p&gt;Even today, existing monoliths are equipped with additional components, bringing new capabilities to existing systems. Algolia is an excellent example of how an external provider can replace and drastically improve the shop's search. Such solutions work reliably and cost-effectively.&lt;/p&gt;

&lt;p&gt;However, the integrations are always tailored to the respective system, sometimes even to the individual use case. But they beautifully show that the idea of "Composable Commerce" is heading in the right direction.&lt;/p&gt;

&lt;p&gt;So it remains exciting, and ultimately time will tell whether the MACH Alliance will establish common standards that find cross-industry support. At the moment, companies need to take the time to understand the possibilities, impacts, and costs to make an informed decision. Every hype should be critically questioned, and always remember that technology should always be a tool for the company; not the other way around.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Rico Neitzel is Co-Founder of run_as_root GmbH in Germany. We specialize in software quality automation and high quality software development. We primarily focus on ecommerce.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ecommerce</category>
      <category>architecture</category>
      <category>development</category>
      <category>api</category>
    </item>
    <item>
      <title>Navigating Magento 2 Architecture: Discovering Plugin Architecture for Newcomers</title>
      <dc:creator>David Lambauer</dc:creator>
      <pubDate>Mon, 17 Jul 2023 18:15:13 +0000</pubDate>
      <link>https://forem.com/run_as_root/navigating-magento-2-architecture-discovering-plugin-architecture-for-newcomers-312i</link>
      <guid>https://forem.com/run_as_root/navigating-magento-2-architecture-discovering-plugin-architecture-for-newcomers-312i</guid>
      <description>&lt;p&gt;Hello again, and welcome back to our series on Magento 2 Architecture for newcomers! We're glad to see you return for the second part of our journey, a clear indicator of your dedication to understanding and mastering this powerful platform. In our first post, we delved into the fascinating world of Magento 2's Observer Architecture. We explored its unique functionality and got our hands dirty with practical examples to bolster our knowledge.&lt;/p&gt;

&lt;p&gt;Today, we will be unraveling the intricacies of Magento 2's Plugin Architecture. Our aim is to ensure you not only understand the concept theoretically but also gain the practical skills necessary to apply them efficiently. So buckle up, as we gear up for another exciting ride through the world of Magento!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Role of Plugins in Magento 2
&lt;/h2&gt;

&lt;p&gt;In Magento 2, plugins, also referred to as Interceptors, play a critical role in extending the functionality of existing public methods within a class. They provide a mechanism that allows us to alter or extend the behavior of public methods without modifying the actual class.&lt;/p&gt;

&lt;p&gt;The charm of plugins lies in their ability to interact with a method at three different points: before, after, and around. This allows developers to modify the input or output data or even change the flow of processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Registering a Plugin
&lt;/h2&gt;

&lt;p&gt;To leverage the power of plugins, we need to register them first. This is done in the &lt;code&gt;di.xml&lt;/code&gt; file of your module. Here's a basic example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;config&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt; &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"urn:magento:framework:ObjectManager/etc/config.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;type&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Magento\Framework\App\Action\Action"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;plugin&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"vendor_module_slack_notifier"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"Vendor\Module\Plugin\SlackNotifier"&lt;/span&gt; &lt;span class="na"&gt;sortOrder=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/type&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we've registered a plugin named &lt;code&gt;vendor_module_slack_notifier&lt;/code&gt; for the class &lt;code&gt;Magento\Framework\App\Action\Action&lt;/code&gt;. The plugin's class is &lt;code&gt;Vendor\Module\Plugin\SlackNotifier&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing a Plugin
&lt;/h2&gt;

&lt;p&gt;Now, let's get down to the nitty-gritty of writing our plugin. Remember our goal here is to send a Slack message at a clever place. A good idea could be to notify a Slack channel whenever a new order is placed on the website. In this case, let's consider we are writing a plugin for the &lt;code&gt;placeOrder&lt;/code&gt; method in the &lt;code&gt;Magento\Sales\Model\Service\OrderService&lt;/code&gt; class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Vendor\Module\Plugin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SlackNotifier&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$slack&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;Vendor\Module\Model\Slack&lt;/span&gt; &lt;span class="nv"&gt;$slack&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;slack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$slack&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;afterPlaceOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;Magento\Sales\Model\Service\OrderService&lt;/span&gt; &lt;span class="nv"&gt;$subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"New order placed with ID: "&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;slack&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this plugin, we are using the &lt;code&gt;after&lt;/code&gt; plugin to send a Slack message after an order has been placed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unit Testing a Plugin
&lt;/h2&gt;

&lt;p&gt;After we've written our plugin, it's important to test it to ensure it behaves as expected. Here's how you can use PHPUnit to test your plugin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Vendor\Module\Test\Unit\Plugin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SlackNotifierTest&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;PHPUnit\Framework\TestCase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$slackNotifier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;slackNotifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMockBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;Vendor\Module\Plugin\SlackNotifier&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;disableOriginalConstructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;testAfterPlaceOrder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$orderServiceMock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createMock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;Magento\Sales\Model\Service\OrderService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"100001"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;slackNotifier&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;expects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;once&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'afterPlaceOrder'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$orderServiceMock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;slackNotifier&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;afterPlaceOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$orderServiceMock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Observers vs Plugins
&lt;/h2&gt;

&lt;p&gt;While observers and plugins might seem similar, they serve different purposes and their usage depends on your requirements. Plugins are great for modifying the behavior of existing public methods. However, if you're interested in reacting to specific events in the application, observers would be a more suitable choice.&lt;/p&gt;

&lt;p&gt;The observer pattern can be seen as a more global approach, as it enables a certain type of code (observers) to react to system-wide events without coupling the observers to the code that emits those events. Plugins, on the other hand, are more targeted, allowing us to interact with a method at specific points in its execution.&lt;/p&gt;

&lt;p&gt;With the knowledge you've gained from this post and the previous one, you are now well-equipped to start implementing these concepts into your projects. Next in this series, we will dive deeper into the world of Magento 2 architecture. Stay tuned and happy coding!&lt;/p&gt;

</description>
      <category>php</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>magento2</category>
    </item>
    <item>
      <title>Extension Attributes in Adobe Commerce: Achieving Clean and Decoupled Code</title>
      <dc:creator>Vladyslav Podorozhnyi 🇺🇦 🌻</dc:creator>
      <pubDate>Thu, 13 Jul 2023 20:21:44 +0000</pubDate>
      <link>https://forem.com/run_as_root/extension-attributes-in-adobe-commerce-achieving-clean-and-decoupled-code-1i88</link>
      <guid>https://forem.com/run_as_root/extension-attributes-in-adobe-commerce-achieving-clean-and-decoupled-code-1i88</guid>
      <description>&lt;h3&gt;
  
  
  Table Of Contents
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Chapter 1: Understanding the Adobe Commerce Service Layer&lt;/li&gt;
&lt;li&gt;Chapter 2: Exploring the Idea of Extension Attributes&lt;/li&gt;
&lt;li&gt;Chapter 3: Extension Attributes and Service Contracts&lt;/li&gt;
&lt;li&gt;Chapter 4: Saving Data for Extension Attributes&lt;/li&gt;
&lt;li&gt;Chapter 5: Key Takeaways&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to our blog post, where we delve into the practical realm of Extension Attributes in Adobe Commerce (formerly Magento). If you've been working with Magento 2 for a while, you've probably encountered Extension Attributes and harnessed their capabilities. However, it's surprising how often developers overlook the valuable benefits that Extension Attributes offer, especially when compared to outdated methods of entity extension.&lt;/p&gt;

&lt;p&gt;In this article, we aim to shed light on the advantages of Extension Attributes and how they can enhance your development projects. We'll explore their integration with the Service Layer, providing you with insights on how to write clean, modular code that avoids unnecessary dependencies on other layers or extensions.&lt;/p&gt;

&lt;p&gt;Whether you're an experienced Adobe Commerce developer seeking to broaden your understanding or a newcomer eager to learn more, this article guides you to a bit deeper understanding of Extension Attributes' place &amp;amp; role in Adobe Commerce.&lt;/p&gt;

&lt;p&gt;Join us in exploring Extension Attributes. Let's dive in and discover the practical benefits they bring to your development workflow. Let's get started!&lt;/p&gt;




&lt;h2&gt;
  
  
  Chapter 1: Understanding the Adobe Commerce Service Layer
&lt;/h2&gt;

&lt;p&gt;As you may know already, Adobe Commerce has four Architecture layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Presentation layer - point of user interaction with Adobe Commerce.&lt;/li&gt;
&lt;li&gt;Service layer - it is a "glue" between layers and a set of APIs used by modules and external systems to communicate with each other. Service contracts are in use here.&lt;/li&gt;
&lt;li&gt;Domain layer - contains the business logic of the module.&lt;/li&gt;
&lt;li&gt;Persistence layer - resource level, knows about DB and other storages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See more details here: &lt;a href="https://developer.adobe.com/commerce/php/architecture/layers/" rel="noopener noreferrer"&gt;Architectural layers overview&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The service layer is vital for understanding extension attributes, especially the principle of "service contracts" that is used as a system pillar in Adobe Commerce. If you do not know about "service contracts," - read this article &lt;a href="https://developer.adobe.com/commerce/php/development/components/service-contracts/" rel="noopener noreferrer"&gt;Service contracts&lt;/a&gt; and keep in mind that this is an agreement between parts of the system on the functionality list available for each party and how these functionalities could be triggered. REST API and API folder in modules are two bright examples of service contract implementation.&lt;/p&gt;

&lt;p&gt;The service layer allows to build long term, strictly defined relations between services, both internal and external.  &lt;/p&gt;

&lt;p&gt;While developing your extension and needing to change/extend one or another functionality of the system - you would need to rely on the Service layer, not any other. Only the Service layer would be able to provide you with a warranty to be changed in the nearest perspective - so there are no troubles with upgrades all the time, as the API definition is &lt;br&gt;
not changed often. I'm not even mentioning the benefits of explicit entry point definition, its parameters, and predictable output.&lt;/p&gt;




&lt;h2&gt;
  
  
  Chapter 2: Exploring the Idea of Extension Attributes
&lt;/h2&gt;

&lt;p&gt;Imagine you're working with a fundamental entity like an order in Adobe Commerce. This entity has its dedicated table called &lt;code&gt;sales_order&lt;/code&gt; and a considerable list of predefined properties. But what if you find yourself in need of adding another property to the order entity?&lt;/p&gt;

&lt;p&gt;Sure, you could take the easy route and directly modify the &lt;code&gt;sales_order&lt;/code&gt; table, implementing custom observers and models to handle the new property. However, doing so would take you into the Domain layer of another extension, which isn't good. The domain layer of other modules is the last resort for an extension, as they are changed quite often by the vendor. It means meddling with the system's inner workings - system you are out of control, risking compatibility issues during future upgrades. &lt;a href="https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle" rel="noopener noreferrer"&gt;Open–closed principle&lt;/a&gt; - the system should be closed for direct changes but open for extending. In this case, modifying the entity directly would be considered changing the system controllable by the vendor Magento - not you.&lt;/p&gt;

&lt;p&gt;So, how can we add a property to the entity using the "right" tools?&lt;/p&gt;

&lt;p&gt;The answer lies in the power of Extension Attributes. 🙌&lt;/p&gt;

&lt;p&gt;Extension Attributes provide a sanctioned approach for extending entities without tampering with the core system's code. Instead of directly modifying the entity table, we leverage Extension Attributes to add new properties in a clean and modular way. By utilizing this method, we ensure our modifications align with the principles of the system's architecture, reducing the risk of compatibility issues and enabling smoother upgrades in the long run.&lt;/p&gt;

&lt;p&gt;In the following chapter, we'll explore how Extension Attributes work hand in hand with the Service Layer, enabling you to extend entities effectively and maintain a flexible and scalable codebase.&lt;/p&gt;




&lt;h2&gt;
  
  
  Chapter 3: Extension Attributes and Service Contracts
&lt;/h2&gt;

&lt;p&gt;Extension Attributes provide a "legitimate" way to modify Service contracts while maintaining backward compatibility for both providers and consumers. Although a simplified definition, it captures the essence of Extension Attributes in Adobe Commerce.&lt;/p&gt;

&lt;h4&gt;
  
  
  Extension Object: Leveraging Composition over Inheritance
&lt;/h4&gt;

&lt;p&gt;Adobe Commerce adopts the principle of "composition over inheritance" to achieve this flexibility within its development ecosystem. Each entity supporting Extension Attributes has a setter and getter for the Extension object. In the case of orders, it is an instance of a class that implements &lt;code&gt;\Magento\Sales\Api\Data\OrderExtensionInterface&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Configuring Extension Attributes with XML
&lt;/h4&gt;

&lt;p&gt;The behavior of the Extension object is configured through simple and straightforward XML configurations. It is in this configuration where we specify which extension attributes need to be added, define their types, and associate them with the respective entities.&lt;br&gt;&lt;br&gt;
For example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;

// @see vendor/magento/module-sales/etc/extension_attributes.xml
&lt;span class="nt"&gt;&amp;lt;config&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt; &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"urn:magento:framework:Api/etc/extension_attributes.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;extension_attributes&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"Magento\Sales\Api\Data\OrderInterface"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;attribute&lt;/span&gt; &lt;span class="na"&gt;code=&lt;/span&gt;&lt;span class="s"&gt;"shipping_assignments"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"Magento\Sales\Api\Data\ShippingAssignmentInterface[]"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/extension_attributes&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  Support for Extension Attributes: The Role of ExtensibleDataInterface
&lt;/h4&gt;

&lt;p&gt;How does Adobe Commerce knows that order entity supports extension attributes?&lt;br&gt;&lt;br&gt;
Well, &lt;code&gt;\Magento\Sales\Api\Data\OrderInterface&lt;/code&gt; extends &lt;code&gt;\Magento\Framework\Api\ExtensibleDataInterface&lt;/code&gt;, which informs Adobe Commerce that the order entity will have Extension Attributes. The XML configuration specifies that the property &lt;code&gt;shipping_assignments&lt;/code&gt; is one of those Extension Attributes.&lt;/p&gt;
&lt;h4&gt;
  
  
  Generated Service Contracts
&lt;/h4&gt;

&lt;p&gt;Now that Adobe Commerce is aware of the Extension Attributes, what comes next?&lt;/p&gt;

&lt;p&gt;It generates &lt;code&gt;\Magento\Sales\Api\Data\OrderExtensionInterface&lt;/code&gt; in the &lt;code&gt;generated/code&lt;/code&gt; folder based on the collected XML configurations from &lt;code&gt;extension_attributes.xml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The generated interface looks like this:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderExtensionInterface&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;\Magento\Framework\Api\ExtensionAttributesInterface&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * @return \Magento\Sales\Api\Data\ShippingAssignmentInterface[]|null
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getShippingAssignments&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * @param \Magento\Sales\Api\Data\ShippingAssignmentInterface[] $shippingAssignments
     * @return $this
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setShippingAssignments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$shippingAssignments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mf"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Additionally, the implementation of the contract/interface, &lt;code&gt;\Magento\Sales\Api\Data\OrderExtension&lt;/code&gt;, is also generated.&lt;/p&gt;

&lt;h4&gt;
  
  
  Summary
&lt;/h4&gt;

&lt;p&gt;In essence, Adobe Commerce has automatically generated a new Service Contract for us based on our configurations. We can now use this contract to effortlessly set and retrieve values of Extension Attributes for the order entity, without the need to navigate through other layers apart from the Service Layer of the &lt;code&gt;Magento_Sales&lt;/code&gt; model.&lt;/p&gt;

&lt;p&gt;This outstanding move allows your newly added Extension Attribute to become an integral part of the Service Contracts and Service Layer. It remains independent of the implementation details of any modules, extensions, or the system itself that you do not have control over.&lt;/p&gt;

&lt;p&gt;Congratulations on leveraging this remarkable feature of Adobe Commerce!&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fknr8pbizzl731svw0ocd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fknr8pbizzl731svw0ocd.png" alt="Outstanding move - Adobe Commerce"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next chapter, we'll explore how to not get unwanted dependencies while further Extension Attributes implementation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Chapter 4: Saving Data for Extension Attributes
&lt;/h2&gt;

&lt;p&gt;It's not always necessary to save Extension Attribute data persistently. Sometimes, it suffices to calculate their values on the fly when needed. However, let's assume we do need to save an attribute. &lt;/p&gt;

&lt;h4&gt;
  
  
  Main Steps for Saving Extension Attribute Data
&lt;/h4&gt;

&lt;p&gt;The Adobe Docs article, &lt;a href="https://developer.adobe.com/commerce/php/development/components/add-attributes/" rel="noopener noreferrer"&gt;Add extension attributes to entities&lt;/a&gt;, provides a comprehensive guide on how to achieve this. Let's go through a quick recap. To add data to an Extension Attribute, you typically follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a plugin that intercepts the save operation of the entity to retrieve the Extension Attribute data and save it at the resource level.&lt;/li&gt;
&lt;li&gt;Create a plugin that intercepts the load operation of the entity to retrieve the Extension Attribute data and populate the entity using the Extension object.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While there can be variations, this is the main scenario, requiring at least two plugins for saving and loading operations.&lt;/p&gt;

&lt;h4&gt;
  
  
  Where to Save Extension Attribute Data?
&lt;/h4&gt;

&lt;p&gt;Now, that's a brilliant question that leaves many stumped, but not you! 😄&lt;/p&gt;

&lt;p&gt;One option is to save the Extension Attribute data in the same table as the entity—for example, the &lt;code&gt;sales_order&lt;/code&gt; table for orders. However, this approach introduces a dependency on the Domain Layer of the &lt;code&gt;Magento_Sales&lt;/code&gt; module.&lt;/p&gt;

&lt;p&gt;And yes, you're right—it's not "legal" in our paradigm. We aim to extend the software, not modify it.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Best Approach: Have Your Own Entity
&lt;/h4&gt;

&lt;p&gt;In my opinion, the best approach is to have your own entity to save and retrieve the Extension Attribute data. It doesn't matter what persistence layer you choose—whether it's a database, session, file, API, carrier pigeons, or even Morse code written on paper.&lt;/p&gt;

&lt;p&gt;It may seem like overkill, but it's the most sensible way to avoid unwanted dependencies on unmodifiable parts of the system. Your Extension Attribute implementation should not impact the core system or violate the Open-Closed principle. By cutting down all connections and dependencies on the extendable module, except for the Extension Attributes definition, you ensure the integrity of the Service Contracts and keep the system functioning as intended.&lt;/p&gt;

&lt;p&gt;This approach allows you to maintain a clean and decoupled codebase, free from unnecessary entanglements with uncontrollable system components.&lt;/p&gt;




&lt;h2&gt;
  
  
  Chapter 5: Key Takeaways
&lt;/h2&gt;

&lt;p&gt;As we conclude this article on Extension Attributes in Adobe Commerce, let's summarize the key takeaways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Service Layer and Service Contracts&lt;/strong&gt;: The Service Layer and Service Contracts serve as excellent foundations for extending functionality. They provide stability, predictability, and explicit definitions, making them ideal for seamless extension development.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Persistence, Domain, and Presentation Layers&lt;/strong&gt;: On the other hand, the Persistence, Domain, and Presentation Layers are not suitable for direct extension. These layers can undergo changes with each release, making them less reliable for long-term extension development.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Extension Attributes and Service Contracts&lt;/strong&gt;: Extension Attributes are an integral part of the Service Layer and Service Contracts. Leveraging Extension Attributes enables effective extension development and facilitates communication across services within Adobe Commerce.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Avoid Saving Extension Attributes Inside the Entity&lt;/strong&gt;: It is crucial to refrain from saving Extension Attributes within the entity you are trying to extend. Doing so would introduce a dependency on the Persistence Layer of the extendable module, rendering the benefits of the Service Layer useless. Instead, opt for storing Extension Attribute data in a separate entity to maintain a clean and decoupled architecture.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By adhering to these takeaways, you can build robust and scalable extensions that align with the principles of Adobe Commerce's architecture. Remember, the Service Layer and Service Contracts offer a stable foundation, while Extension Attributes provide a flexible and effective approach to extending functionality.&lt;/p&gt;

&lt;p&gt;Thank you for joining us in exploring Extension Attributes in Adobe Commerce. We hope you found this article informative and insightful.   &lt;/p&gt;

&lt;p&gt;Happy developing!&lt;/p&gt;

</description>
      <category>magento2</category>
      <category>adobecommerce</category>
      <category>webdev</category>
      <category>development</category>
    </item>
    <item>
      <title>Kickstarting Magento 2 Architecture Journey: Mastering Observer Architecture for Newcomers</title>
      <dc:creator>David Lambauer</dc:creator>
      <pubDate>Mon, 19 Jun 2023 21:36:09 +0000</pubDate>
      <link>https://forem.com/run_as_root/kickstarting-magento-2-architecture-journey-mastering-observer-architecture-for-newcomers-47h4</link>
      <guid>https://forem.com/run_as_root/kickstarting-magento-2-architecture-journey-mastering-observer-architecture-for-newcomers-47h4</guid>
      <description>&lt;p&gt;Hello and welcome to the first post in our blog series dedicated to the architectural nuances of Magento 2. Aimed specifically at those taking their initial steps into the Magento ecosystem, this series will illuminate key architectural elements and concepts of the platform. Our inaugural topic is a vital part of Magento's Event-Driven Architecture - the Observer Pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Magento 2 Observer Architecture: An Overview
&lt;/h2&gt;

&lt;p&gt;The Observer Architecture, an event-driven design pattern in Magento, paves the way for effective communication between various sections of code. It adopts the principle of 'Loose Coupling,' promoting interactions without rigid dependencies. Essentially, it lets you extend and customize Magento's functionality without modifying the core system files significantly.&lt;/p&gt;

&lt;p&gt;Various events, such as adding a product to the cart or placing an order, initiate specific sequences of actions within Magento 2. The Dispatcher identifies these events and calls the corresponding observers - class methods assigned to carry out actions related to the event.&lt;/p&gt;

&lt;h2&gt;
  
  
  Identifying Event Names
&lt;/h2&gt;

&lt;p&gt;Magento 2 has predefined event names for numerous actions. To identify the correct event name, you need to understand the functionality you wish to extend or modify. Often, you can find these event names within Magento's core code files, specifically inside the dispatch function calls. A pro tip: For a more comprehensive list, refer to the &lt;a href="https://github.com/alaa-almaliki/magento2-events-log"&gt;Magento 2 Event Cheat Sheet&lt;/a&gt; available online.&lt;/p&gt;

&lt;h2&gt;
  
  
  Registering an Observer with &lt;code&gt;events.xml&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To declare an observer in Magento 2, you will utilize the events.xml file. You'll need to specify the event name you're observing and the class that will handle it. Here's a quick walkthrough on registering an observer:&lt;/p&gt;

&lt;p&gt;Create an &lt;code&gt;events.xml&lt;/code&gt; file in the etc directory of your module.&lt;/p&gt;

&lt;p&gt;Register your observer in the &lt;code&gt;events.xml&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;config&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt; &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"urn:magento:framework:Event/etc/events.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;event&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"checkout_cart_product_add_after"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;observer&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"slack_message_observer"&lt;/span&gt; &lt;span class="na"&gt;instance=&lt;/span&gt;&lt;span class="s"&gt;"Vendor\Module\Observer\SlackMessageObserver"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/event&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Crafting an Observer: Sending a Slack Message on Cart Addition
&lt;/h2&gt;

&lt;p&gt;Let's put this into practice by creating an observer that sends a Slack message whenever a product is added to the cart.&lt;/p&gt;

&lt;p&gt;First, define your Observer class, &lt;code&gt;SlackMessageObserver.php&lt;/code&gt;, within your module's Observer directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Vendor\Module\Observer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Magento\Framework\Event\ObserverInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Vendor\Module\Model\SlackMessageSender&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SlackMessageObserver&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ObserverInterface&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;SlackMessageSender&lt;/span&gt; &lt;span class="nv"&gt;$slackMessageSender&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;SlackMessageSender&lt;/span&gt; &lt;span class="nv"&gt;$slackMessageSender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;slackMessageSender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$slackMessageSender&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;Magento\Framework\Event\Observer&lt;/span&gt; &lt;span class="nv"&gt;$observer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$observer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getDataUsingMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'product'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'A product has been added to the cart: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;slackMessageSender&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this code, we've implemented the execute method, which gets invoked when the &lt;code&gt;checkout_cart_product_add_after&lt;/code&gt; event is dispatched. The execute method gathers the product data and sends a message to a Slack channel using the &lt;code&gt;SlackMessageSender&lt;/code&gt; service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Your Observer
&lt;/h2&gt;

&lt;p&gt;Testing in Magento 2 typically involves two levels - Unit Testing and Integration Testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unit Testing&lt;/strong&gt; aims at the smallest testable parts of an application, in isolation. In our observer's case, we can create a mock of &lt;code&gt;SlackMessageSender&lt;/code&gt; and confirm if the send method is called with the expected argument.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integration Testing&lt;/strong&gt;, conversely, examines the interaction between different application components. It could involve testing the entire process, from adding a product to the cart to sending the Slack message. Here's how an integration test for our observer might look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Vendor\Module\Observer\Test\Integration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Magento\TestFramework\Helper\Bootstrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Magento\Framework\Event\ManagerInterface&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;EventManager&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Magento\Catalog\Api\ProductRepositoryInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Magento\Checkout\Model\Cart&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Vendor\Module\Observer\SlackMessageObserver&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Vendor\Module\Model\SlackMessageSender&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SlackMessageObserverTest&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;PHPUnit\Framework\TestCase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$objectManager&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$cart&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$slackMessageSenderMock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;objectManager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Bootstrap&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getObjectManager&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;objectManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Cart&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;slackMessageSenderMock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createMock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SlackMessageSender&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;objectManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addSharedInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;slackMessageSenderMock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SlackMessageSender&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;objectManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addSharedInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;objectManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SlackMessageObserver&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;SlackMessageObserver&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;testObserverIsTriggeredOnAddToCart&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$productRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;objectManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProductRepositoryInterface&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$productRepository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'simple'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 'simple' should be a SKU of a product in your test database&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;slackMessageSenderMock&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;expects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;once&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'send'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;stringContains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;tearDown&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;objectManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;removeSharedInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SlackMessageSender&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;objectManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;removeSharedInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SlackMessageObserver&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition, a basic unit test might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Vendor\Module\Test\Unit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;PHPUnit\Framework\TestCase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Vendor\Module\Observer\SlackMessageObserver&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Vendor\Module\Model\SlackMessageSender&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Magento\Catalog\Model\Product&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Magento\Framework\Event\Observer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Magento\Framework\Event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SlackMessageObserverTest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;TestCase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;testExecute&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$productName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Test Product'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Mock product&lt;/span&gt;
        &lt;span class="nv"&gt;$productMock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createMock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$productMock&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'getName'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;willReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$productName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Mock event&lt;/span&gt;
        &lt;span class="nv"&gt;$eventMock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createMock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$eventMock&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'getData'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'product'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;willReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$productMock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Mock observer&lt;/span&gt;
        &lt;span class="nv"&gt;$observerMock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createMock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Observer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$observerMock&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'getEvent'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;willReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$eventMock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Mock SlackMessageSender&lt;/span&gt;
        &lt;span class="nv"&gt;$slackMessageSenderMock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createMock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SlackMessageSender&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$slackMessageSenderMock&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;expects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;once&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'send'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;stringContains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$productName&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// Instantiate the SlackMessageObserver with the mocked SlackMessageSender&lt;/span&gt;
        &lt;span class="nv"&gt;$observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SlackMessageObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$slackMessageSenderMock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$observer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$observerMock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Testing is as critical as writing code itself. Tests ensure your modules' stability and reliability, and mitigate future issues arising from updates or changes in other parts of the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Declare the Observer in the Appropriate Scope
&lt;/h3&gt;

&lt;p&gt;The first step in using Magento 2 observers effectively is to ensure you declare them in the right scope: &lt;code&gt;global&lt;/code&gt;, &lt;code&gt;frontend&lt;/code&gt;, or &lt;code&gt;adminhtml&lt;/code&gt;. The scope of your observer depends on where you need it to operate. A &lt;code&gt;global&lt;/code&gt; scope means the observer operates universally, both in the admin area and the frontend. If your observer is only needed in the backend, use the &lt;code&gt;adminhtml&lt;/code&gt; scope. Meanwhile, if it's strictly for the user-facing part of your site, then &lt;code&gt;frontend&lt;/code&gt; is the appropriate scope.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make the Observer as Efficient as Possible
&lt;/h3&gt;

&lt;p&gt;Efficiency is key when it comes to coding, particularly with observers in Magento 2. To increase the efficiency of your observer, streamline your code. Try to reduce unnecessary loops and calculations as these can slow down your web pages. Remember, a lean observer is a fast observer. For instance, if there's an SQL query within your observer code that's not essential or could be optimized, it might be worth revisiting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoid Cyclical Event Loops
&lt;/h3&gt;

&lt;p&gt;Avoiding cyclical event loops is another crucial best practice. In a cyclical event loop, an event triggers an observer which then dispatches another event, which then triggers the initial event, and so on. This can lead to significant performance issues. To prevent this, ensure that your observers are mindful of the events they're triggering and avoid creating unnecessary dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do Not Rely on Invocation Order
&lt;/h3&gt;

&lt;p&gt;Finally, when dealing with multiple observers in Magento 2, do not rely on their invocation order. The order in which observers are invoked in Magento 2 is not specified, so building dependencies based on order can lead to unpredictable results. Each observer should be designed to operate independently, regardless of when it's invoked in the sequence.&lt;/p&gt;

&lt;p&gt;In conclusion, using Magento 2 observers effectively involves careful scope placement, lean and efficient coding, avoidance of cyclical event loops, and independent design to prevent reliance on invocation order. Following these best practices will help you optimize your Magento 2 observer use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Come to an end
&lt;/h2&gt;

&lt;p&gt;And there we have it - the &lt;strong&gt;first&lt;/strong&gt; steps into Magento 2's intriguing observer architecture. Don't worry if it seems a bit overwhelming now; with each post in this series, the fog will clear, and the road will become more navigable. Remember, all great journeys start with a single step.&lt;/p&gt;

&lt;p&gt;As we journey together, I'd love to connect with you on other platforms as well. You can follow me on &lt;a href="https://twitter.com/DavidLambauer"&gt;Twitter&lt;/a&gt; for regular insights, quick tips, and all things Magento. Or join me on &lt;a href="https://github.com/DavidLambauer"&gt;Github&lt;/a&gt; to explore my projects, learn from the code and maybe even contribute to some open source initiatives!&lt;/p&gt;

&lt;p&gt;Stay tuned for the next post in this series, and don't forget, the world of Magento 2 is expansive and full of opportunities for those willing to explore. As always, happy Magento coding!&lt;/p&gt;

</description>
      <category>magento2</category>
      <category>php</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Understanding Magento 2 Modules: A Comprehensive Look into Key Class Types</title>
      <dc:creator>David Lambauer</dc:creator>
      <pubDate>Thu, 15 Jun 2023 18:28:46 +0000</pubDate>
      <link>https://forem.com/run_as_root/understanding-magento-2-modules-a-comprehensive-look-into-key-class-types-2cm2</link>
      <guid>https://forem.com/run_as_root/understanding-magento-2-modules-a-comprehensive-look-into-key-class-types-2cm2</guid>
      <description>&lt;p&gt;Greetings, Magento 2 enthusiasts. The life of a developer is laden with challenges, one of the most prominent being structuring and naming modules effectively. This task, albeit complex, is crucial in creating software that is easy to read, modify, and debug. Let's embark on an exploration of five pivotal class types that are instrumental in structuring Magento 2 modules efficiently.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Mapper Classes
&lt;/h2&gt;

&lt;p&gt;The first class on our list is the Mapper Class. Mapper classes are invaluable tools that mediate and translate between different data types and structures. They provide an efficient way to convert data from one format to another, hence increasing the interoperability of your modules.&lt;/p&gt;

&lt;p&gt;In a Magento context, Mapper classes can be utilized to translate data from a CSV file to a more structured format like RSS or even a product DTO (Data Transfer Object). The Mapper class ensures this conversion process is streamlined and efficient, reducing code redundancy and increasing maintainability.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CsvToRssMapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$csvData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Conversion logic from CSV data to RSS format&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By encapsulating the mapping logic within its domain, a Mapper class promotes single responsibility and enhances code readability.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Validator Classes
&lt;/h2&gt;

&lt;p&gt;Validator Classes play a crucial role in maintaining the integrity and consistency of data within the application. They enforce rules and constraints to ensure the incoming data adheres to the specified format and conditions.&lt;/p&gt;

&lt;p&gt;For example, in an e-commerce store, a ProductValidator class might scrutinize whether the product data entered by the admin meets all requirements, such as non-empty product name and valid SKU, before it gets saved into the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductValidator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$productData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Validate if the productData meets the requirements&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validator classes not only safeguard the application from invalid data but also encapsulate validation logic, keeping the code clean and organized.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Command Classes (CQRS)
&lt;/h2&gt;

&lt;p&gt;Command classes form a critical part of the Command Query Responsibility Segregation (CQRS) pattern, which segregates operations that mutate state (Commands) from operations that return data (Queries).&lt;/p&gt;

&lt;p&gt;In a Magento store, you might have a command like "UpdateProductStockCommand" that takes a product SKU and the new stock quantity, updating the inventory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UpdateProductStockCommand&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$newQuantity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Logic to update the stock quantity of the product&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Command classes, by focusing solely on state mutation, bring clarity to your code and reduce the likelihood of side effects in data manipulation operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Query Classes (CQRS)
&lt;/h2&gt;

&lt;p&gt;Complementing Command classes in the CQRS pattern are Query classes. They are solely responsible for data retrieval and do not alter the state of the application.&lt;/p&gt;

&lt;p&gt;For instance, a Query class in Magento could be "GetProductBySkuQuery," which fetches a product's data based on its SKU without affecting the underlying data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetProductBySkuQuery&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sku&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Fetch and return the product data using SKU&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By decoupling data retrieval from data mutation, Query classes enable safer and more efficient data handling, enhancing the performance and reliability of the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Service Classes
&lt;/h2&gt;

&lt;p&gt;Service classes are the workhorses of any Magento application. They encapsulate business logic and serve as a bridge between the presentation and data access layers.&lt;/p&gt;

&lt;p&gt;In a typical e-commerce scenario, a ProductService might orchestrate the process of creating a product, which could involve validating input data, mapping it to a product entity, and saving it to the database using a repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;createProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$productData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Business logic to create a new product&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Service classes bring structure and order to the application, encapsulating complex operations, and promoting code reusability and maintenance.&lt;/p&gt;

&lt;p&gt;To sum up, understanding these five class types – Mapper, Validator, Command, Query, and Service – is essential to structure and name Magento 2 modules effectively. These classes, when utilized properly, can make your code more readable, maintainable, and scalable. Here's to writing cleaner and more efficient Magento 2 code!&lt;/p&gt;

</description>
      <category>magento</category>
      <category>magento2</category>
      <category>softwareengineering</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
