<?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: Etienne Lebarillier</title>
    <description>The latest articles on Forem by Etienne Lebarillier (@etienneleba).</description>
    <link>https://forem.com/etienneleba</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%2Fuser%2Fprofile_image%2F1512998%2Fd27dbea2-83a5-46f7-82c6-1f93564b240f.jpg</url>
      <title>Forem: Etienne Lebarillier</title>
      <link>https://forem.com/etienneleba</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/etienneleba"/>
    <language>en</language>
    <item>
      <title>My go-to workflow to build the business logic 🚀</title>
      <dc:creator>Etienne Lebarillier</dc:creator>
      <pubDate>Wed, 04 Dec 2024 11:20:27 +0000</pubDate>
      <link>https://forem.com/etienneleba/my-go-to-workflow-to-build-the-business-logic-4mfg</link>
      <guid>https://forem.com/etienneleba/my-go-to-workflow-to-build-the-business-logic-4mfg</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/etienneleba" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F1512998%2Fd27dbea2-83a5-46f7-82c6-1f93564b240f.jpg" alt="etienneleba"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/etienneleba/test-all-your-business-logic-in-less-than-1-second-2n84" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Test All your Business Logic in less than 1 second&lt;/h2&gt;
      &lt;h3&gt;Etienne Lebarillier ・ Dec 3&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#php&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#testing&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#performance&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Test All your Business Logic in less than 1 second</title>
      <dc:creator>Etienne Lebarillier</dc:creator>
      <pubDate>Tue, 03 Dec 2024 08:07:00 +0000</pubDate>
      <link>https://forem.com/etienneleba/test-all-your-business-logic-in-less-than-1-second-2n84</link>
      <guid>https://forem.com/etienneleba/test-all-your-business-logic-in-less-than-1-second-2n84</guid>
      <description>&lt;p&gt;This article isn’t about testing. It’s about adopting a workflow that keeps you in control while developing your feature. Tests are just the engine and the happy consequence of this process. &lt;/p&gt;

&lt;p&gt;This workflow has completely transformed the way I code, and it never fails to put a smile on my face. My hope is that it does the same for you. By the end, you’ll have a fully developed feature that satisfies all business rules and a test suite that validates it in less than a second.&lt;/p&gt;

&lt;p&gt;I used PHP for the demonstration, but this workflow is fully adaptable to any language.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Workflow and Testing Frustrations
&lt;/h2&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Where do I start my feature?
&lt;/h3&gt;

&lt;p&gt;When it’s time to develop a new feature, it’s often hard to know where to begin. Should you start with the business logic, the controller, or the front end ?&lt;/p&gt;

&lt;p&gt;It’s equally tricky to know when to stop. Without a clear process, the only way to gauge your progress is through manual testing. A tedious and error-prone approach.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Don’t be scare, it’s just a 2,000-line file, without any tests 😁
&lt;/h3&gt;

&lt;p&gt;You know the one. That file where unexplainable things happen. You need to change a single line, but every attempt seems to break the entire project. Without a safety net of tests, refactoring feels like walking a tightrope over a canyon.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Tests are painfully slow
&lt;/h3&gt;

&lt;p&gt;To avoid this chaos, you decide to test every feature with end-to-end tests. Great idea ! Until you realize you have enough time to drink five cups of coffee while waiting for the test suite to finish. Productivity ? Out the window.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Tests Break After Every Refactor
&lt;/h3&gt;

&lt;p&gt;In an effort to create instant feedback, you decide to write fine-grained tests for all your classes. However, now every change you make leads to a cascade of broken tests, and you end up spending more time fixing them than working on the actual change. It’s frustrating, inefficient, and makes you dread every refactor.&lt;/p&gt;




&lt;h2&gt;
  
  
  Let's Dream ! What's the perfect test ?
&lt;/h2&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Instant feedback
&lt;/h3&gt;

&lt;p&gt;Feedback is key at every stage of software development. While developing the core of the project, I need instant feedback to know immediately if any of my business rules are broken. During refactoring, having an assistant that informs me whether the code is broken or if I can proceed safely is an invaluable advantage.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Focus on the behaviors
&lt;/h3&gt;

&lt;p&gt;What the project is able to do, the project behaviors, is the most important aspect. These behaviors should be user-centric. Even if the implementation (the code created to make a feature work) changes, the user's intention will remain the same.&lt;/p&gt;

&lt;p&gt;For example, when a user places a product order, they want to be notified. There are many ways to notify a user: email, SMS, mail, etc. While the user's intention doesn't change, the implementation often will.&lt;/p&gt;

&lt;p&gt;If a test represents the user's intention and the code no longer fulfills that intention, the test should fail. However, during refactoring, if the code still meets the user's intention, the test should not break.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  The Copilot
&lt;/h3&gt;

&lt;p&gt;Imagine being guided step by step through the creation of a new feature. By writing the test first, it becomes your guide to coding the correct feature. It might not be entirely clear yet, but I want my test to function as my GPS while I code. With this approach, I don’t need to think about the next step, I simply follow the GPS instructions. If this concept still feels unclear, don’t worry ! I’ll explain it in detail in the workflow section. 😉&lt;/p&gt;




&lt;h2&gt;
  
  
  Few Requirements before Starting
&lt;/h2&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  The Feature and the Acceptance Tests
&lt;/h3&gt;

&lt;p&gt;As I mentioned in the introduction, this article isn’t about testing—it’s about creating a feature. And to do that, I need a feature. More specifically, I need a feature accompanied by acceptance tests defined with examples. For each feature, the team should establish acceptance criteria or rules, and for each rule, create one or more examples.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ These examples must be user/domain-centric, with no technical aspects described. A simple way to check if your examples are well-defined is to ask yourself: "Would my example work with any implementation (Web front-end, HTTP API, Terminal CLI, Real life, etc.)?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;For instance, if I want to develop the "Add product to basket" feature, my examples could look 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%2Fxrnhzrb1qb2y821aopg8.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%2Fxrnhzrb1qb2y821aopg8.png" alt="Examples for the feature " width="610" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These examples will serve as the foundation for the tests. For more complex scenarios, I use the Given/When/Then pattern for additional detail, but it’s not necessary for simpler ones. &lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Domain-centric architecture
&lt;/h3&gt;

&lt;p&gt;Instant feedback and a focus on behaviors, isn’t that quite a challenge ? With the Symfony MVC + Services architecture, achieving this can be difficult. That’s why I’ll use a domain-focused architecture, which I’ve detailed in this article: &lt;a href="https://dev.to/etienneleba/another-way-to-structure-your-symfony-project-llo"&gt;Another Way to Structure Your Symfony Project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Behaviors focused tests
&lt;/h3&gt;

