<?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>DEV Community: Giovani Pessoa</title>
    <description>The latest articles on DEV Community by Giovani Pessoa (@giovanipessoa).</description>
    <link>https://dev.to/giovanipessoa</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%2F2902064%2F62be04e6-122e-4af5-946a-cbed8ebb3db1.jpg</url>
      <title>DEV Community: Giovani Pessoa</title>
      <link>https://dev.to/giovanipessoa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/giovanipessoa"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>Giovani Pessoa</dc:creator>
      <pubDate>Tue, 12 Aug 2025 23:53:51 +0000</pubDate>
      <link>https://dev.to/giovanipessoa/-2585</link>
      <guid>https://dev.to/giovanipessoa/-2585</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/giovanipessoa/php-laravel-clean-architecture-e-ddd-4j03" class="crayons-story__hidden-navigation-link"&gt;PHP, Laravel, Clean Architecture and DDD&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/giovanipessoa" class="crayons-avatar  crayons-avatar--l  "&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%2F2902064%2F62be04e6-122e-4af5-946a-cbed8ebb3db1.jpg" alt="giovanipessoa profile" class="crayons-avatar__image" width="800" height="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/giovanipessoa" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Giovani Pessoa
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Giovani Pessoa
                
              
              &lt;div id="story-author-preview-content-2744385" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/giovanipessoa" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2F2902064%2F62be04e6-122e-4af5-946a-cbed8ebb3db1.jpg" class="crayons-avatar__image" alt="" width="800" height="800"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Giovani Pessoa&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/giovanipessoa/php-laravel-clean-architecture-e-ddd-4j03" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 5 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/giovanipessoa/php-laravel-clean-architecture-e-ddd-4j03" id="article-link-2744385"&gt;
          PHP, Laravel, Clean Architecture and DDD
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/giovanipessoa/php-laravel-clean-architecture-e-ddd-4j03" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;15&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/giovanipessoa/php-laravel-clean-architecture-e-ddd-4j03#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              1&lt;span class="hidden s:inline"&gt; comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            11 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>php</category>
      <category>laravel</category>
      <category>architecture</category>
      <category>ddd</category>
    </item>
    <item>
      <title>PHP, Laravel, Clean Architecture and DDD</title>
      <dc:creator>Giovani Pessoa</dc:creator>
      <pubDate>Tue, 05 Aug 2025 23:23:10 +0000</pubDate>
      <link>https://dev.to/giovanipessoa/php-laravel-clean-architecture-e-ddd-4j03</link>
      <guid>https://dev.to/giovanipessoa/php-laravel-clean-architecture-e-ddd-4j03</guid>
      <description>&lt;p&gt;This is a sample project built with Laravel, designed to share knowledge about applying Domain-Driven Design (DDD) and Clean Architecture.&lt;/p&gt;

&lt;p&gt;Domain-Driven Design (DDD) and Clean Architecture are complementary approaches. DDD focuses on modeling the business domain, aiming to establish a common language between developers and domain experts, which facilitates communication and understanding. Clean Architecture seeks to structure the application in layers, with the domain at the core. DDD helps define what the application does (the business logic), while Clean Architecture defines how the application is structured to do it, with a focus on dependency rules.&lt;/p&gt;

&lt;p&gt;Let’s imagine an application for inventory control, where our first task is to register new products.&lt;/p&gt;

&lt;p&gt;Since a domain shouldn't be linked to any specific framework, because one of the principles of Clean Architecture is to keep code portable, we’ll adopt a purist approach. Ideally, the domain layer should be placed in a top-level directory, outside the framework's default structure, such as inside a src/ folder. For example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo81bzdijgyupn3za94s5.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%2Fo81bzdijgyupn3za94s5.jpg" alt="The app/ folder acting as Laravel’s 'interface' to the isolated domain." width="800" height="682"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this way, and within the context of Clean Architecture and DDD, the domain remains completely isolated, and the app/ folder becomes Laravel’s "interface" to your domain. You need to install Laravel first, and then assess what needs to be created or moved.&lt;/p&gt;

&lt;p&gt;What truly matters is the direction of dependencies, not the folder names where files are stored.&lt;/p&gt;