&lt;p&gt;To focus on behaviors, I need to structure the tests the right way. First, I describe the state of the system before the user action. Next, I execute the user action, which changes the state of the system. Finally, I assert that the system's state matches my expectations. &lt;/p&gt;

&lt;p&gt;By testing this way, the test doesn’t care &lt;em&gt;how&lt;/em&gt; the user action is handled by the system; it only checks whether the action succeeds or not. This means that if we change the implementation of the user action, the test won’t break. However, if the implementation fails to update the system’s state as expected, the test &lt;em&gt;will&lt;/em&gt; break—and that’s exactly what we want!&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Fake the I/O
&lt;/h3&gt;

&lt;p&gt;To achieve instant feedback, mocking certain parts of the system is needed. A domain-centric architecture makes this possible because the domain relies solely on interfaces to interact with external libraries. This makes it incredibly easy to fake those dependencies, allowing the functional tests to run super fast. Of course, the real implementations will also be tested (though not in this article) using integration tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  The GPS Workflow
&lt;/h2&gt;

&lt;p&gt;In this workflow, the test is my GPS ! I set a destination, let it guide me, and notifies me when I’ve arrived. The test will take shape based on the example the team provides.&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%2Fuawg85sd5waf29ouurw4.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%2Fuawg85sd5waf29ouurw4.png" alt="The first example" width="351" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Enter your Destination
&lt;/h3&gt;

&lt;p&gt;To test the business logic and the user intention, I use a functional test :&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="c1"&gt;// tests/Functional/AddProductToBasketTest.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Tests\Functional&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddProductToBasketTest&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;testAddProductToBasket&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;This file will contain all the tests for this feature, but let's start with the first one. &lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Arrange
&lt;/h4&gt;

&lt;p&gt;In the first part, I describe the state of the application. Here, there is an empty basket for the customer with the id "1".&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="nv"&gt;$customerId&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="nv"&gt;$productId&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="nv"&gt;$basketId&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="nv"&gt;$basket&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;Basket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$basketId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$inMemoryBasketRepository&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;InMemoryBasketRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baskets&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$basket&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Act
&lt;/h4&gt;

&lt;p&gt;The handler and the command represent the user intention, like this it's explicit and everyone understand.&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="nv"&gt;$commandHandler&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;AddProductToBasketCommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$inMemoryBasketRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$commandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AddProductToBasketCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;basketId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$basketId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;📝 : I decided to separate the commands and the queries in this project, but we could have a AddProductToBasketUseCase. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Assert
&lt;/h4&gt;

&lt;p&gt;Finally, it's time to describe what the final result should look like. I expect to have the right product in my basket.&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="nv"&gt;$expectedBasket&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;Basket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$basketId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$productId&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;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$expectedBasket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$inMemoryBasketRepository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;baskets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  The Destination
&lt;/h4&gt;

&lt;p&gt;Here is the test that translates the first example into code.&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="c1"&gt;// tests/Functional/AddProductToBasketTest.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Tests\Functional&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddProductToBasketTest&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;testAddProductToBasket&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="c1"&gt;// Arrange&lt;/span&gt;
        &lt;span class="nv"&gt;$customerId&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="nv"&gt;$productId&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="nv"&gt;$basketId&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="nv"&gt;$basket&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;Basket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$basketId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$inMemoryBasketRepository&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;InMemoryBasketRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baskets&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$basket&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="c1"&gt;// Act&lt;/span&gt;
        &lt;span class="nv"&gt;$commandHandler&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;AddProductToBasketCommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$inMemoryBasketRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$commandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AddProductToBasketCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;basketId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$basketId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// Assert&lt;/span&gt;
        &lt;span class="nv"&gt;$expectedBasket&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;Basket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$basketId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$productId&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;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$expectedBasket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$inMemoryBasketRepository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;baskets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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; &lt;/p&gt;

&lt;h3&gt;
  
  
  Don't be Scared of Errors
&lt;/h3&gt;

&lt;p&gt;At this point, my IDE is showing errors everywhere because nothing I used in this test exists yet. But are these really errors, or are they simply the next steps toward our destination ? I treat these errors as instructions, each time I run the test, it will tell me what to do next. This way, I don’t have to overthink; I just follow the GPS.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; To improve the developer experience, you can enable a file watcher that automatically runs the test suite whenever you make a change. Since the test suite is extremely fast, you’ll get instant feedback on every update.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;In this example, I’ll address each error one by one. Of course, if you feel confident, you can take shortcuts along the way. 😉&lt;/p&gt;

&lt;p&gt;So, let’s run the test ! For each error, I’ll do the bare minimum needed to move on to the next step.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Make the test compile
&lt;/h4&gt;

&lt;p&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❌ 🤖 : &lt;strong&gt;&lt;em&gt;"Error : Class "App\Tests\Functional\Basket" not found"&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since it's the first test for this feature, most of the classes do not exist yet. So first step, I create a Basket object :&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="c1"&gt;// src/Catalog/Domain/Basket.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Catalog\Domain&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;Basket&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$id&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;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$products&lt;/span&gt; &lt;span class="o"&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;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; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❌ 🤖 : &lt;strong&gt;&lt;em&gt;"Error : Class "App\Tests\Functional\InMemoryBasketRepository" not found"&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I create the InMemoryBasketRepository :&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="c1"&gt;// src/Catalog/Infrastructure/Persistence/InMemory/InMemoryBasketRepository.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Catalog\Infrastructure\Persistence\InMemory&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;InMemoryBasketRepository&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="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$baskets&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; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❌ 🤖 : &lt;strong&gt;&lt;em&gt;"Error : Class "App\Tests\Functional\AddProductToBasketCommandHandler" not found"&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I create the AddProductToBasketCommandHandler in the Catalog/Application folder, this handler will be the entry point for the "Add product to basket" feature.&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="c1"&gt;// src/Catalog/Application/Command/AddProductToBasket/AddProductToBasketCommandHandler.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Catalog\Application\Command\AddProductToBasket&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;App\Catalog\Domain\BasketRepository&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;AddProductToBasketCommandHandler&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;BasketRepository&lt;/span&gt; &lt;span class="nv"&gt;$basketRepository&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__invoke&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;To respect the domain-centric architecture, I create an interface for the repository, like this we invert the dependency.&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="c1"&gt;// src/Catalog/Domain/BasketRepository.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Catalog\Domain&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;BasketRepository&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; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❌ 🤖 : &lt;strong&gt;&lt;em&gt;"TypeError : App\Catalog\Application\Command\AddProductToBasket\AddProductToBasketCommandHandler::__construct(): Argument #1 ($basketRepository) must be of type App\Catalog\Domain\BasketRepository, App\Catalog\Infrastructure\Persistence\InMemory\InMemoryBasketRepository given"&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, the InMemory implementation must implement the interface to be able to be injected in the command handler.&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="mf"&gt;...&lt;/span&gt; 

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InMemoryBasketRepository&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;BasketRepository&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; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❌ 🤖 : &lt;strong&gt;_"Error : Class "App\Tests\Functional\AddProductToBasketCommand" not found"&lt;br&gt;
_&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I create a command, since the a command is always a DTO, I marked it as readonly and put all the properties public.&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="c1"&gt;// src/Catalog/Application/Command/AddProductToBasket/AddProductToBasketCommand.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Catalog\Application\Command\AddProductToBasket&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddProductToBasketCommand&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="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$basketId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$productId&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;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❌ 🤖 : &lt;strong&gt;&lt;em&gt;"Failed asserting that two objects are equal."&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The test is compiling ! It's still red, so we are not finish yet, let's keep going ! 🚀 &lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Let's Go for the Business Logic
&lt;/h4&gt;

&lt;p&gt;It’s time to code the business logic. Just like when I was writing the test, even though nothing exists yet, I write how I expect my handler to process the command. It won’t compile, but once again, the test will guide me to the next step.&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="c1"&gt;// src/Catalog/Application/Command/AddProductToBasket/AddProductToBasketCommandHandler.php&lt;/span&gt;

&lt;span class="mf"&gt;...&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddProductToBasketCommandHandler&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;__invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;AddProductToBasketCommand&lt;/span&gt; &lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$basket&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;basketRepository&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="nv"&gt;$command&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;basketId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$basket&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;productId&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;basketRepository&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="nv"&gt;$basket&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; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❌ 🤖 : &lt;strong&gt;&lt;em&gt;"Error : Call to undefined method App\Catalog\Infrastructure\Persistence\InMemory\InMemoryBasketRepository::get()"&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I create the get method in the repository interface :&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="c1"&gt;// src/Catalog/Domain/BasketRepository.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Catalog\Domain&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;BasketRepository&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;get&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;$basketId&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Basket&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; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❌ 🤖 : &lt;strong&gt;&lt;em&gt;"PHP Fatal error:  Class App\Catalog\Infrastructure\Persistence\InMemory\InMemoryBasketRepository contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (App\Catalog\Domain\BasketRepository::get)"&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And now I implement it in the InMemory repository. Since it's only to test, I create a really simple implementation :&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="c1"&gt;// src/Catalog/Infrastructure/Persistence/InMemory/InMemoryBasketRepository.php&lt;/span&gt;
&lt;span class="mf"&gt;...&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InMemoryBasketRepository&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;BasketRepository&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;get&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;$basketId&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Basket&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&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;baskets&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$basket&lt;/span&gt;&lt;span class="p"&gt;)&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;$basket&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$basketId&lt;/span&gt;&lt;span class="p"&gt;)&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;$basket&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❌ 🤖 : &lt;strong&gt;&lt;em&gt;"Error : Call to undefined method App\Catalog\Domain\Basket::add()"&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I create the &lt;em&gt;add&lt;/em&gt; method and implement it on the basket object&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="c1"&gt;// src/Domain/Basket.php&lt;/span&gt;

&lt;span class="mf"&gt;...&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Basket&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;add&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;$productId&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;products&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$productId&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; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❌ 🤖 : &lt;strong&gt;&lt;em&gt;Error : Call to undefined method App\Catalog\Infrastructure\Persistence\InMemory\InMemoryBasketRepository::save()&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Same here, I create the method in the interface and I implement it in the in-memory 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="c1"&gt;// src/Catalog/Domain/BasketRepository.php&lt;/span&gt;

&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;BasketRepository&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;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Basket&lt;/span&gt; &lt;span class="nv"&gt;$basket&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="p"&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 php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/Catalog/Infrastructure/Persistence/InMemory/InMemoryBasketRepository.php&lt;/span&gt;

&lt;span class="mf"&gt;...&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InMemoryBasketRepository&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;BasketRepository&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;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Basket&lt;/span&gt; &lt;span class="nv"&gt;$basket&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;baskets&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$basket&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; &lt;/p&gt;

&lt;blockquote&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%2Fow199o4ln80g10wpvytz.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%2Fow199o4ln80g10wpvytz.png" alt="Image description" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Destination reached! 🎉 🍾 🥂  &lt;/p&gt;

&lt;p&gt;With this workflow, I’ve gamified the developer experience. After the challenge of tackling the red test, seeing it turn green gives you a shot of dopamine 💉🎊&lt;br&gt;
 &lt;/p&gt;
&lt;h3&gt;
  
  
  Refactoring Time
&lt;/h3&gt;

&lt;p&gt;To make the test pass, I intentionally went a bit fast. Now, I can take the time to refine the test and implement the code in a better way.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h4&gt;
  
  
  The test
&lt;/h4&gt;

&lt;p&gt;The test translate the example but lost the user intention during the translation. With few functions, I can make it much closer to the original example. &lt;/p&gt;

&lt;p&gt;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%2F7vi7dn3enjj83iml2ho3.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%2F7vi7dn3enjj83iml2ho3.png" alt="Image description" width="351" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Test :&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="c1"&gt;// tests/Functional/AddProductToBasketTest.php&lt;/span&gt;

&lt;span class="mf"&gt;...&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddProductToBasketTest&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;testAddProductToBasket&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="c1"&gt;// Arrange&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;givenAnEmptyBasketExists&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Act&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;whenIAddAProductToTheBasket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;basketId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Assert&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;thenTheProductShouldBeInTheBasket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"1"&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; &lt;/p&gt;

&lt;h4&gt;
  
  
  The code
&lt;/h4&gt;

&lt;p&gt;This isn’t the focus of this article, so I won’t go into detail, but I could express the domain model more precisely using a rich domain model. &lt;/p&gt;

&lt;p&gt;Since the test is green, now I can refactor as much as I want, the test has my back. 🔒&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Other examples
&lt;/h3&gt;

&lt;p&gt;As you saw at the beginning of the article, one feature has a lot of examples to describe it. So it's time to implement them all :&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="c1"&gt;// tests/Functional/AddProductToBasketTest.php&lt;/span&gt;