&lt;p&gt;The only layer that should be aware of both your domain and the framework is the Infrastructure layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1&lt;/strong&gt; - Using your preferred IDE, create a new project using Laravel. If you're using VSCODE, open a New Terminal and type:&lt;br&gt;
&lt;code&gt;composer create-project laravel/laravel sample-project&lt;/code&gt;. Don't forget to enter the sample-project folder!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(optional)&lt;/strong&gt; - In the example project, start the Laravel server:&lt;br&gt;
&lt;code&gt;php artisan serve&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2&lt;/strong&gt; - At the root of the project, create a new src/ folder and inside it create the following structure below:&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%2Fg5oixpw48srln4vfrhay.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%2Fg5oixpw48srln4vfrhay.jpg" alt="Plain Old PHP Object (POPO)" width="800" height="683"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Adjust &lt;em&gt;composer.json&lt;/em&gt;, as it need to know where the new classes are located.&lt;br&gt;
Open the &lt;em&gt;composer.json&lt;/em&gt; file and add the src/ folder to autoload.psr-4.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "autoload": {
        "psr-4": {
            "App\\": "app/",
            "AppCore\\": "src/"
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, run &lt;code&gt;composer dump-autoload&lt;/code&gt; to generate a new class map.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3&lt;/strong&gt; - Let's create the &lt;strong&gt;Eloquent Model **for our product. This model, which we will name **EloquentProductModel&lt;/strong&gt;, will serve as the bridge to the database and will be used by our repository for all CRUD operations.&lt;/p&gt;

&lt;p&gt;Run the command &lt;code&gt;php artisan make:model Product -m&lt;/code&gt; because it creates the product model and the associated migration file. Artisan will create the Eloquent Product Model in the app/Models folder as &lt;strong&gt;Product.php&lt;/strong&gt; and the migration file in database/migrations as &lt;strong&gt;YYYY_MM_DD_HHMMSS_create_products_table.php&lt;/strong&gt;, something like this. After that, rename the generated &lt;strong&gt;Product.php&lt;/strong&gt; model to &lt;strong&gt;EloquentProductModel.php&lt;/strong&gt; and move it to the app/Infrastructure/Persistence/Eloquent folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4&lt;/strong&gt; - &lt;a href="https://laravel.com/docs/5.0/eloquent" rel="noopener noreferrer"&gt;Read about Eloquent&lt;/a&gt; and &lt;a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html" rel="noopener noreferrer"&gt;Clean Architecture&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5&lt;/strong&gt; - Customize the migration file created for the Product.&lt;/p&gt;

&lt;p&gt;Open the file "&lt;strong&gt;YYYY_MM_DD_HHMMSS_create_products_table.php&lt;/strong&gt;" in database/migrations and inside the &lt;code&gt;up()&lt;/code&gt; method, add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table-&amp;gt;id();
            $table-&amp;gt;string('name');
            $table-&amp;gt;string('description')-&amp;gt;nullable();
            $table-&amp;gt;decimal('price', 10, 2);
            $table-&amp;gt;integer('stock')-&amp;gt;default(0);
            $table-&amp;gt;boolean('active')-&amp;gt;default(1);
            $table-&amp;gt;timestamps();
        });
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, inside the &lt;code&gt;down()&lt;/code&gt; method, add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    public function down(): void
    {
        Schema::dropIfExists('products');
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6&lt;/strong&gt; - &lt;strong&gt;Important!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before the next step, you need to set the database. You can use MySQL, PostgreSQL, or another non-relational database, such as MongoDB.&lt;/p&gt;

&lt;p&gt;In this case, we're using PostgreSQL through AWS RDS.&lt;br&gt;
You can search for "Using PostgreSQL on AWS RDS" or learn more in our article.&lt;/p&gt;

&lt;p&gt;After getting the host, database, username and password, set these data in the .env file as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DB_CONNECTION=pgsql
DB_HOST=database.xxxxxxxxxxxx.us-east-2.rds.amazonaws.com
DB_PORT=5432 (default)
DB_DATABASE=database
DB_USERNAME=username
DB_PASSWORD=password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the open file and change SESSION_DRIVER to "file." We'll explain more about this later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# SESSION_DRIVER=database
SESSION_DRIVER=file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, run the command &lt;code&gt;php artisan migrate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Up to here, congrats!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7&lt;/strong&gt; - Create and enrich the Product model.&lt;/p&gt;

&lt;p&gt;Entities (Product) contain pure business logic, with no dependencies on Laravel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remember!&lt;/strong&gt; The essence of clean architecture is independence. Your domain entity cannot inherit from any infrastructure class. Your Product class must be a simple POPO (Plain Old PHP Object).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;set validations for the properties&lt;/li&gt;
&lt;li&gt;set domain behavior methods&lt;/li&gt;
&lt;li&gt;set getters to access the properties
&lt;/li&gt;
&lt;/ol&gt;

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

// src/Domain/Entities/Product.php
namespace AppCore\Domain\Entities;

use InvalidArgumentException;

class Product
{
    private ?int $id;
    private string $name;
    private ?string $description;
    private float $price;
    private int $stock;
    private bool $active;

    // set validations for the properties
    public function __construct(
        string $name,
        float $price,
        int $stock = 0,
        ?string $description = null,
        bool $active = true,
        ?int $id = null
    ) {
        if (empty($name)) {
            throw new InvalidArgumentException('Name is required');
        }
        if ($price &amp;lt; 0) {
            throw new InvalidArgumentException('Price must be greater than 0');
        }
        if ($stock &amp;lt; 0) {
            throw new InvalidArgumentException('Stock must be greater than 0');
        }

        $this-&amp;gt;id = $id;
        $this-&amp;gt;name = $name;
        $this-&amp;gt;description = $description;
        $this-&amp;gt;price = $price;
        $this-&amp;gt;stock = $stock;
        $this-&amp;gt;active = $active;
    }

    // set domain behavior methods
    public function decreaseStock(int $quantity): void
    {
        if ($this-&amp;gt;stock &amp;lt; $quantity) {
            throw new InvalidArgumentException('Stock is not enough for the requested quantity');
        }
        $this-&amp;gt;stock -= $quantity;
    }

    public function increaseStock(int $quantity): void
    {
        $this-&amp;gt;stock += $quantity;
    }

    // set getters to access the properties
    public function getId(): ?int
    {
        return $this-&amp;gt;id;
    }

    public function getName(): string
    {
        return $this-&amp;gt;name;
    }

    public function getDescription(): ?string
    {
        return $this-&amp;gt;description;
    }

    public function getPrice(): float
    {
        return $this-&amp;gt;price;
    }

    public function getStock(): int
    {
        return $this-&amp;gt;stock;
    }

    public function isActive(): bool
    {
        return $this-&amp;gt;active;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;8&lt;/strong&gt; - Create the Interface for the Product repository.&lt;/p&gt;

&lt;p&gt;Inside the src/Application/Repositories folder, create a new file &lt;strong&gt;InterfaceProductRepository.php&lt;/strong&gt;. This interface defines how the domain and use cases interact with the outside world.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By convention, it's common to suffix interfaces with "Interface".&lt;br&gt;
&lt;/p&gt;

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

// src/Application/Repositories/InterfaceProductRepository.php
namespace AppCore\Application\Repositories;

use AppCore\Domain\Entities\Product;

interface InterfaceProductRepository
{
    public function save(Product $product): void;
    public function findAll(): array;
    public function findById(int $id): ?Product;
    public function delete(Product $product): void;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;9&lt;/strong&gt; - Create the first use case to register the product.&lt;/p&gt;

&lt;p&gt;Inside the src/Application/UseCases folder, create a new file &lt;strong&gt;RegisterProductUseCase.php&lt;/strong&gt;. This use case will be responsible for creating the new product.&lt;br&gt;
&lt;/p&gt;

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

// src/Application/UseCases/RegisterProductUseCase.php
namespace AppCore\Application\UseCases;

use AppCore\Application\Repositories\InterfaceProductRepository;
use AppCore\Application\DTO\RegisterProductData;
use AppCore\Domain\Entities\Product;

class RegisterProductUseCase
{
    public function __construct(private InterfaceProductRepository $productRepository) {}

    public function execute(RegisterProductData $data): Product
    {
        $product = new Product(
            $data-&amp;gt;name,
            $data-&amp;gt;description,
            $data-&amp;gt;price,
            $data-&amp;gt;stock,
            true
        );

        $this-&amp;gt;productRepository-&amp;gt;save($product);
        return $product;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;10&lt;/strong&gt; - Create the Eloquent for the Product repository.&lt;/p&gt;

&lt;p&gt;Inside the app/Infrastructure/Persistence folder, create a new file named &lt;strong&gt;EloquentProductRepository.php&lt;/strong&gt;. This file will implement all the methods of InterfaceProductRepository.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notes:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By nomenclature, you can start the file name using "Eloquent". Do you know Eloquent? It is an ORM (Object-Relational Mapper) that allows you to interact with your database using object-oriented syntax. It's similar to other ORMs like "Doctrine".&lt;br&gt;
[Read about Eloquent]&lt;/p&gt;

&lt;p&gt;(&lt;a href="https://laravel.com/docs/5.0/eloquent" rel="noopener noreferrer"&gt;https://laravel.com/docs/5.0/eloquent&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Remember that the methods of an Eloquent model can be used to perform operations such as querying data, inserting new data, updating records, deleting records, and establishing relationships with other models. However, in this case, we need to create an Eloquent model for the Product that will serve as a bridge to the database.&lt;/p&gt;

&lt;p&gt;Some methods must receive the POPO domain entity as an argument so that the repository can translate the entity into an Eloquent model.&lt;/p&gt;

&lt;p&gt;You may use a different structure here, but the logic will be the same. The methods used to insert new data and update records, for example, must receive the POPO domain entity as an argument so that the repository can translate the entity.&lt;/p&gt;

&lt;p&gt;Before implementing Eloquent for the Product Repository, let's create Eloquent for the Product model. Inside the app/Infrastructure/Persistence/Eloquent folder, create a new file named &lt;strong&gt;EloquentProductModel.php&lt;/strong&gt; and implements as below:&lt;br&gt;
&lt;/p&gt;

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

// app/Infrastructure/Persistence/Eloquent/EloquentProductModel.php
namespace App\Infrastructure\Persistence\Eloquent;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class EloquentProductModel extends Model
{
    // set the factory to use the model
    use HasFactory;

    // set the table name
    protected $table = 'products';

    // set the fillable fields
    protected $fillable = [
        'name',
        'description',
        'price',
        'stock',
        'active'
    ];
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that we can implement Eloquent for the Product Repository.&lt;br&gt;
&lt;/p&gt;

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

// app/Infrastructure/Persistence/EloquentProductRepository.php
namespace App\Infrastructure\Persistence;

use AppCore\Application\Repositories\InterfaceProductRepository;
use App\Infrastructure\Persistence\Eloquent\EloquentProductModel;
use AppCore\Domain\Entities\Product;

class EloquentProductRepository implements InterfaceProductRepository
{
    public function save(Product $product): void
    {
        // the domain entity is received as an argument
        // the repository translates this entity to an Eloquent model
        $eloquentModel = $product-&amp;gt;getId()
            ? EloquentProductModel::find($product-&amp;gt;getId())
            : new EloquentProductModel();

        $eloquentModel-&amp;gt;name = $product-&amp;gt;getName();
        $eloquentModel-&amp;gt;description = $product-&amp;gt;getDescription();
        $eloquentModel-&amp;gt;price = $product-&amp;gt;getPrice();
        $eloquentModel-&amp;gt;stock = $product-&amp;gt;getStock();
        $eloquentModel-&amp;gt;active = $product-&amp;gt;isActive();

        $eloquentModel-&amp;gt;save();
    }

    public function findAll(): array
    {
        $eloquentModels = EloquentProductModel::all();
        $products = [];

        foreach ($eloquentModels as $eloquentModel) {
            $products[] = new Product(
                $eloquentModel-&amp;gt;name,
                $eloquentModel-&amp;gt;price,
                $eloquentModel-&amp;gt;stock,
                $eloquentModel-&amp;gt;description,
                $eloquentModel-&amp;gt;active,
                $eloquentModel-&amp;gt;id
            );
        }

        return $products;
    }

    public function findById(int $id): ?Product
    {
        $eloquentModel = EloquentProductModel::find($id);

        if (!$eloquentModel) {
            return null;
        }

        return new Product(
            $eloquentModel-&amp;gt;name,
            $eloquentModel-&amp;gt;price,
            $eloquentModel-&amp;gt;stock,
            $eloquentModel-&amp;gt;description,
            $eloquentModel-&amp;gt;active,
            $eloquentModel-&amp;gt;id
        );
    }

    public function delete(Product $product): void
    {
        EloquentProductModel::destroy($product-&amp;gt;getId());
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;11&lt;/strong&gt; - When creating a Laravel project, we noticed that it suggests an &lt;strong&gt;AppServiceProvider.php&lt;/strong&gt; file within app/Providers.&lt;/p&gt;

&lt;p&gt;Remember that we made a small adjustment to the app folder structure to suit the project's needs. As we're adopting a custom structure, we'll need to move the **AppServiceProvider.php **file from its default location (app/Providers) to our new app/Infrastructure/Providers folder. If the original app/Providers folder is now empty, you can safely delete it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important!&lt;/strong&gt; The Laravel Service Container queries the AppServiceProvider and creates the instance of the concrete class. &lt;strong&gt;EloquentProductRepository&lt;/strong&gt; implements the methods of the &lt;strong&gt;InterfaceProductRepository&lt;/strong&gt;, which defines the contract for Product persistence.&lt;br&gt;
&lt;/p&gt;

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

// app/Infrastructure/Providers/AppServiceProvider.php
namespace App\Infrastructure\Providers;

use AppCore\Application\Repositories\InterfaceProductRepository;
use App\Infrastructure\Persistence\EloquentProductRepository;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // bind interface to its concrete implementation
        $this-&amp;gt;app-&amp;gt;bind(InterfaceProductRepository::class, EloquentProductRepository::class);
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        //
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You need to change the class path of the application's service provider. Instead of &lt;strong&gt;App\Providers\AppServiceProvider::class&lt;/strong&gt; to &lt;strong&gt;App\Infrastructure\Providers\AppServiceProvider::class&lt;/strong&gt;. Open the bootstrap/&lt;strong&gt;providers.php&lt;/strong&gt; file and do it.&lt;br&gt;
&lt;/p&gt;

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

return [
    App\Infrastructure\Providers\AppServiceProvider::class,
];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;12&lt;/strong&gt; - Let's use mocks for &lt;strong&gt;InterfaceProductRepository&lt;/strong&gt; in the use case testing units.&lt;/p&gt;

&lt;p&gt;Inside tests folder, create the following structure: tests/Unit/Application/UseCases/RegisterProductUseCaseTest.php.&lt;/p&gt;

&lt;p&gt;Open **RegisterProductUseCaseTest.php **file and implements:&lt;br&gt;
&lt;/p&gt;

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

namespace Tests\Unit\Application\UseCases;

use PHPUnit\Framework\TestCase;
use AppCore\Application\UseCases\RegisterProductUseCase;
use AppCore\Application\Repositories\InterfaceProductRepository;
use AppCore\Domain\Entities\Product;
use Mockery;

class RegisterProductUseCaseTest extends TestCase
{
    /**
     * @var InterfaceProductRepository|Mockery\MockInterface
     */
    protected $productRepositoryMock;

    protected function setUp(): void
    {
        parent::setUp();
        $this-&amp;gt;productRepositoryMock = Mockery::mock(InterfaceProductRepository::class);
    }

    protected function tearDown(): void
    {
        Mockery::close();
        parent::tearDown();
    }

    /**
     * test if the use case saves the product successfully
     *
     * @return void
     */
    public function testExecuteSavesProductSuccessfully()
    {
        $productData = [
            'name' =&amp;gt; 'Caneta',
            'description' =&amp;gt; 'Caneta Azul',
            'price' =&amp;gt; 2.50,
            'stock' =&amp;gt; 10,
        ];

        $this-&amp;gt;productRepositoryMock-&amp;gt;shouldReceive('save')
            -&amp;gt;once()
            -&amp;gt;withArgs(function (Product $product) use ($productData) {
                return $product-&amp;gt;getName() === $productData['name'] &amp;amp;&amp;amp;
                    $product-&amp;gt;getPrice() === $productData['price'];
            });

        $useCase = new RegisterProductUseCase($this-&amp;gt;productRepositoryMock);
        $product = $useCase-&amp;gt;execute($productData);

        $this-&amp;gt;assertInstanceOf(Product::class, $product);
        $this-&amp;gt;assertEquals($productData['name'], $product-&amp;gt;getName());
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Important! In the terminal, run the command &lt;code&gt;./vendor/bin/phpunit tests/Unit/Application/UseCases/RegisterProductUseCaseTest.php&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notes:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The setUp() method runs automatically before each test method in your class. Its main purpose is to prepare the test environment, ensuring that each test starts from scratch, with a clean and consistent state. It creates a mock object of your repository interface.&lt;/p&gt;

&lt;p&gt;The tearDown() method does the opposite of setUp(): it runs automatically after each test method in your class. Its main purpose is to clean up the test environment so that the next test starts in a completely clean state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;13&lt;/strong&gt; - &lt;strong&gt;IMPORTANT!&lt;/strong&gt; FROM HERE, WE'LL WORK WITH THE OUTER LAYER (WE CAN CALL IT "PRESENTATION LAYER"). WE WON'T CREATE A NEW FOLDER NAMED UI, BECAUSE WE HAVE SPECIFIC RESOURCES FOR IT.&lt;/p&gt;

&lt;p&gt;Inside the routes folder, open the &lt;strong&gt;web.php&lt;/strong&gt; file. This file allows us to define all the routes for the web application.&lt;/p&gt;

&lt;p&gt;For our CRUD scenario, the most efficient way to create routes is using &lt;code&gt;Route::resource&lt;/code&gt;. It generates seven default routes for a resource (&lt;em&gt;index&lt;/em&gt;, &lt;em&gt;create&lt;/em&gt;, &lt;em&gt;store&lt;/em&gt;, &lt;em&gt;show&lt;/em&gt;, &lt;em&gt;edit&lt;/em&gt;, &lt;em&gt;update _and _destroy&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;Change its contents to:&lt;br&gt;
&lt;/p&gt;

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

use App\Http\Controllers\ProductController;

use Illuminate\Support\Facades\Route;

Route::resource('/products', ProductController::class);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;14&lt;/strong&gt; - Using Terminal, type &lt;code&gt;php artisan make:controller ProductController --resource&lt;/code&gt; to Laravel server creates a new Controller inside the app/Http/Controllers folder. About the --resource argument, it can create a new Controller with all the standard CRUD methods index(), create(), store(), show(), edit(), update() and destroy().&lt;/p&gt;

&lt;p&gt;See below the ProductController logical usage map based on dependency inversion. The Dependency Inversion Principle (DIP) states that low-level models should depend on high-level models.&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%2Fwnexrnedu6uarfmaedmi.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%2Fwnexrnedu6uarfmaedmi.jpg" alt="logical usage map" width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open the &lt;strong&gt;ProductController.php&lt;/strong&gt; file and implements its structure as below:&lt;br&gt;
&lt;/p&gt;

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

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use AppCore\Application\UseCases\RegisterProductUseCase;

class ProductController extends Controller
{
    /**
     * display a listing of the resource.
     */
    public function index()
    {
        //
    }

    /**
     * show the form for creating a new resource.
     */
    public function create()
    {
        return view('products.Register');
    }

    /**
     * store a newly created resource in storage.
     */
    public function store(Request $request, RegisterProductUseCase $registerProductUseCase)
    {
        try {
            $request-&amp;gt;validate([
                'name' =&amp;gt; 'required|string|max:255',
                'description' =&amp;gt; 'nullable|string',
                'price' =&amp;gt; 'required|numeric|min:0',
                'stock' =&amp;gt; 'required|integer|min:0',
            ], [
                'name.required' =&amp;gt; 'Name is require',
                'price.required' =&amp;gt; 'Price is required',
                'price.numeric' =&amp;gt; 'Price must be a number',
                'stock.required' =&amp;gt; 'Stock is required',
                'stock.integer' =&amp;gt; 'Stock must be an integer',
            ]);

            $registerProductUseCase-&amp;gt;execute($request-&amp;gt;all());

            return redirect()-&amp;gt;route('products.index')
                -&amp;gt;with('success', "Product created successfully!");
        } catch (\Illuminate\Validation\ValidationException $e) {
            return redirect()-&amp;gt;back()
                -&amp;gt;withErrors($e-&amp;gt;errors())
                -&amp;gt;withInput();
        } catch (\InvalidArgumentException $e) {
            // domain's exceptions are handled here
            return redirect()-&amp;gt;back()
                -&amp;gt;withErrors(['general' =&amp;gt; $e-&amp;gt;getMessage()])
                -&amp;gt;withInput();
        } catch (\Exception $e) {
            // this is a generic exception
            return redirect()-&amp;gt;back()
                -&amp;gt;withErrors(['general' =&amp;gt; $e-&amp;gt;getMessage()])
                -&amp;gt;withInput();
        }
    }

    /**
     * display the specified resource.
     */
    public function show(string $id)
    {
        //
    }

    /**
     * show the form for editing the specified resource.
     */
    public function edit(string $id)
    {
        //
    }

    /**
     * update the specified resource in storage.
     */
    public function update(Request $request, string $id)
    {
        //
    }

    /**
     * remove the specified resource from storage.
     */
    public function destroy(string $id)
    {
        //
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Notes:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$request-&amp;gt;validate&lt;/code&gt; acts as a "gatekeeper": if the data matches, it opens the door. Otherwise, it rejects the request and returns it to its origin, with an explanation of why. You might consider using DTOs (Data Transfer Objects) or richer Form Requests to decouple validation logic from the controller and keep it clean.&lt;/p&gt;

&lt;p&gt;The Laravel Service Container injects the &lt;strong&gt;RegisterProductUseCase&lt;/strong&gt; instance into the &lt;strong&gt;ProductController's&lt;/strong&gt; store method.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/giovanipessoa/php-laravel-clear-architecture-ddd" rel="noopener noreferrer"&gt;Download the repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion and next steps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We've reached the end of this guide, and using our sample application, we've demonstrated how DDD and Clean Architecture can be applied to a Laravel project. This approach helps us create a well-organized and maintainable structure. Our code is now more decoupled, business logic is safeguarded within the application's core, and swapping out external components, such as the database, has become far simpler. This enables us to build applications that are more robust, testable, and scalable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deepening the architecture: Additional yips and practices&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The journey doesn't end here. To take your architecture to the next level, consider these practices that complement what we've learned:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing the outer layers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While we focused on unit testing the Application layer (the Use Cases), it's crucial to ensure the outer layers also function as expected. Create integration tests for your Infrastructure layer, especially for the repositories. This guarantees that communication with the database is correct and that the mapping between the domain entity and the Eloquent model works perfectly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The role of mappers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To keep the repository class clean and focused, we can extract object conversion logic into dedicated classes. A Mapper is a class responsible for translating data from one layer to another. For example, a ProductMapper could be used to convert a Product domain entity into an EloquentProductModel (and vice versa). This centralizes the mapping logic, preventing it from being scattered throughout your code and ensuring the repository is responsible solely for coordinating persistence, not for data conversion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The power of the Service Provider&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All the magic of Dependency Injection in our project happens thanks to Laravel's Service Container, orchestrated by the Service Provider. It's the "hero" that resolves dependencies and delivers the concrete class (like EloquentProductRepository) whenever the interface (our ProductRepositoryInterface) is requested. Remember, you can use the bind or singleton methods in the Service Provider to register any service in your application, not just repositories.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next step: Refactoring the Controller&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In our ProductController, data validation is still handled directly within the method. While this approach is functional, in a system that adheres to the single responsibility principle, we can do better! The ideal is for the controller to focus solely on orchestrating the data flow, while the validation logic resides in its own dedicated place.&lt;/p&gt;

&lt;p&gt;In my next article, we will delve deeper into the Presentation layer and refactor our code to use Form Requests and Data Transfer Objects (DTOs). This will decouple the validation process and make our controller even cleaner and more focused on its primary task.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