&lt;span class="mf"&gt;...&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddProductToBasketTest&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="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;testAddAProductToTheBasket&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="c1"&gt;// Arrange&lt;/span&gt;
        &lt;span class="nv"&gt;$customerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$basketId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$productId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&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="nf"&gt;givenAnEmptyBasketExists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$basketId&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;givenAProductExists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$productId&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;givenACustomerExists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Act&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;whenIAddAProductToTheBasket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;basketId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$basketId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Assert&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;thenTheProductShouldBeInTheBasket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$basketId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"1"&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;testAddProductAlreadyInTheBasketToTheBasket&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="mf"&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;testAddProductThatDoesNotExistInTheBasket&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="mf"&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;testAddProductToBasketWithCustomerNotExists&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="c1"&gt;// Arrange&lt;/span&gt;
        &lt;span class="nv"&gt;$customerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Assert&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;thenIShouldGetAnError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CustomerNotFound&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="s2"&gt;"Customer with id &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; not found"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Arrange&lt;/span&gt;
        &lt;span class="nv"&gt;$productId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&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="nf"&gt;givenNoCustomerExists&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;givenNoBasketExists&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;givenAProductExists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Act&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;whenIAddAProductToTheBasket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;basketId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$productId&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;I’ve followed this workflow multiple times with different tests, and various parts of my code have evolved to align with the business rules. As you saw with the first test, my objects were very simple and sometimes even basic. However, as new business rules were introduced, they pushed me to develop a much smarter domain model. Here’s my folder structure :&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%2Fptsxzlhioiu6cxgl6slh.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%2Fptsxzlhioiu6cxgl6slh.png" alt="Image description" width="544" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;To simplify testing while preserving the encapsulation of domain models, I introduced the &lt;a href="https://refactoring.guru/design-patterns/builder" rel="noopener noreferrer"&gt;builder&lt;/a&gt; and snapshot patterns.&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="c1"&gt;// tests/Builder/BasketBuilder.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Tests\Builder&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;App\Catalog\Domain\Basket\BasketSnapshot&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;BasketBuilder&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;$id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1"&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;$customerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1"&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;array&lt;/span&gt; &lt;span class="nv"&gt;$entries&lt;/span&gt; &lt;span class="o"&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;buildSnapshot&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;BasketSnapshot&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BasketSnapshot&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;id&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;customerId&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;entries&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;setId&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;$id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;BasketBuilder&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;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$id&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="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;setCustomerId&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;$customerId&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;BasketBuilder&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;customerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$customerId&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="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;setEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$basketEntries&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;BasketBuilder&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;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$basketEntries&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="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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/Catalog/Domain/Basket/BasketSnapshot.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Catalog\Domain\Basket&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BasketSnapshot&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="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$entries&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/Catalog/Domain/Basket/Basket.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Catalog\Domain\Basket&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;Basket&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;array&lt;/span&gt; &lt;span class="nv"&gt;$entries&lt;/span&gt; &lt;span class="o"&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;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$customerId&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;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;add&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;$productId&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="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;isset&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;entries&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$productId&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;entries&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$productId&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;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;else&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;entries&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;]&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="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;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;fromSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;BasketSnapshot&lt;/span&gt; &lt;span class="nv"&gt;$snapshot&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Basket&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$basket&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;Basket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$snapshot&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$snapshot&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$basket&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$snapshot&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;entries&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;$basket&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;getSnapshot&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;BasketSnapshot&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BasketSnapshot&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;id&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;customerId&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;entries&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;How I use it :&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="c1"&gt;// tests/Functional/AddProductToBasketTest.php&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;givenAnEmptyBasketExists&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;$basketId&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;$basketSnapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BasketBuilder&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;setId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$basketId&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;buildSnapshot&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;inMemoryBasketRepository&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;InMemoryBasketRepository&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nv"&gt;$basketSnapshot&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$basketSnapshot&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Speed ⚡
&lt;/h2&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Workflow
&lt;/h3&gt;

&lt;p&gt;Since I’m completely focused on the business logic, my test guides me at every step, telling me what to do each time I add new code. Additionally, I no longer need to perform manual testing to ensure everything works, which significantly improves my efficiency.&lt;/p&gt;

&lt;p&gt;In this article, I demonstrated a very simple example where I mostly created classes. However, with more complex business logic, each new rule will add complexity to the domain model. During refactoring, my goal is to simplify the domain model while keeping the tests green. It’s a real challenge—but an incredibly satisfying one when it finally works.&lt;/p&gt;

&lt;p&gt;What I’ve shown here is just the first part of the complete workflow. As I mentioned, to have a fully functional feature, I still need to implement the real adapters. In this case, that would include creating the repositories (Basket, Customer, and Product) using a test-first approach with a real database. Once that’s complete, I would simply configure the real implementations in the dependency injection configuration. &lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;p&gt;I used pure PHP without relying on any framework, focusing solely on encapsulating all the business logic. With this strategy, I no longer have to worry about test performance. Whether there are hundreds or even thousands of tests, they would still run in just a few seconds, providing instant feedback while coding.&lt;/p&gt;

&lt;p&gt;To give you a hint of the performance, here’s the test suite repeated 10,000 times:&lt;/p&gt;

&lt;p&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%2Fmwh2uaq4d2lf8rppihxo.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%2Fmwh2uaq4d2lf8rppihxo.png" alt="Image description" width="475" height="91"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  4 seconds... ⚡
&lt;/h3&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Concepts
&lt;/h2&gt;

&lt;p&gt;This workflow sits at the intersection of several concepts. Let me introduce some of them so you can explore them further if needed.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Example mapping
&lt;/h3&gt;

&lt;p&gt;To describe a feature, the best approach is to provide as many examples as necessary. A team meeting can help you gain a deep understanding of the feature by identifying concrete examples of how it should work.&lt;/p&gt;

&lt;p&gt;The Given-When-Then framework is a great tool for formalizing these examples.&lt;/p&gt;

&lt;p&gt;To translate these examples into code, the Gherkin language (with Behat in PHP) can be helpful. However, I personally prefer to work directly in PHPUnit tests and name my functions using these keywords.&lt;/p&gt;

&lt;h4&gt;
  
  
  Go Further :
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cucumber.io/blog/bdd/example-mapping-introduction/" rel="noopener noreferrer"&gt;Matt Wynne - Introducing Example Mapping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=VwvrGfWmG_U" rel="noopener noreferrer"&gt;Aslak Hellesøy - Introducing Example Mapping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=WvkBKvMnyuc" rel="noopener noreferrer"&gt;Kenny Baas-Schwegler - Crunching 'real-life stories' with DDD &amp;amp; Event Storming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://martinfowler.com/bliki/GivenWhenThen.html" rel="noopener noreferrer"&gt;Martin Fowler - Given, When, Then&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  F.I.R.S.T
&lt;/h3&gt;

&lt;p&gt;The "what do we want ?" part could be summarize with the acronym F.I.R.S.T : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast &lt;/li&gt;
&lt;li&gt;Isolated &lt;/li&gt;
&lt;li&gt;Repeatable&lt;/li&gt;
&lt;li&gt;Self-validating &lt;/li&gt;
&lt;li&gt;Thorough
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now you have a checklist to know if your tests are well made. &lt;/p&gt;

&lt;h4&gt;
  
  
  Go further
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Robert C. Martin - Clean Code, Chapter 9 : Unit Tests, F.I.R.S.T&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Ports and Adapters, Hexagonal, Onion, and Clean Architecture
&lt;/h3&gt;

&lt;p&gt;All these architectures aim to isolate business logic from implementation details. Whether you use Ports and Adapters, Hexagonal, or Clean Architecture, the core idea is to make the business logic framework-agnostic and easy to test. &lt;/p&gt;

&lt;p&gt;Once you have this in mind, there is a full spectrum of implementations, and the best one depends on your context and preferences. A major advantage of this architecture is that, by isolating the business logic, it enables much more efficient testing.&lt;/p&gt;

&lt;h4&gt;
  
  
  Go Further
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://alistair.cockburn.us/hexagonal-architecture" rel="noopener noreferrer"&gt;Alistair Cockburn - Hexagonal Architecture&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html" rel="noopener noreferrer"&gt;Uncle Bob - Clean Architecture&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/" rel="noopener noreferrer"&gt;Herberto Graca - DDD, Hexagonal, Onion, Clean, CQRS, … How I Put It All Together&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Outside-in Diamond Test Strategy
&lt;/h3&gt;

&lt;p&gt;I guess your already familiar with the pyramid of test, instead I prefer to use the diamond representation. This strategy, described by Thomas Pierrain, is use-case driven and focus on the behaviors of our application. &lt;/p&gt;

&lt;p&gt;This strategy also encourages black-box testing of the application, focusing on the results it produces rather than how it produces them. This approach makes refactoring significantly easier. &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%2Fl3hlo24phfnhmzw5ifc6.jpg" 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%2Fl3hlo24phfnhmzw5ifc6.jpg" alt="Outside-in diamond test strategy" width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Go Further
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://tpierrain.blogspot.com/2021/03/outside-in-diamond-tdd-1-style-made.html" rel="noopener noreferrer"&gt;Thomas Pierrain - Outside-in Diamond 🔷 TDD #1 - a style made from (&amp;amp; for) ordinary people &lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="http://tpierrain.blogspot.com/2021/03/outside-in-diamond-tdd-2-anatomy-of.html" rel="noopener noreferrer"&gt;Thomas Pierrain - Outside-in Diamond 🔷 TDD #2 (anatomy of a style) &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=djdMp9i04Sc" rel="noopener noreferrer"&gt;Thomas Pierrain - Write Antifragile &amp;amp; Domain-Driven tests with ”Outside-in diamond” ◆ TDD&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Wishful thinking programming
&lt;/h3&gt;

&lt;p&gt;As you saw throughout the workflow, in the test and in the command handler, I always wrote what I wanted before it even existed. I made some "wishes". This approach is known as wishful thinking programming. It allows you to imagine the structure of your system and the ideal way to interact with it before deciding how to implement it. When combined with testing, it becomes a powerful method to guide your programming.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Arrange, Act, Assert
&lt;/h3&gt;

&lt;p&gt;This pattern helps structure tests effectively. First, the system is set to the correct initial state. Next, a state modifier, such as a command or user action, is executed. Finally, an assertion is made to ensure the system is in the expected state after the action.&lt;/p&gt;

&lt;h4&gt;
  
  
  Go further
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://xp123.com/3a-arrange-act-assert/" rel="noopener noreferrer"&gt;Bill Wake - 3A – Arrange, Act, Assert&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;We’ve walked through how a domain-focused architecture combined with acceptance test driven development can transform your development experience. Instant feedback, robust tests, and a focus on user intentions make coding not just more efficient but more enjoyable. &lt;/p&gt;

&lt;p&gt;Try this workflow, and you might find yourself smiling every time a test turns green ✅ ➡️ 😁 &lt;/p&gt;

&lt;p&gt;Let me know in the comments what would be your perfect workflow or what would you improve in this one ! &lt;/p&gt;

</description>
      <category>php</category>
      <category>testing</category>
      <category>performance</category>
    </item>
    <item>
      <title>Another Way to Structure your Symfony Project</title>
      <dc:creator>Etienne Lebarillier</dc:creator>
      <pubDate>Mon, 25 Nov 2024 09:54:41 +0000</pubDate>
      <link>https://forem.com/etienneleba/another-way-to-structure-your-symfony-project-llo</link>
      <guid>https://forem.com/etienneleba/another-way-to-structure-your-symfony-project-llo</guid>
      <description>&lt;p&gt;The MVC + Services architecture is so common in Symfony projects that it feels like the only way. It’s simple, familiar, and works... until it doesn’t. As your project grows, cracks begin to show: your business logic is everywhere, app behavior is unclear, and maintaining the code becomes painful. While it is the most common approach, Symfony does not force you to stick with it. &lt;/p&gt;

&lt;p&gt;What if there was a better way ?&lt;/p&gt;




&lt;h2&gt;
  
  
  The Frustrations of Using the MVC + Services Architecture
&lt;/h2&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Domain Logic is Spread Everywhere
&lt;/h3&gt;

&lt;p&gt;As a project grows, business logic tends to spread across the entire code base. Every layer of the project — controllers, services, forms, entities — ends up containing bits and pieces of the domain model. This makes it increasingly difficult to focus on any specific part.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  The Project Boundaries Are Not Clear
&lt;/h3&gt;

&lt;p&gt;When your architecture is organized around technical layers, it becomes harder to identify clear boundaries between different contexts as the project grows. This lack of clarity can lead to tightly coupled code and maintenance challenges.&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%2Fqdi8pw8y6xipqkn8dp5b.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%2Fqdi8pw8y6xipqkn8dp5b.png" alt="Symfony default folder tree" width="389" height="265"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  The Project Behaviors Are Not Clear
&lt;/h3&gt;

&lt;p&gt;Since the default architecture emphasizes technical layers, it becomes quite challenging to understand the behaviors of the project. You might infer that certain entities are managed by specific services or guess the database schema, but the actual behaviors of the project, which are the most important aspects, remain unclear and implicit.&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%2F2xfdwlqsjn2qoxzsooyv.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%2F2xfdwlqsjn2qoxzsooyv.png" alt="Symfony default folder tree opened" width="457" height="648"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Different Lifecycles
&lt;/h3&gt;

&lt;p&gt;When business logic is scattered across the project and mixed with implementation details, it becomes difficult to evolve them independently. Over time, The lifecycle of business logic tends to be much longer than that of implementation details (such as frameworks, third-party APIs, or databases). This mismatch forces you to rewrite large portions of code whenever even minor changes occur in a dependency.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Better Way to Structure Your Architecture
&lt;/h2&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Isolate the Business Logic
&lt;/h3&gt;

&lt;p&gt;To make it easier to focus on the business logic when needed, the first step is to isolate it from the rest of the project. To achieve this, create a &lt;strong&gt;&lt;code&gt;Domain&lt;/code&gt;&lt;/strong&gt; folder. This folder becomes the core of the project, where business logic is modeled using pure PHP objects, free from dependencies on implementation details. While the rest of the project depends on this folder, the &lt;code&gt;Domain&lt;/code&gt; folder depends on no one.&lt;/p&gt;

&lt;p&gt;Inside the &lt;code&gt;Domain&lt;/code&gt; folder, files should be grouped by domain purpose rather than technical purpose. This means no &lt;code&gt;Entities&lt;/code&gt;, &lt;code&gt;Services&lt;/code&gt;, or &lt;code&gt;Controllers&lt;/code&gt; folders here, only folder names that correspond to features or domain concepts.&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%2F2due5xu6jjafu15nd4i3.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%2F2due5xu6jjafu15nd4i3.png" alt="Domain with modules inside opened" width="404" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  The Front Door of the Domain
&lt;/h3&gt;

&lt;p&gt;The most important aspect of a project is what it &lt;em&gt;does&lt;/em&gt;, the actions it can handle. These actions represent the project's behaviors and should serve as the only way to access the business logic. To reflect this, create an &lt;strong&gt;&lt;code&gt;Application&lt;/code&gt;&lt;/strong&gt; folder that explicitly showcases all the project's behaviors. For example, as a new developer on the project, I should be able to understand at a glance what the project is capable of doing by looking at this folder.&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%2F0fw9e27va7n55la9xntm.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%2F0fw9e27va7n55la9xntm.png" alt="The Application folder opened with some queries and command inside" width="494" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting to the Outside World
&lt;/h3&gt;

&lt;p&gt;With the &lt;strong&gt;&lt;code&gt;App&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;Domain&lt;/code&gt;&lt;/strong&gt; folders, it becomes easy to focus on the business logic. However, at some point, this business logic needs to interact with external systems. To handle this, create a third folder called &lt;strong&gt;&lt;code&gt;Infrastructure&lt;/code&gt;&lt;/strong&gt;, which contains all the implementation details such as framework-specific code, database connections, and libraries.&lt;/p&gt;

&lt;p&gt;Files in the &lt;code&gt;Infrastructure&lt;/code&gt; folder depend on the &lt;code&gt;App&lt;/code&gt; and &lt;code&gt;Domain&lt;/code&gt; files. For example, they might call an application handler from the &lt;code&gt;App&lt;/code&gt; folder or implement an interface defined in the &lt;code&gt;Domain&lt;/code&gt; folder.&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%2Fwctcjwy65qdjwirzdj3s.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%2Fwctcjwy65qdjwirzdj3s.png" alt="The infrastructure folder opened" width="454" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Concretely, in Symfony, this involves modifying the &lt;code&gt;Controller&lt;/code&gt; folder and declaring which services implement which interfaces.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/routes.yaml&lt;/span&gt;

&lt;span class="na"&gt;controllers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../src/Catalog/Infrastructure/Controller/&lt;/span&gt;
        &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;App\Catalog\Infrastructure\Controller&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;attribute&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Reveal the Boundaries
&lt;/h3&gt;

&lt;p&gt;As your project evolves, you might notice that some parts of the business logic deserve their own space in the architecture. A good indicator is when the same term starts to have different meanings depending on the context. For example, the word Product might refer to a factory product, a warehouse product, or an e-commerce product, each requiring its own model. A god object can also be a good indicator; the &lt;em&gt;User&lt;/em&gt; class often has this type of issue in Symfony projects. &lt;/p&gt;

&lt;p&gt;When this happens, it’s time to extract it and let its business logic evolve independently. &lt;/p&gt;

&lt;p&gt;Some contexts will form the core of your project, while others will support it. Generic contexts, such as Auth, can use a simpler architecture because they are not central to your domain&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%2Fo5b68wpsb1cvpq8xxnjk.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%2Fo5b68wpsb1cvpq8xxnjk.png" alt="All the bounded contexts opened" width="495" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this picture, we can see that the Auth context uses a standard Symfony structure, the Order and Catalog contexts use a domain-focused architecture, and the Shipping context uses a feature-focused architecture. &lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Incremental Modularity
&lt;/h3&gt;

&lt;p&gt;If a specific context grows to the point where it needs to scale independently, consider splitting it into a separate deployment unit. &lt;/p&gt;

&lt;p&gt;However, don’t rush into this step. Start by making your project modular, and if you notice that a context needs to scale individually, then deploy it separately. &lt;br&gt;
Only split the codebase if organizational challenges arise, such as two teams struggling to collaborate on the same codebase.&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%2Fbiexfzozgdkaw9xtp251.jpg" 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%2Fbiexfzozgdkaw9xtp251.jpg" alt="The different classes of project architectures, credits: Simon Brown" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Go Further
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=5OjqD-ow8GE" rel="noopener noreferrer"&gt;Simon Brown - Modular Monoliths&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Concepts
&lt;/h2&gt;

&lt;p&gt;As we explored these solutions, we applied several craftsmanship concepts. Let’s name and briefly explain them, so you can dive deeper into each one.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Ubiquitous Language
&lt;/h3&gt;

&lt;p&gt;Behind this unusual term lies a very simple concept. The ubiquitous language is the vocabulary your team uses to describe your domain model. This vocabulary should be documented and consistently used everywhere, in product conversations, the codebase, and beyond.&lt;/p&gt;

&lt;p&gt;Concretely, create a markdown file at the root of a Bounded Context and bring together product people, domain experts, and tech teams to define each concept of your project. &lt;/p&gt;

&lt;h4&gt;
  
  
  Go Further
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Eric Evans - DDD - Chapter 2 : Communication and the use of language&lt;/li&gt;
&lt;li&gt;&lt;a href="https://martinfowler.com/bliki/UbiquitousLanguage.html" rel="noopener noreferrer"&gt;Martin Fowler - Ubiquitous Language&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Bounded Context
&lt;/h3&gt;

&lt;p&gt;A bounded context defines the linguistic boundary within your project, separating parts of the system where the ubiquitous language no longer aligns. Tools like Context Maps and Event Storming can help identify these boundaries.&lt;/p&gt;

&lt;p&gt;A bounded context is an abstract concept; it can be implemented in many ways, from a simple folder in a modular monolith to a cluster in a microservices architecture.&lt;/p&gt;

&lt;h4&gt;
  
  
  Go Further
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Eric Evans - DDD - Part IV : Strategic Design &lt;/li&gt;
&lt;li&gt;Vaughn Vernon - IDDD - Chapter 2 : Domains, Subdomains, and Bounded Context
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://martinfowler.com/bliki/BoundedContext.html" rel="noopener noreferrer"&gt;Martin Fowler - Bounded Context&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Ports and Adapters, Hexagonal, Onion, and Clean Architecture
&lt;/h3&gt;

&lt;p&gt;All these architectures aim to isolate business logic from implementation details. Whether you use Ports and Adapters, Hexagonal, or Clean Architecture, the core idea is to make the business logic framework-agnostic and easy to test. &lt;/p&gt;

&lt;p&gt;Once you have this in mind, there is a full spectrum of implementations, and the best one depends on your context and preferences. A major advantage of this architecture is that, by isolating your business logic, it enables much more efficient testing.&lt;/p&gt;

&lt;h4&gt;
  
  
  Go Further
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://alistair.cockburn.us/hexagonal-architecture" rel="noopener noreferrer"&gt;Alistair Cockburn - Hexagonal Architecture&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html" rel="noopener noreferrer"&gt;Uncle Bob - Clean Architecture&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/" rel="noopener noreferrer"&gt;Herberto Graca - DDD, Hexagonal, Onion, Clean, CQRS, … How I Put It All Together&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Screaming Architecture
&lt;/h3&gt;

&lt;p&gt;The idea of organizing folders and files to "scream" the business logic is known as Screaming Architecture. This concept emphasizes that the structure of your code should make the project’s purpose immediately clear. The goal is for a new developer to understand what the project does at a glance. &lt;/p&gt;

&lt;p&gt;I highly recommend reading Uncle Bob’s article on the topic—his comparison to a house plan is particularly insightful.&lt;/p&gt;

&lt;h4&gt;
  
  
  Go Further
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://blog.cleancoder.com/uncle-bob/2011/09/30/Screaming-Architecture.html" rel="noopener noreferrer"&gt;Uncle Bob - Screaming Architecture&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Vertical Slicing Architecture
&lt;/h3&gt;

&lt;p&gt;Vertical slicing organizes your project by features, allowing each feature to evolve independently. It enables you to apply different architectures to different features based on complexity and maturity.&lt;/p&gt;

&lt;p&gt;Although the idea is interesting, it requires highly skilled engineers to implement and maintain such an architecture effectively.&lt;/p&gt;

&lt;h4&gt;
  
  
  Go Further
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.jimmybogard.com/vertical-slice-architecture/" rel="noopener noreferrer"&gt;Jimmy Bogard - Vertical Slice Architecture&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=L2Wnq0ChAIA" rel="noopener noreferrer"&gt;CodeOpinion - Vertical Slice Architecture, not Layers!&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The way you structure your Symfony project has a profound impact on its scalability, maintainability, and clarity. By isolating your business logic and making behaviors explicit, you’ll create a system that’s easier to understand and evolve.&lt;/p&gt;

&lt;p&gt;If you’re new to these ideas, don’t worry, software craftsmanship is a journey, not a destination. The concepts might seem overwhelming at first, but each one will help you deliver more value to your business.&lt;/p&gt;

&lt;p&gt;Have questions or want to share your experience ? Drop them in the comments ! And stay tuned for the next article 🚀&lt;/p&gt;

</description>
      <category>php</category>
      <category>symfony</category>
      <category>architecture</category>
      <category>ddd</category>
    </item>
    <item>
      <title>My Favorite Quotes in Extreme Programming</title>
      <dc:creator>Etienne Lebarillier</dc:creator>
      <pubDate>Mon, 18 Nov 2024 10:33:41 +0000</pubDate>
      <link>https://forem.com/etienneleba/my-favorite-quotes-in-extreme-programming-4kjj</link>
      <guid>https://forem.com/etienneleba/my-favorite-quotes-in-extreme-programming-4kjj</guid>
      <description>&lt;p&gt;Extreme Programming (XP) might not be as "extreme" as its name suggests, but its influence on software development is undeniable. Kent Beck's visionary approach laid the foundation for many practices that are now industry standards. Understanding the 'why' behind these practices is crucial, as the values and principles Beck shared are arguably the most important aspects of XP. While we often focus on the practices, it’s the underlying values that truly drive successful software development. Here are 8 quotes from Beck that have impacted me.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;It's not my job to "manage" someone else expectations. It's their job to manage their own expectations. It's my job to do my best and communicate clearly (p. 5)&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;Everything in software changes. The requirements change. The design changes. The business changes. The technology changes. The problem isn't change,  because change is going to happen; the problem, rather, it's our inability to cope with change (p.11)&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;Direction set in advance of experience have an especially short half-life. Change is inevitable, but change create the need for feedback (p. 19)&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;In software development, "perfect" is a verb, not an adjective (p. 28)&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;If you have trouble succeeding, fail (p. 32)&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;Sacrificing quality is not effective as a means of control. Quality is not a control variable. Projects don't go faster by accepting lower quality. They don't go slower by demanding higher quality. Pushing quality higher often results in faster delivery; while lowering quality standards often results in later, less predictable delivery (p. 32)&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;If I don't have to compute the right answers, I can make a system go as fast as you want (p. 125)&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;Sharing power is pragmatic, not idealistic (p. 155)&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;And you ? Which one is your favorite ? &lt;/p&gt;

</description>
      <category>productivity</category>
      <category>agile</category>
      <category>management</category>
      <category>xp</category>
    </item>
    <item>
      <title>10 Tips to Code Faster - Guaranteed Without AI ! 🚀</title>
      <dc:creator>Etienne Lebarillier</dc:creator>
      <pubDate>Tue, 12 Nov 2024 13:27:42 +0000</pubDate>
      <link>https://forem.com/etienneleba/10-tips-to-code-faster-guaranteed-without-ai--72c</link>
      <guid>https://forem.com/etienneleba/10-tips-to-code-faster-guaranteed-without-ai--72c</guid>
      <description>&lt;p&gt;Sure, AI is transforming the way we work, making us more productive than ever. But what about your speed &lt;em&gt;before&lt;/em&gt; AI ? There are plenty of powerful, old-school tricks that can boost your productivity without any AI involved. Here, I’m sharing some tried-and-true tips that have significantly accelerated my workflow, and they can do the same for you.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  1. Master Typing with All 10 Fingers 🖐️🤚
&lt;/h2&gt;

&lt;p&gt;This is the bedrock of every productivity hack to come. &lt;/p&gt;

&lt;p&gt;Can you type without looking at the keyboard ? If so, are you using all 10 fingers? &lt;/p&gt;

&lt;p&gt;Back in university, I could type without peeking, but my technique was off — I was slower, made more typos, and overused certain fingers. So, I took the time to properly learn 10-finger typing, and it completely changed the game. Now, I’m quicker, make fewer mistakes, and can use keyboard shortcuts far more effectively. &lt;/p&gt;

&lt;p&gt;Learning this skill took a few weeks, but it’s one of the best investments I’ve made.&lt;/p&gt;

&lt;p&gt;My go-to tool for this ? &lt;a href="https://www.typingclub.com/" rel="noopener noreferrer"&gt;Typing Club&lt;/a&gt;. It offers a step-by-step program to build up your 10-finger skills and improve your Words Per Minute 🏆&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%2F253ir6ehzgxc0fh4zh4d.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%2F253ir6ehzgxc0fh4zh4d.png" alt="typing club exercise" width="800" height="697"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  2. Supercharge Your Terminal with Zsh and Oh My Zsh 🖥️✨
&lt;/h2&gt;

&lt;p&gt;For any developer, the terminal is a daily tool. &lt;a href="https://ohmyz.sh/" rel="noopener noreferrer"&gt;Zsh and Oh My Zsh&lt;/a&gt; let you customize your terminal and add powerful features easily.&lt;/p&gt;

&lt;p&gt;With plugins, you’ll enjoy streamlined Git commands, smarter auto-completions, and a generally enhanced terminal experience. Check out the full list of plugins here: &lt;a href="https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins" rel="noopener noreferrer"&gt;https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins&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%2Fn2b2kf17hbw9c70gpw0v.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%2Fn2b2kf17hbw9c70gpw0v.png" alt="Oh My Zsh terminal" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  3. Ditch Editor Tabs
&lt;/h2&gt;

&lt;p&gt;Stop relying on editor tabs ! They slow you down. Instead, use the "recent files" menu or "search everywhere" function in your editor. You’ll navigate your code much faster.&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%2Ffuju6vqrrdd4bs16xexq.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%2Ffuju6vqrrdd4bs16xexq.png" alt="Recent files feature" width="616" height="230"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  4. Create Your Own Editor Shortcuts
&lt;/h2&gt;

&lt;p&gt;A few years back, I tried to memorize all my IDE shortcuts — bad idea ! Only 20-30 shortcuts were truly useful, and many were awkward combinations, like Shift + Ctrl + Alt + U. I ended up removing all defaults and creating my own custom shortcuts for the most useful commands. Now, each one is mapped to an easy-to-hit key combo. It took a little time to learn, but it was absolutely worth 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%2Fc3erimieqegiobxkyqox.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%2Fc3erimieqegiobxkyqox.png" alt="PHPStorm custom keybindings" width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  5. Navigate Your Browser with Vimium
&lt;/h2&gt;

&lt;p&gt;The Vimium extension (&lt;a href="https://addons.mozilla.org/en-US/firefox/addon/vimium-ff/" rel="noopener noreferrer"&gt;Firefox&lt;/a&gt;|&lt;a href="https://chromewebstore.google.com/detail/vimium/dbepggeogbaibhgnhhndojpepiihcmeb?hl=en" rel="noopener noreferrer"&gt;Chrome&lt;/a&gt;) allows you to use keyboard commands to move around in your browser. Pressing "f" highlights all clickable elements, making it easy to navigate. This is where 10-finger typing comes in handy — you’ll need to know your way around the keyboard to fully enjoy this extension !&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%2Fc6hmrsfc6bh7pvhi7lv5.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%2Fc6hmrsfc6bh7pvhi7lv5.png" alt="Vimium on a browser" width="720" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  6. Remap Arrow Keys
&lt;/h2&gt;

&lt;p&gt;This tip is a game-changer for me. We use arrow keys constantly, so I mapped them directly on my keyboard (I took this idea from Vim). On my French keyboard, I mapped left to “j,” up to “k,” right to “l,” and down to “m” (on Qwerty it would be mapped on "hjkl").  This setup works across all my software (terminal, IDE, browser, etc). My hand stays in place, and I can even make more complex shortcuts. The only trade-off ? I had to sacrifice my “Caps Lock” key, but it’s a small price to pay.&lt;/p&gt;

&lt;p&gt;You're using Ubuntu too ? &lt;a href="https://github.com/etienneleba/configs?tab=readme-ov-file#arrow-keys-to-jklm-on-ubuntu-2404" rel="noopener noreferrer"&gt;Here how I did it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  7. Keep All Your Configs in a Repository
&lt;/h2&gt;

&lt;p&gt;Even if it doesn’t happen often, there are times when you’ll switch computers. To avoid reconfiguring everything, save your custom configs in a GitHub repository. Be sure to include detailed steps in the README; your future self will thank you ! Here’s mine if you want some inspiration: &lt;a href="https://github.com/etienneleba/configs" rel="noopener noreferrer"&gt;etienneleba/configs&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%2Fsb2jadgekssrtvpfn9m3.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%2Fsb2jadgekssrtvpfn9m3.png" alt="EtienneLeba configs repository on Github" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  8. Go Mouse-Free
&lt;/h2&gt;

&lt;p&gt;With these tips, you should be able to work mouse-free. Try it for a week and every time you reach for the mouse, think of a shortcut instead and write it down. By the end of the week, you’ll be surprised at how much faster you’ve become.&lt;/p&gt;

&lt;p&gt;Don’t throw away your mouse, some tasks still need it, but ask yourself every time: “Could a shortcut replace this mouse action?”&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  9. Share Tips with Your Team
&lt;/h2&gt;

&lt;p&gt;Everyone has their own shortcuts and tweaks for faster coding, but we rarely share them. Take a moment to discuss and exchange these tips with your team, colleagues, or community, you’ll all benefit!&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  10. Invest in Yourself
&lt;/h2&gt;

&lt;p&gt;Most of these tips aren’t instant wins, they require time to master. But, I encourage you to make that investment in yourself. These practices will level up your developer game, guaranteed.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;I hope you’ve learned some new tips to speed up your work ! I’m sure I missed some obvious ones, so please drop your favorite shortcuts and productivity hacks in the comments below 👇&lt;/p&gt;

</description>
      <category>programming</category>
      <category>beginners</category>
      <category>productivity</category>
      <category>shortcuts</category>
    </item>
  </channel>
</rss>
