<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Lucas Sahdo | Software Engineer]]></title><description><![CDATA[@sahdoio
🚀 Software Engineer with +3 years working abroad 🇺🇸 
⏳ +12 years of experience with IT 
⚡️ I write code in PHP, JS, TS and GO 👇🏻]]></description><link>https://read.sahdo.io</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1762697135393/19b26623-a868-48d4-b011-4f9152cc8496.png</url><title>Lucas Sahdo | Software Engineer</title><link>https://read.sahdo.io</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 25 Apr 2026 00:48:26 GMT</lastBuildDate><atom:link href="https://read.sahdo.io/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Implementing The Repository Pattern - The Right Way]]></title><description><![CDATA[Have you ever encountered someone saying: "Repositories are useless! They're just proxy layers for ORMs"? If you've heard this, know that this person probably never truly understood what this design pattern is for.
In this article, we'll explain end-...]]></description><link>https://read.sahdo.io/implementing-the-repository-pattern-the-right-way</link><guid isPermaLink="true">https://read.sahdo.io/implementing-the-repository-pattern-the-right-way</guid><category><![CDATA[repository]]></category><category><![CDATA[PHP]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[DDD]]></category><dc:creator><![CDATA[Lucas Sahdo]]></dc:creator><pubDate>Fri, 30 Jan 2026 07:15:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736090497823/7f1703c6-f3fe-4b9a-84f4-736c5c90059c.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Have you ever encountered someone saying: "Repositories are useless! They're just proxy layers for ORMs"? If you've heard this, know that this person probably never truly understood what this design pattern is for.</p>
<p>In this article, we'll explain end-to-end the purpose of the Repository pattern, the wrong and right ways to implement it, and most importantly: when to use it.</p>
<p>But before we dive into repositories, we need to understand what a Domain Model is, otherwise the pattern explanation won't make much sense. So, have you heard about Domain Models?</p>
<h2 id="heading-domain-models">Domain Models</h2>
<blockquote>
<p>"A model is a simplified representation of a thing or phenomenon that intentionally emphasizes certain aspects while ignoring others. An abstraction with a specific use in mind."</p>
<p>— Rebecca Wirfs-Brock, quoted in <em>Learning Domain-Driven Design</em>, Vlad Khononov</p>
</blockquote>
<p>The Domain Model is the heart of software in Domain-Driven Design. It represents not just data, but also behaviors and business rules of the domain we're modeling. As Eric Evans describes in his seminal book:</p>
<blockquote>
<p>"The heart of software is its ability to solve domain-related problems for its user. All other features, vital though they may be, support this basic purpose."</p>
<p>— Eric Evans, <em>Domain-Driven Design: Tackling Complexity in the Heart of Software</em></p>
</blockquote>
<p>A Domain Model is composed of several building blocks. Let's understand the main ones:</p>
<h3 id="heading-entities">Entities</h3>
<p>An <strong>Entity</strong> is an object that has a unique identity that persists over time, even when its attributes change. Identity is what defines an Entity, not its values.</p>
<p>Think of it this way: you can change your address, phone number, even your name, but you're still you. Your identity (your social security number, for example) remains the same.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Domain</span>\<span class="hljs-title">Entities</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Customer</span>
</span>{
    <span class="hljs-keyword">public</span> readonly CustomerId $id;
    <span class="hljs-keyword">public</span> readonly \DateTimeImmutable $registeredAt;

    <span class="hljs-keyword">private</span>(set) <span class="hljs-keyword">string</span> $name;
    <span class="hljs-keyword">private</span>(set) Email $email;
    <span class="hljs-keyword">private</span>(set) Address $address;
    <span class="hljs-keyword">private</span>(set) CustomerStatus $status;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> $failedPaymentAttempts = <span class="hljs-number">0</span>;

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">array</span> $domainEvents = [];

    <span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">register</span>(<span class="hljs-params">
        CustomerId $id,
        <span class="hljs-keyword">string</span> $name,
        Email $email,
        Address $address
    </span>): <span class="hljs-title">self</span> </span>{
        $customer = <span class="hljs-keyword">new</span> <span class="hljs-built_in">self</span>($id, $name, $email, $address);

        $customer-&gt;recordEvent(<span class="hljs-keyword">new</span> CustomerRegistered($id, $email));

        <span class="hljs-keyword">return</span> $customer;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">changeEmail</span>(<span class="hljs-params">Email $newEmail</span>): <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-keyword">$this</span>-&gt;ensureIsActive();

        $oldEmail = <span class="hljs-keyword">$this</span>-&gt;email;
        <span class="hljs-keyword">$this</span>-&gt;email = $newEmail;

        <span class="hljs-keyword">$this</span>-&gt;recordEvent(<span class="hljs-keyword">new</span> CustomerEmailChanged(<span class="hljs-keyword">$this</span>-&gt;id, $oldEmail, $newEmail));
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">recordFailedPayment</span>(<span class="hljs-params"></span>): <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-keyword">$this</span>-&gt;failedPaymentAttempts++;

        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">$this</span>-&gt;failedPaymentAttempts &gt;= <span class="hljs-number">3</span>) {
            <span class="hljs-keyword">$this</span>-&gt;suspend(<span class="hljs-string">'Too many failed payment attempts'</span>);
        }
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">suspend</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> $reason</span>): <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">$this</span>-&gt;status-&gt;isSuspended()) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> \<span class="hljs-built_in">DomainException</span>(<span class="hljs-string">'Customer is already suspended'</span>);
        }

        <span class="hljs-keyword">$this</span>-&gt;status = CustomerStatus::suspended();

        <span class="hljs-keyword">$this</span>-&gt;recordEvent(<span class="hljs-keyword">new</span> CustomerSuspended(<span class="hljs-keyword">$this</span>-&gt;id, $reason));
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">reactivate</span>(<span class="hljs-params"></span>): <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">$this</span>-&gt;status-&gt;isSuspended()) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> \<span class="hljs-built_in">DomainException</span>(<span class="hljs-string">'Customer is not suspended'</span>);
        }

        <span class="hljs-keyword">$this</span>-&gt;status = CustomerStatus::active();
        <span class="hljs-keyword">$this</span>-&gt;failedPaymentAttempts = <span class="hljs-number">0</span>;

        <span class="hljs-keyword">$this</span>-&gt;recordEvent(<span class="hljs-keyword">new</span> CustomerReactivated(<span class="hljs-keyword">$this</span>-&gt;id));
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ensureIsActive</span>(<span class="hljs-params"></span>): <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">$this</span>-&gt;status-&gt;isSuspended()) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> \<span class="hljs-built_in">DomainException</span>(<span class="hljs-string">'Cannot perform this action on a suspended customer'</span>);
        }
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">recordEvent</span>(<span class="hljs-params"><span class="hljs-keyword">object</span> $event</span>): <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-keyword">$this</span>-&gt;domainEvents[] = $event;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">pullDomainEvents</span>(<span class="hljs-params"></span>): <span class="hljs-title">array</span>
    </span>{
        $events = <span class="hljs-keyword">$this</span>-&gt;domainEvents;
        <span class="hljs-keyword">$this</span>-&gt;domainEvents = [];
        <span class="hljs-keyword">return</span> $events;
    }
}
</code></pre>
<p>Notice that <code>Customer</code> can change address and email, but the <code>CustomerId</code> remains the same. Two customers with the same name and email, but different IDs, are <strong>different</strong> customers.</p>
<h3 id="heading-value-objects">Value Objects</h3>
<p>Unlike Entities, <strong>Value Objects</strong> are defined by their attributes, not by an identity. They are immutable and compared by value.</p>
<p>Think about money: a $100 bill is equal to any other $100 bill. It doesn't matter which specific bill you have, what matters is the value.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Domain</span>\<span class="hljs-title">ValueObjects</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Money</span>
</span>{
    <span class="hljs-keyword">public</span> readonly <span class="hljs-keyword">float</span> $amount;
    <span class="hljs-keyword">public</span> readonly <span class="hljs-keyword">string</span> $currency;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"><span class="hljs-keyword">float</span> $amount, <span class="hljs-keyword">string</span> $currency</span>)
    </span>{
        <span class="hljs-keyword">if</span> ($amount &lt; <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">InvalidArgumentException</span>(<span class="hljs-string">'Amount cannot be negative'</span>);
        }

        <span class="hljs-keyword">$this</span>-&gt;amount = $amount;
        <span class="hljs-keyword">$this</span>-&gt;currency = $currency;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">add</span>(<span class="hljs-params">Money $other</span>): <span class="hljs-title">Money</span>
    </span>{
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">$this</span>-&gt;currency !== $other-&gt;currency) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">InvalidArgumentException</span>(<span class="hljs-string">'Cannot add different currencies'</span>);
        }

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Money(<span class="hljs-keyword">$this</span>-&gt;amount + $other-&gt;amount, <span class="hljs-keyword">$this</span>-&gt;currency);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">subtract</span>(<span class="hljs-params">Money $other</span>): <span class="hljs-title">Money</span>
    </span>{
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">$this</span>-&gt;currency !== $other-&gt;currency) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">InvalidArgumentException</span>(<span class="hljs-string">'Cannot subtract different currencies'</span>);
        }

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Money(<span class="hljs-keyword">$this</span>-&gt;amount - $other-&gt;amount, <span class="hljs-keyword">$this</span>-&gt;currency);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">equals</span>(<span class="hljs-params">Money $other</span>): <span class="hljs-title">bool</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;amount === $other-&gt;amount 
            &amp;&amp; <span class="hljs-keyword">$this</span>-&gt;currency === $other-&gt;currency;
    }
}
</code></pre>
<p>Another classic example is <code>Address</code>:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Domain</span>\<span class="hljs-title">ValueObjects</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Address</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params">
        <span class="hljs-keyword">public</span> readonly <span class="hljs-keyword">string</span> $street,
        <span class="hljs-keyword">public</span> readonly <span class="hljs-keyword">string</span> $city,
        <span class="hljs-keyword">public</span> readonly <span class="hljs-keyword">string</span> $state,
        <span class="hljs-keyword">public</span> readonly <span class="hljs-keyword">string</span> $zipCode
    </span>) </span>{}

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">equals</span>(<span class="hljs-params">Address $other</span>): <span class="hljs-title">bool</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;street === $other-&gt;street
            &amp;&amp; <span class="hljs-keyword">$this</span>-&gt;city === $other-&gt;city
            &amp;&amp; <span class="hljs-keyword">$this</span>-&gt;state === $other-&gt;state
            &amp;&amp; <span class="hljs-keyword">$this</span>-&gt;zipCode === $other-&gt;zipCode;
    }
}
</code></pre>
<p>Value Objects are immutable. If you need to "change" a Value Object, you create a new one.</p>
<h3 id="heading-aggregates">Aggregates</h3>
<p>Here we arrive at the most important concept for understanding Repositories correctly.</p>
<blockquote>
<p>"An Aggregate is a cluster of domain objects that we treat as a unit for the purpose of data changes."</p>
<p>— Eric Evans, <em>Domain-Driven Design</em></p>
</blockquote>
<p>An Aggregate defines a consistency boundary. It groups Entities and Value Objects that need to be modified together to keep business rules consistent.</p>
<p>Every Aggregate has an <strong>Aggregate Root</strong>: the main Entity through which all external access must pass. External objects can only reference the Aggregate Root, never the internal entities directly.</p>
<p>Vaughn Vernon, in the red book (<em>Implementing Domain-Driven Design</em>), reinforces:</p>
<blockquote>
<p>"Prefer references to external Aggregates only by their globally unique identity, not by holding a direct object reference (or 'pointer')."</p>
</blockquote>
<h3 id="heading-practical-example-order"><strong>Practical Example: Order</strong></h3>
<p>Let's use a classic e-commerce example: an order with its items.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Domain</span>\<span class="hljs-title">Entities</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Domain</span>\<span class="hljs-title">ValueObjects</span>\<span class="hljs-title">Money</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Domain</span>\<span class="hljs-title">ValueObjects</span>\<span class="hljs-title">OrderId</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Domain</span>\<span class="hljs-title">ValueObjects</span>\<span class="hljs-title">CustomerId</span>;

<span class="hljs-comment">// Aggregate Root</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Order</span>
</span>{
    <span class="hljs-keyword">public</span> readonly OrderId $id;
    <span class="hljs-keyword">public</span> readonly CustomerId $customerId;
    <span class="hljs-keyword">public</span> readonly \DateTimeImmutable $createdAt;

    <span class="hljs-keyword">private</span>(set) <span class="hljs-keyword">string</span> $status;
    <span class="hljs-keyword">private</span>(set) Money $totalAmount;

    <span class="hljs-comment">/** <span class="hljs-doctag">@var</span> OrderItem[] */</span>
    <span class="hljs-keyword">private</span>(set) <span class="hljs-keyword">array</span> $items = [];

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params">OrderId $id, CustomerId $customerId</span>)
    </span>{
        <span class="hljs-keyword">$this</span>-&gt;id = $id;
        <span class="hljs-keyword">$this</span>-&gt;customerId = $customerId;
        <span class="hljs-keyword">$this</span>-&gt;status = <span class="hljs-string">'pending'</span>;
        <span class="hljs-keyword">$this</span>-&gt;totalAmount = <span class="hljs-keyword">new</span> Money(<span class="hljs-number">0</span>, <span class="hljs-string">'USD'</span>);
        <span class="hljs-keyword">$this</span>-&gt;createdAt = <span class="hljs-keyword">new</span> \DateTimeImmutable();
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addItem</span>(<span class="hljs-params">
        <span class="hljs-keyword">string</span> $productId,
        <span class="hljs-keyword">string</span> $productName,
        Money $unitPrice,
        <span class="hljs-keyword">int</span> $quantity
    </span>): <span class="hljs-title">void</span> </span>{
        <span class="hljs-keyword">$this</span>-&gt;ensurePending();

        <span class="hljs-keyword">$this</span>-&gt;items[] = <span class="hljs-keyword">new</span> OrderItem(
            productId: $productId,
            productName: $productName,
            unitPrice: $unitPrice,
            quantity: $quantity
        );

        <span class="hljs-keyword">$this</span>-&gt;recalculateTotal();
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">removeItem</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> $productId</span>): <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-keyword">$this</span>-&gt;ensurePending();

        <span class="hljs-keyword">$this</span>-&gt;items = array_values(array_filter(
            <span class="hljs-keyword">$this</span>-&gt;items,
            <span class="hljs-function"><span class="hljs-keyword">fn</span>(<span class="hljs-params">OrderItem $item</span>) =&gt; $<span class="hljs-title">item</span>-&gt;<span class="hljs-title">productId</span> !== $<span class="hljs-title">productId</span>
        ))</span>;

        <span class="hljs-keyword">$this</span>-&gt;recalculateTotal();
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">confirm</span>(<span class="hljs-params"></span>): <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">empty</span>(<span class="hljs-keyword">$this</span>-&gt;items)) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> \<span class="hljs-built_in">DomainException</span>(<span class="hljs-string">'Cannot confirm an empty order'</span>);
        }

        <span class="hljs-keyword">$this</span>-&gt;status = <span class="hljs-string">'confirmed'</span>;
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ensurePending</span>(<span class="hljs-params"></span>): <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">$this</span>-&gt;status !== <span class="hljs-string">'pending'</span>) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> \<span class="hljs-built_in">DomainException</span>(<span class="hljs-string">'Cannot modify a confirmed order'</span>);
        }
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">recalculateTotal</span>(<span class="hljs-params"></span>): <span class="hljs-title">void</span>
    </span>{
        $total = <span class="hljs-keyword">new</span> Money(<span class="hljs-number">0</span>, <span class="hljs-string">'USD'</span>);

        <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">$this</span>-&gt;items <span class="hljs-keyword">as</span> $item) {
            $total = $total-&gt;add($item-&gt;subtotal);
        }

        <span class="hljs-keyword">$this</span>-&gt;totalAmount = $total;
    }
}
</code></pre>
<p>And the <code>OrderItem</code> (an Entity internal to the Aggregate):</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Domain</span>\<span class="hljs-title">Entities</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Domain</span>\<span class="hljs-title">ValueObjects</span>\<span class="hljs-title">Money</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderItem</span>
</span>{
    <span class="hljs-keyword">public</span> readonly <span class="hljs-keyword">string</span> $productId;
    <span class="hljs-keyword">public</span> readonly <span class="hljs-keyword">string</span> $productName;
    <span class="hljs-keyword">public</span> readonly Money $unitPrice;

    <span class="hljs-keyword">private</span>(set) <span class="hljs-keyword">int</span> $quantity;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params">
        <span class="hljs-keyword">string</span> $productId,
        <span class="hljs-keyword">string</span> $productName,
        Money $unitPrice,
        <span class="hljs-keyword">int</span> $quantity
    </span>) </span>{
        <span class="hljs-keyword">if</span> ($quantity &lt;= <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> \<span class="hljs-built_in">InvalidArgumentException</span>(<span class="hljs-string">'Quantity must be positive'</span>);
        }

        <span class="hljs-keyword">$this</span>-&gt;productId = $productId;
        <span class="hljs-keyword">$this</span>-&gt;productName = $productName;
        <span class="hljs-keyword">$this</span>-&gt;unitPrice = $unitPrice;
        <span class="hljs-keyword">$this</span>-&gt;quantity = $quantity;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateQuantity</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $quantity</span>): <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-keyword">if</span> ($quantity &lt;= <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> \<span class="hljs-built_in">InvalidArgumentException</span>(<span class="hljs-string">'Quantity must be positive'</span>);
        }

        <span class="hljs-keyword">$this</span>-&gt;quantity = $quantity;
    }

    <span class="hljs-keyword">public</span> Money $subtotal {
        get =&gt; <span class="hljs-keyword">new</span> Money(
            <span class="hljs-keyword">$this</span>-&gt;unitPrice-&gt;amount * <span class="hljs-keyword">$this</span>-&gt;quantity,
            <span class="hljs-keyword">$this</span>-&gt;unitPrice-&gt;currency
        );
    }
}
</code></pre>
<p><strong>Important points:</strong></p>
<ol>
<li><p><code>Order</code> is the Aggregate Root</p>
</li>
<li><p><code>OrderItem</code> can only be accessed through <code>Order</code></p>
</li>
<li><p>All invariants (business rules) are protected by the Aggregate Root</p>
</li>
<li><p>The Aggregate guarantees transactional consistency</p>
</li>
</ol>
<p>Notice that we don't have an <code>OrderItemRepository</code>. Order items are always manipulated through <code>Order</code>.</p>
<h2 id="heading-dao-vs-repository">DAO vs Repository</h2>
<p>Now that we understand Domain Models and Aggregates, we can finally understand the difference between DAO and Repository. This is the part that most developers confuse.</p>
<h3 id="heading-dao-data-access-object">DAO (Data Access Object)</h3>
<p>The DAO pattern emerged in the context of J2EE applications, documented in the book <em>Core J2EE Patterns</em> (Alur, Crupi, and Malks, 2001). It's a <strong>data-oriented</strong> pattern.</p>
<p>A DAO is an abstraction <strong>close to the database</strong>. It encapsulates access to a specific data source (usually a table) and provides CRUD operations.</p>
<p><strong>DAO Characteristics:</strong></p>
<ul>
<li><p><strong>Table-centric</strong>: Usually one DAO per table</p>
</li>
<li><p><strong>Database-oriented</strong>: Methods reflect database operations (insert, update, delete, find)</p>
</li>
<li><p><strong>Lower-level</strong>: Closer to infrastructure</p>
</li>
<li><p><strong>Data-focused</strong>: Works with data, not domain behavior</p>
</li>
</ul>
<p><strong>Note:</strong> The following examples use Laravel with Eloquent ORM to demonstrate the concepts in a familiar context for PHP developers.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Infrastructure</span>\<span class="hljs-title">DAO</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>\<span class="hljs-title">Order</span> <span class="hljs-title">as</span> <span class="hljs-title">OrderModel</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Collection</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderDAO</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">insert</span>(<span class="hljs-params"><span class="hljs-keyword">array</span> $data</span>): <span class="hljs-title">int</span>
    </span>{
        $order = OrderModel::create($data);
        <span class="hljs-keyword">return</span> $order-&gt;id;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">update</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $id, <span class="hljs-keyword">array</span> $data</span>): <span class="hljs-title">bool</span>
    </span>{
        <span class="hljs-keyword">return</span> OrderModel::where(<span class="hljs-string">'id'</span>, $id)-&gt;update($data) &gt; <span class="hljs-number">0</span>;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">delete</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $id</span>): <span class="hljs-title">bool</span>
    </span>{
        <span class="hljs-keyword">return</span> OrderModel::destroy($id) &gt; <span class="hljs-number">0</span>;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">findById</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $id</span>): ?<span class="hljs-title">array</span>
    </span>{
        $order = OrderModel::find($id);
        <span class="hljs-keyword">return</span> $order?-&gt;toArray();
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">findByCustomerId</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $customerId</span>): <span class="hljs-title">array</span>
    </span>{
        <span class="hljs-keyword">return</span> OrderModel::where(<span class="hljs-string">'customer_id'</span>, $customerId)
            -&gt;get()
            -&gt;toArray();
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">findByStatus</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> $status</span>): <span class="hljs-title">array</span>
    </span>{
        <span class="hljs-keyword">return</span> OrderModel::where(<span class="hljs-string">'status'</span>, $status)
            -&gt;get()
            -&gt;toArray();
    }
}
</code></pre>
<p>And a separate DAO for the items:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Infrastructure</span>\<span class="hljs-title">DAO</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>\<span class="hljs-title">OrderItem</span> <span class="hljs-title">as</span> <span class="hljs-title">OrderItemModel</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderItemDAO</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">insert</span>(<span class="hljs-params"><span class="hljs-keyword">array</span> $data</span>): <span class="hljs-title">int</span>
    </span>{
        $item = OrderItemModel::create($data);
        <span class="hljs-keyword">return</span> $item-&gt;id;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deleteByOrderId</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $orderId</span>): <span class="hljs-title">int</span>
    </span>{
        <span class="hljs-keyword">return</span> OrderItemModel::where(<span class="hljs-string">'order_id'</span>, $orderId)-&gt;delete();
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">findByOrderId</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $orderId</span>): <span class="hljs-title">array</span>
    </span>{
        <span class="hljs-keyword">return</span> OrderItemModel::where(<span class="hljs-string">'order_id'</span>, $orderId)
            -&gt;get()
            -&gt;toArray();
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">update</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $id, <span class="hljs-keyword">array</span> $data</span>): <span class="hljs-title">bool</span>
    </span>{
        <span class="hljs-keyword">return</span> OrderItemModel::where(<span class="hljs-string">'id'</span>, $id)-&gt;update($data) &gt; <span class="hljs-number">0</span>;
    }
}
</code></pre>
<p><strong>Notice:</strong> DAOs work with arrays, not domain objects. They are agnostic to the domain model. Each DAO corresponds to a single table.</p>
<h3 id="heading-repository">Repository</h3>
<p>The Repository pattern, on the other hand, emerged in the context of Domain-Driven Design, described by Eric Evans. It's a <strong>domain-oriented</strong> pattern.</p>
<blockquote>
<p>"A Repository represents all objects of a certain type as a conceptual set... It acts like an in-memory collection of domain objects."</p>
<p>— Eric Evans, <em>Domain-Driven Design</em></p>
</blockquote>
<p><strong>Repository Characteristics:</strong></p>
<ul>
<li><p><strong>Aggregate-centric</strong>: One Repository per Aggregate Root</p>
</li>
<li><p><strong>Domain-oriented</strong>: Methods speak the domain language</p>
</li>
<li><p><strong>Higher-level</strong>: Abstraction over data access</p>
</li>
<li><p><strong>Collection-like</strong>: Simulates an in-memory collection</p>
</li>
<li><p><strong>Encapsulates complexity</strong>: Hides how data is mapped and persisted</p>
</li>
</ul>
<h3 id="heading-repository-in-domain-driven-design-context">Repository in Domain Driven Design Context</h3>
<p>In Domain-Driven Design, there's a clear separation between layers:</p>
<ul>
<li><p><strong>Domain Layer</strong>: Contains Entities, Value Objects, Aggregates, Domain Services, and <strong>Repository Interfaces</strong></p>
</li>
<li><p><strong>Infrastructure Layer</strong>: Contains <strong>Repository Implementations</strong>, ORM configurations, external service integrations</p>
</li>
</ul>
<p>The Repository <strong>interface</strong> belongs to the Domain layer because it's part of the domain's vocabulary. The domain knows it needs to "save an Order" or "find Orders by customer", but it doesn't care how that's done.</p>
<p>The Repository <strong>implementation</strong> belongs to the Infrastructure layer because it deals with technical details like Eloquent, database connections, and query building.</p>
<pre><code class="lang-php">app/
├── Domain/
│   ├── Aggregates/
│   │   └── Order.php
│   ├── Entities/
│   │   └── OrderItem.php
│   ├── ValueObjects/
│   │   ├── OrderId.php
│   │   ├── CustomerId.php
│   │   └── Money.php
│   └── Repositories/
│       └── OrderRepositoryInterface.php  &lt;-- <span class="hljs-class"><span class="hljs-keyword">Interface</span> (<span class="hljs-title">Domain</span> <span class="hljs-title">Layer</span>)
│
└── <span class="hljs-title">Infrastructure</span>/
    └── <span class="hljs-title">Repositories</span>/
        └── <span class="hljs-title">EloquentOrderRepository</span>.<span class="hljs-title">php</span>   &lt;-- <span class="hljs-title">Implementation</span> (<span class="hljs-title">Infrastructure</span> <span class="hljs-title">Layer</span>)</span>
</code></pre>
<p>This separation allows you to:</p>
<ol>
<li><p>Test domain logic without database dependencies</p>
</li>
<li><p>Swap implementations without changing domain code</p>
</li>
<li><p>Keep domain code clean and focused on business rules</p>
</li>
</ol>
<h3 id="heading-repository-interface-domain-layer">Repository Interface (Domain Layer)</h3>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Domain</span>\<span class="hljs-title">Repositories</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Domain</span>\<span class="hljs-title">Aggregates</span>\<span class="hljs-title">Order</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Domain</span>\<span class="hljs-title">ValueObjects</span>\<span class="hljs-title">OrderId</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Domain</span>\<span class="hljs-title">ValueObjects</span>\<span class="hljs-title">CustomerId</span>;

<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">OrderRepositoryInterface</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">save</span>(<span class="hljs-params">Order $order</span>): <span class="hljs-title">void</span></span>;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">findById</span>(<span class="hljs-params">OrderId $id</span>): ?<span class="hljs-title">Order</span></span>;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">findByCustomer</span>(<span class="hljs-params">CustomerId $customerId</span>): <span class="hljs-title">array</span></span>;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">findPendingOrders</span>(<span class="hljs-params"></span>): <span class="hljs-title">array</span></span>;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">delete</span>(<span class="hljs-params">Order $order</span>): <span class="hljs-title">void</span></span>;
}
</code></pre>
<h3 id="heading-repository-implementation-infrastructure-layer">Repository Implementation (Infrastructure Layer)</h3>
<p>Now the implementation using Eloquent directly (without DAO):</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Infrastructure</span>\<span class="hljs-title">Repositories</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Domain</span>\<span class="hljs-title">Aggregates</span>\<span class="hljs-title">Order</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Domain</span>\<span class="hljs-title">Entities</span>\<span class="hljs-title">OrderItem</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Domain</span>\<span class="hljs-title">Repositories</span>\<span class="hljs-title">OrderRepositoryInterface</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Domain</span>\<span class="hljs-title">ValueObjects</span>\<span class="hljs-title">CustomerId</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Domain</span>\<span class="hljs-title">ValueObjects</span>\<span class="hljs-title">Money</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Domain</span>\<span class="hljs-title">ValueObjects</span>\<span class="hljs-title">OrderId</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>\<span class="hljs-title">Order</span> <span class="hljs-title">as</span> <span class="hljs-title">OrderModel</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>\<span class="hljs-title">OrderItem</span> <span class="hljs-title">as</span> <span class="hljs-title">OrderItemModel</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">DB</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EloquentOrderRepository</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">OrderRepositoryInterface</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">save</span>(<span class="hljs-params">Order $order</span>): <span class="hljs-title">void</span>
    </span>{
        DB::transaction(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) <span class="hljs-title">use</span> (<span class="hljs-params">$order</span>) </span>{
            <span class="hljs-comment">// Upsert the order</span>
            OrderModel::updateOrCreate(
                [<span class="hljs-string">'id'</span> =&gt; $order-&gt;getId()-&gt;getValue()],
                [
                    <span class="hljs-string">'customer_id'</span> =&gt; $order-&gt;getCustomerId()-&gt;getValue(),
                    <span class="hljs-string">'status'</span> =&gt; $order-&gt;getStatus(),
                    <span class="hljs-string">'total_amount'</span> =&gt; $order-&gt;getTotalAmount()-&gt;getAmount(),
                    <span class="hljs-string">'currency'</span> =&gt; $order-&gt;getTotalAmount()-&gt;getCurrency(),
                    <span class="hljs-string">'created_at'</span> =&gt; $order-&gt;getCreatedAt(),
                ]
            );

            <span class="hljs-comment">// Remove old items and insert new ones</span>
            OrderItemModel::where(<span class="hljs-string">'order_id'</span>, $order-&gt;getId()-&gt;getValue())-&gt;delete();

            <span class="hljs-keyword">foreach</span> ($order-&gt;getItems() <span class="hljs-keyword">as</span> $item) {
                OrderItemModel::create([
                    <span class="hljs-string">'order_id'</span> =&gt; $order-&gt;getId()-&gt;getValue(),
                    <span class="hljs-string">'product_id'</span> =&gt; $item-&gt;getProductId(),
                    <span class="hljs-string">'product_name'</span> =&gt; $item-&gt;getProductName(),
                    <span class="hljs-string">'unit_price'</span> =&gt; $item-&gt;getUnitPrice()-&gt;getAmount(),
                    <span class="hljs-string">'currency'</span> =&gt; $item-&gt;getUnitPrice()-&gt;getCurrency(),
                    <span class="hljs-string">'quantity'</span> =&gt; $item-&gt;getQuantity(),
                ]);
            }
        });
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">findById</span>(<span class="hljs-params">OrderId $id</span>): ?<span class="hljs-title">Order</span>
    </span>{
        $orderModel = OrderModel::with(<span class="hljs-string">'items'</span>)-&gt;find($id-&gt;getValue());

        <span class="hljs-keyword">if</span> (!$orderModel) {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
        }

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;toDomainEntity($orderModel);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">findByCustomer</span>(<span class="hljs-params">CustomerId $customerId</span>): <span class="hljs-title">array</span>
    </span>{
        $orders = OrderModel::with(<span class="hljs-string">'items'</span>)
            -&gt;where(<span class="hljs-string">'customer_id'</span>, $customerId-&gt;getValue())
            -&gt;get();

        <span class="hljs-keyword">return</span> $orders-&gt;map(<span class="hljs-function"><span class="hljs-keyword">fn</span>(<span class="hljs-params">$model</span>) =&gt; $<span class="hljs-title">this</span>-&gt;<span class="hljs-title">toDomainEntity</span>(<span class="hljs-params">$model</span>))-&gt;<span class="hljs-title">all</span>(<span class="hljs-params"></span>)</span>;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">findPendingOrders</span>(<span class="hljs-params"></span>): <span class="hljs-title">array</span>
    </span>{
        $orders = OrderModel::with(<span class="hljs-string">'items'</span>)
            -&gt;where(<span class="hljs-string">'status'</span>, <span class="hljs-string">'pending'</span>)
            -&gt;get();

        <span class="hljs-keyword">return</span> $orders-&gt;map(<span class="hljs-function"><span class="hljs-keyword">fn</span>(<span class="hljs-params">$model</span>) =&gt; $<span class="hljs-title">this</span>-&gt;<span class="hljs-title">toDomainEntity</span>(<span class="hljs-params">$model</span>))-&gt;<span class="hljs-title">all</span>(<span class="hljs-params"></span>)</span>;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">delete</span>(<span class="hljs-params">Order $order</span>): <span class="hljs-title">void</span>
    </span>{
        DB::transaction(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) <span class="hljs-title">use</span> (<span class="hljs-params">$order</span>) </span>{
            OrderItemModel::where(<span class="hljs-string">'order_id'</span>, $order-&gt;getId()-&gt;getValue())-&gt;delete();
            OrderModel::destroy($order-&gt;getId()-&gt;getValue());
        });
    }

    <span class="hljs-comment">/**
     * Reconstitutes a domain Order aggregate from an Eloquent model.
     */</span>
    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">toDomainEntity</span>(<span class="hljs-params">OrderModel $model</span>): <span class="hljs-title">Order</span>
    </span>{
        $order = <span class="hljs-keyword">new</span> Order(
            <span class="hljs-keyword">new</span> OrderId($model-&gt;id),
            <span class="hljs-keyword">new</span> CustomerId($model-&gt;customer_id)
        );

        <span class="hljs-comment">// Add items to the order</span>
        <span class="hljs-keyword">foreach</span> ($model-&gt;items <span class="hljs-keyword">as</span> $itemModel) {
            $order-&gt;addItem(
                $itemModel-&gt;product_id,
                $itemModel-&gt;product_name,
                <span class="hljs-keyword">new</span> Money($itemModel-&gt;unit_price, $itemModel-&gt;currency),
                $itemModel-&gt;quantity
            );
        }

        <span class="hljs-comment">// Restore the status using reflection (since status might not be 'pending')</span>
        <span class="hljs-keyword">if</span> ($model-&gt;status !== <span class="hljs-string">'pending'</span>) {
            <span class="hljs-keyword">$this</span>-&gt;setPrivateProperty($order, <span class="hljs-string">'status'</span>, $model-&gt;status);
        }

        <span class="hljs-comment">// Restore the original created_at</span>
        <span class="hljs-keyword">$this</span>-&gt;setPrivateProperty($order, <span class="hljs-string">'createdAt'</span>, <span class="hljs-keyword">new</span> \DateTimeImmutable($model-&gt;created_at));

        <span class="hljs-keyword">return</span> $order;
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setPrivateProperty</span>(<span class="hljs-params"><span class="hljs-keyword">object</span> $object, <span class="hljs-keyword">string</span> $property, mixed $value</span>): <span class="hljs-title">void</span>
    </span>{
        $reflection = <span class="hljs-keyword">new</span> \ReflectionProperty($object::class, $property);
        $reflection-&gt;setAccessible(<span class="hljs-literal">true</span>);
        $reflection-&gt;setValue($object, $value);
    }
}
</code></pre>
<p><strong>Key points:</strong></p>
<ol>
<li><p>The Repository receives and returns <strong>complete Aggregates</strong> (Order with its Items)</p>
</li>
<li><p>The Repository handles multiple tables internally (orders + order_items)</p>
</li>
<li><p>The interface speaks the domain language (<code>findPendingOrders</code>, not <code>findByStatus</code>)</p>
</li>
<li><p>The mapping between tables and domain objects is encapsulated</p>
</li>
<li><p>Eloquent models are <strong>infrastructure details</strong>, not domain objects</p>
</li>
<li><p>The domain <code>Order</code> class has behavior; the Eloquent <code>Order</code> model is just for persistence</p>
</li>
</ol>
<h2 id="heading-comparison-table-dao-vs-repository">Comparison Table: DAO vs Repository</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Aspect</td><td>DAO</td><td>Repository</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Focus</strong></td><td>Data / Tables</td><td>Domain / Aggregates</td></tr>
<tr>
<td><strong>Granularity</strong></td><td>One per table</td><td>One per Aggregate Root</td></tr>
<tr>
<td><strong>Language</strong></td><td>Database terms (insert, update)</td><td>Domain terms (save, findPending)</td></tr>
<tr>
<td><strong>Return Type</strong></td><td>Arrays or DTOs</td><td>Domain objects (Aggregates)</td></tr>
<tr>
<td><strong>Abstraction</strong></td><td>Low-level</td><td>High-level</td></tr>
<tr>
<td><strong>Origin</strong></td><td>J2EE Patterns</td><td>Domain-Driven Design</td></tr>
<tr>
<td><strong>Use Case</strong></td><td>Simple CRUD</td><td>Complex domains</td></tr>
<tr>
<td><strong>Layer</strong></td><td>Infrastructure only</td><td>Interface in Domain, Implementation in Infrastructure</td></tr>
</tbody>
</table>
</div><h2 id="heading-repository-works-better-with-a-domain-model">Repository Works Better with a Domain Model</h2>
<p>The main point is: <strong>Repositories make sense when you have a rich Domain Model</strong>.</p>
<p>A Repository receives an Aggregate ID and returns a complete, hydrated Aggregate. It's the layer that translates between two worlds: the domain world (with Aggregates, Entities, and Value Objects) and the database world (with tables and relationships).</p>
<p><strong>One Aggregate can persist data across multiple tables.</strong></p>
<p>In our <code>Order</code> example:</p>
<ul>
<li><p>The domain has: 1 Aggregate (Order with OrderItems)</p>
</li>
<li><p>The database has: 2 tables (orders and order_items)</p>
</li>
</ul>
<p>This is a 1:2 relationship between Aggregate and tables.</p>
<p>We can have other proportions:</p>
<ul>
<li><p>1:1 - A simple Aggregate in one table</p>
</li>
<li><p>1:3 - A complex Aggregate distributed across three tables</p>
</li>
<li><p>1:N - Aggregates with many internal entities</p>
</li>
</ul>
<p>The Repository hides all this complexity from the rest of the application.</p>
<h2 id="heading-repos-without-a-domain-model-are-daos">Repos Without a Domain Model are DAOs?</h2>
<p>If you don't have a rich Domain Model, if your "models" are just DTOs or anemic objects without behavior, then yes, <strong>your Repository is basically a “glorified” DAO</strong>.</p>
<p>And that's not necessarily wrong! Not every application needs DDD. Simple CRUD applications can work perfectly well with Active Record or DAOs.</p>
<p>The problem is when you use the "Repository" nomenclature but implement a DAO, losing all the benefits of the pattern.</p>
<h2 id="heading-are-laravel-eloquent-models-daos">Are Laravel Eloquent Models DAOs?</h2>
<p>This is a very common question in the Laravel ecosystem. The short answer: <strong>Eloquent Models implement the Active Record pattern, not DAO nor Repository</strong>.</p>
<p>The Active Record pattern, described by Martin Fowler in <em>Patterns of Enterprise Application Architecture</em>, combines data and persistence behavior in the same object. Each Model represents a row in the database and knows how to save and load itself.</p>
<pre><code class="lang-php"><span class="hljs-comment">// Active Record - the model knows about persistence</span>
$user = <span class="hljs-keyword">new</span> User();
$user-&gt;name = <span class="hljs-string">'John'</span>;
$user-&gt;save(); <span class="hljs-comment">// The model itself knows how to save</span>
</code></pre>
<p><strong>Active Record is different from both DAO and Repository:</strong></p>
<ul>
<li><p><strong>DAO</strong>: Separate object that manages data access for a table</p>
</li>
<li><p><strong>Repository</strong>: Collection abstraction for Aggregate Roots</p>
</li>
<li><p><strong>Active Record</strong>: The data object itself knows how to persist</p>
</li>
</ul>
<p><strong>Problems with using Active Record as Repository:</strong></p>
<p>Active Record couples your domain model to database infrastructure. This goes against the Repository principle of isolating the domain from persistence details.</p>
<pre><code class="lang-php"><span class="hljs-comment">// This is NOT a real Repository - it's just a wrapper</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserRepository</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">find</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $id</span>): <span class="hljs-title">User</span>
    </span>{
        <span class="hljs-keyword">return</span> User::find($id); <span class="hljs-comment">// Still using Active Record</span>
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">save</span>(<span class="hljs-params">User $user</span>): <span class="hljs-title">void</span>
    </span>{
        $user-&gt;save(); <span class="hljs-comment">// The model still knows about persistence</span>
    }
}
</code></pre>
<p>This "Repository" doesn't provide real abstraction. The <code>User</code> is still an Eloquent Model that knows about the database.</p>
<p><strong>When is this acceptable?</strong></p>
<p>For most Laravel applications, using Eloquent directly is perfectly valid. Active Record is excellent for RAD (Rapid Application Development) and CRUD applications.</p>
<p><strong>When should you consider "real" Repository?</strong></p>
<ul>
<li><p>When you have complex domain logic that needs to be tested in isolation</p>
</li>
<li><p>When you want to swap persistence mechanisms (rare in practice)</p>
</li>
<li><p>When you're strictly following DDD</p>
</li>
</ul>
<h2 id="heading-what-i-see-out-there">What I See Out There</h2>
<p>Let me be honest about what I see in the real world:</p>
<h3 id="heading-1-proxy-repos-with-eloquent">1. Proxy Repos with Eloquent</h3>
<pre><code class="lang-php"><span class="hljs-comment">// This is very common and... questionable</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserRepository</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">all</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> User::all();
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">find</span>(<span class="hljs-params">$id</span>)
    </span>{
        <span class="hljs-keyword">return</span> User::find($id);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">create</span>(<span class="hljs-params"><span class="hljs-keyword">array</span> $data</span>)
    </span>{
        <span class="hljs-keyword">return</span> User::create($data);
    }
}
</code></pre>
<p>This is not a Repository, it's a proxy that adds no value. The Eloquent Model already does all of this.</p>
<h3 id="heading-2-one-repo-per-table">2. One Repo per Table</h3>
<pre><code class="lang-php"><span class="hljs-comment">// Too many repos, following DAO logic</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderRepository</span> </span>{ }
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderItemRepository</span> </span>{ }
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderStatusHistoryRepository</span> </span>{ }
</code></pre>
<p>If <code>OrderItem</code> and <code>OrderStatusHistory</code> only exist in the context of an <code>Order</code>, you don't need separate repositories for them. The <code>OrderRepository</code> <strong>should manage the complete Aggregate</strong>.</p>
<h3 id="heading-3-generic-interface-for-all">3. Generic Interface for All</h3>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">RepositoryInterface</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">all</span>(<span class="hljs-params"></span>)</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">find</span>(<span class="hljs-params">$id</span>)</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">create</span>(<span class="hljs-params"><span class="hljs-keyword">array</span> $data</span>)</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">update</span>(<span class="hljs-params">$id, <span class="hljs-keyword">array</span> $data</span>)</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">delete</span>(<span class="hljs-params">$id</span>)</span>;
}
</code></pre>
<p>This turns all Repositories into DAOs. Each Repository should have its own interface with domain-specific methods.</p>
<h2 id="heading-base-repository-is-it-worth-it">Base Repository: Is It Worth It?</h2>
<p>A common question: is it correct to have a Base Repository for CRUD operations and leave specific repositories only for extra queries?</p>
<pre><code class="lang-php"><span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BaseRepository</span>
</span>{
    <span class="hljs-keyword">protected</span> $model;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">find</span>(<span class="hljs-params">$id</span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;model-&gt;find($id);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">save</span>(<span class="hljs-params">$entity</span>): <span class="hljs-title">void</span>
    </span>{
        $entity-&gt;save();
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">delete</span>(<span class="hljs-params">$entity</span>): <span class="hljs-title">void</span>
    </span>{
        $entity-&gt;delete();
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderRepository</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">BaseRepository</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">$this</span>-&gt;model = <span class="hljs-keyword">new</span> Order();
    }

    <span class="hljs-comment">// Domain-specific methods</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">findPendingByCustomer</span>(<span class="hljs-params">CustomerId $customerId</span>): <span class="hljs-title">array</span>
    </span>{
        <span class="hljs-comment">// ...</span>
    }
}
</code></pre>
<p><strong>My opinion:</strong> this works, but goes against the spirit of the Repository pattern.</p>
<p><strong>Problems:</strong></p>
<ol>
<li><p>Assumes all Aggregates are persisted the same way</p>
</li>
<li><p>A complex Aggregate (like Order with Items) doesn't follow this pattern</p>
</li>
<li><p>Generic methods like <code>find($id)</code> don't speak the domain language</p>
</li>
<li><p>You lose the main advantage: swapping implementations</p>
</li>
</ol>
<p><strong>When it can work:</strong></p>
<ul>
<li><p>Simpler applications where Aggregates map 1:1 to tables</p>
</li>
<li><p>Teams starting with the pattern and needing something pragmatic</p>
</li>
<li><p>Projects where complete abstraction isn't necessary</p>
</li>
</ul>
<p><strong>The ideal alternative for DDD contexts:</strong></p>
<p>For that context, don't use inheritance and don't use a <code>BaseRepository</code>. Each repository should have its own implementation because each <strong>Aggregate has different persistence needs</strong>.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-comment">// Order: complex aggregate (2 tables)</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EloquentOrderRepository</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">OrderRepositoryInterface</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">findById</span>(<span class="hljs-params">OrderId $id</span>): ?<span class="hljs-title">Order</span>
    </span>{
        $model = OrderModel::with(<span class="hljs-string">'items'</span>)-&gt;find($id-&gt;value);

        <span class="hljs-keyword">return</span> $model ? <span class="hljs-keyword">$this</span>-&gt;toDomainEntity($model) : <span class="hljs-literal">null</span>;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">save</span>(<span class="hljs-params">Order $order</span>): <span class="hljs-title">void</span>
    </span>{
        DB::transaction(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) <span class="hljs-title">use</span> (<span class="hljs-params">$order</span>) </span>{
            <span class="hljs-comment">// Saves to 'orders' table</span>
            <span class="hljs-comment">// Saves to 'order_items' table</span>
        });
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">toDomainEntity</span>(<span class="hljs-params">OrderModel $model</span>): <span class="hljs-title">Order</span> </span>{ <span class="hljs-comment">/* ... */</span> }
}

<span class="hljs-comment">// Customer: simple aggregate (1 table)</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EloquentCustomerRepository</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">CustomerRepositoryInterface</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">findById</span>(<span class="hljs-params">CustomerId $id</span>): ?<span class="hljs-title">Customer</span>
    </span>{
        $model = CustomerModel::find($id-&gt;value);

        <span class="hljs-keyword">return</span> $model ? <span class="hljs-keyword">$this</span>-&gt;toDomainEntity($model) : <span class="hljs-literal">null</span>;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">save</span>(<span class="hljs-params">Customer $customer</span>): <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-comment">// Saves to 'customers' table only</span>
        CustomerModel::updateOrCreate([<span class="hljs-comment">/* ... */</span>]);
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">toDomainEntity</span>(<span class="hljs-params">CustomerModel $model</span>): <span class="hljs-title">Customer</span> </span>{ <span class="hljs-comment">/* ... */</span> }
}
</code></pre>
<p><strong>Alternative for simple CRUDs:</strong></p>
<p>If you need to share common CRUD logic for simple aggregates, use a <strong>trait</strong>:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EloquentCustomerRepository</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">CustomerRepositoryInterface</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">CrudRepositoryTrait</span>;  <span class="hljs-comment">// Handles basic find, save, delete</span>

    <span class="hljs-comment">// Domain-specific methods - implemented manually</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">findByEmail</span>(<span class="hljs-params">Email $email</span>): ?<span class="hljs-title">Customer</span> </span>{ <span class="hljs-comment">/* ... */</span> }
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">findActiveCustomers</span>(<span class="hljs-params"></span>): <span class="hljs-title">array</span> </span>{ <span class="hljs-comment">/* ... */</span> }
}
</code></pre>
<p>The key difference from <code>BaseRepository</code>:</p>
<ul>
<li><p><strong>Interface stays domain-specific</strong> — <code>CustomerRepositoryInterface</code> has methods like <code>findByEmail()</code>, not generic <code>find($id)</code></p>
</li>
<li><p><strong>Trait is just implementation detail</strong> — helps reduce boilerplate for basic operations</p>
</li>
<li><p><strong>Each repository still owns its mapping</strong> — <code>toDomainEntity()</code> and <code>toDatabase()</code> are specific to each Aggregate</p>
</li>
</ul>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Aggregate Type</td><td>Example</td><td>Use Trait?</td></tr>
</thead>
<tbody>
<tr>
<td>Simple (1:1 with table)</td><td>Customer, Product, Category</td><td>✅ Can use</td></tr>
<tr>
<td>Complex (multiple tables)</td><td>Order + Items, Cart + Items</td><td>❌ Don't use</td></tr>
</tbody>
</table>
</div><p>For complex aggregates like <code>Order</code> (which persists to <code>orders</code> + <code>order_items</code> tables), write the repository from scratch. The trait won't fit, and that's fine — each repository knows best how to persist its Aggregate.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The Repository Pattern is powerful when used correctly, but frequently misunderstood.</p>
<p><strong>Key points:</strong></p>
<ol>
<li><p><strong>Repository ≠ DAO</strong>: Repository is domain-oriented, DAO is data-oriented</p>
</li>
<li><p><strong>One Repository per Aggregate Root</strong>: Don't create repositories for internal entities</p>
</li>
<li><p><strong>Repository requires Domain Model</strong>: Without a rich Domain Model, you just have a disguised DAO</p>
</li>
<li><p><strong>Eloquent is Active Record</strong>: It's not DAO nor Repository</p>
</li>
<li><p><strong>Interface should speak the domain language</strong>: <code>findPendingOrders()</code>, not <code>findByStatus('pending')</code></p>
</li>
<li><p><strong>Interface in Domain, Implementation in Infrastructure</strong>: This separation is key in DDD</p>
</li>
</ol>
<p>Before implementing the Repository Pattern, ask yourself: "Do I really need this?"</p>
<p>If your application is simple CRUD, Active Record (Eloquent) solves it perfectly. If you have complex domain logic, business invariants, and need real testability, then yes, Repository might be the right choice.</p>
<h2 id="heading-references">References</h2>
<ul>
<li><p><strong>Evans, Eric.</strong> <em>Domain-Driven Design: Tackling Complexity in the Heart of Software</em>. Addison-Wesley, 2003. (Blue Book)</p>
</li>
<li><p><strong>Vernon, Vaughn.</strong> <em>Implementing Domain-Driven Design</em>. Addison-Wesley, 2013. (Red Book)</p>
</li>
<li><p><strong>Khononov, Vlad.</strong> <em>Learning Domain-Driven Design: Aligning Software Architecture and Business Strategy</em>. O'Reilly Media, 2021.</p>
</li>
<li><p><strong>Alur, Deepak; Crupi, John; Malks, Dan.</strong> <em>Core J2EE Patterns: Best Practices and Design Strategies</em>. Prentice Hall, 2001.</p>
</li>
<li><p><strong>Fowler, Martin.</strong> <em>Patterns of Enterprise Application Architecture</em>. Addison-Wesley, 2002.</p>
</li>
<li><p><strong>Evans, Eric.</strong> <em>Domain-Driven Design Reference: Definitions and Pattern Summaries</em>. Domain Language, 2015.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Beyond CRUD: Building a Event-Driven Application with CQRS and Message Queues]]></title><description><![CDATA[As software engineers, we've all been there: building yet another CRUD application with a single database, where read and write operations compete for resources, complex queries slow down as data grows, and scaling becomes a nightmare. For years, thi...]]></description><link>https://read.sahdo.io/beyond-crud-building-a-event-driven-application-with-cqrs-and-message-queues</link><guid isPermaLink="true">https://read.sahdo.io/beyond-crud-building-a-event-driven-application-with-cqrs-and-message-queues</guid><category><![CDATA[slimframework]]></category><category><![CDATA[CORS]]></category><category><![CDATA[architecture]]></category><category><![CDATA[PHP]]></category><category><![CDATA[event-driven-architecture]]></category><dc:creator><![CDATA[Lucas Sahdo]]></dc:creator><pubDate>Sun, 23 Nov 2025 17:39:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1763920118798/fca7824f-9bc3-41bc-a09a-8856b68227a9.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As software engineers, we've all been there: building yet another CRUD application with a single database, where read and write operations compete for resources, complex queries slow down as data grows, and scaling becomes a nightmare. For years, this monolithic approach worked well enough for most applications. But what happens when your system needs to handle thousands of concurrent reads while processing critical writes? What if you need different data structures for displaying information versus storing it?</p>
<p>Enter <strong>CQRS</strong> (Command Query Responsibility Segregation) and <strong>Event-Driven Architecture</strong> – patterns that challenge the traditional way we build applications by answering a simple but important question: <strong><em>Why should reading data work the same way as writing it?</em></strong></p>
<p>In this article, I'll walk you through building a example of a simple Order Management System that implements CQRS with PHP 8.4, using Slim Framework, RabbitMQ, PostgreSQL, and MongoDB. But more importantly, I'll explain <em>why</em> you might want to use these patterns, <em>when</em> they make sense, and <em>how</em> to implement them without overcomplicating your architecture.</p>
<h2 id="heading-what-well-build">What We'll Build</h2>
<p>We'll create a system where:</p>
<ul>
<li><p><strong>Write operations</strong> (creating/updating orders) go to PostgreSQL</p>
</li>
<li><p><strong>Read operations</strong> (querying orders) come from MongoDB</p>
</li>
<li><p><strong>Events</strong> connect both sides through RabbitMQ</p>
</li>
<li><p>Everything is containerized with Docker for easy setup</p>
</li>
</ul>
<p>By the end, you'll understand not just how CQRS works in theory, but how to implement it in PHP.</p>
<h2 id="heading-why-this-matters">Why This Matters</h2>
<p>Traditional CRUD applications face real challenges:</p>
<ul>
<li><p>Read queries can block write operations</p>
</li>
<li><p>Complex joins slow down as data grows</p>
</li>
<li><p>Scaling reads and writes independently is difficult</p>
</li>
<li><p>One database structure must serve all purposes</p>
</li>
</ul>
<p>CQRS solves these problems by recognizing a fundamental truth: <strong>the way we write data is rarely the optimal way to read it.</strong></p>
<h2 id="heading-what-is-cqrs-really">What is CQRS, Really?</h2>
<p>CQRS stands for <strong>Command Query Responsibility Segregation</strong>. The idea is simple: separate the code that changes data from the code that reads data.</p>
<p>In a typical applications, everything hits the same database. One database handles everything with one structure that tries to be good at both reading and writing. Usually it ends up mediocre at both.</p>
<p>CQRS <strong>splits this responsibility</strong>. Write operations use one model and read operations use another model. They can share the same database or use different ones. In our project, we take it further by using PostgreSQL for writes and MongoDB for reads, with RabbitMQ keeping them in sync. When something changes on the write side, an event gets sent through the message queue. A consumer picks it up and updates the read side.</p>
<p>The key point is that CQRS is about separating the models and responsibilities, not necessarily about having two databases. You could have CQRS with a single database using different tables or even different queries against the same tables. The dual-database approach with message queues is just one way to implement it.</p>
<p>This makes sense when your reads vastly outnumber your writes. An e-commerce site might have ten thousand people browsing but only fifty making purchases. It also helps when read and write operations need different things. Writing needs data integrity and transactions. Reading needs speed and denormalized data.</p>
<p><strong>Skip CQRS for simple CRUD apps, admin panels, or internal tools</strong>. If your database isn't slow and your reads and writes are roughly equal, <strong>you're just adding complexity for no reason</strong>.</p>
<p>Event-driven architecture pairs well with CQRS but isn't required. You could implement CQRS by directly updating both models synchronously. In our project, we use events because they provide flexibility. When a user creates an order, you handle the write and create an event. That event goes to RabbitMQ. Multiple listeners can react: one updates MongoDB, another sends an email, another logs analytics. Your write code stays simple. It just says "an order was created" and walks away. Everything else happens in the background.</p>
<p>The trade-off is eventual consistency. Your read database won't update instantly. There's a small delay while the event travels through the message queue. For most applications, a delay of a few milliseconds is fine.</p>
<p>As Greg Young points out in his CQRS documentation, the command and query sides have very different needs. The command side processes transactions with consistent data and needs strong guarantees. The query side, however, can be eventually consistent in most systems. This difference is actually an advantage: it's far easier to process transactions with consistent data than to handle all the edge cases that eventual consistency brings into play on the write side.</p>
<p>But if you're building something like banking software where data must be immediately consistent, you need to think carefully about this trade-off.</p>
<h2 id="heading-a-word-of-caution">A Word of Caution</h2>
<p>Martin Fowler, one of the most respected voices in software architecture, offers an important warning about CQRS. He notes that while CQRS can benefit a few complex domains, such suitability is very much the minority case. Usually there's enough overlap between the command and query sides that sharing a model is easier. Using CQRS on a domain that doesn't match it will add complexity, <strong>thus reducing productivity and increasing risk</strong>.</p>
<p>This is worth taking seriously. <strong>CQRS is not a default architecture</strong>. It's a specialized tool for specific problems. Most applications are better served by a traditional architecture where reads and writes share the same model. The complexity you add with CQRS needs to pay for itself with clear benefits: better scalability, clearer separation of concerns, or the ability to optimize reads and writes differently.</p>
<p>Before implementing CQRS, ask yourself: do I actually have these problems? If your application works fine with a single database and traditional queries, stick with that. Simple is better than clever.</p>
<h2 id="heading-the-project-order-management-system">The Project: Order Management System</h2>
<p>To understand CQRS in practice, we built an order management system. The application is simple enough to grasp quickly but complete enough to demonstrate all the moving parts.</p>
<p>The system handles basic order operations. You can create orders, update their status, and query them. Nothing fancy. But under the hood, writes go to PostgreSQL while reads come from MongoDB, with RabbitMQ keeping everything in sync.</p>
<h3 id="heading-architecture-overview">Architecture Overview</h3>
<p>Here's how the pieces fit together:</p>
<pre><code class="lang-plaintext">+-------------+
|   client    |
+------+------+
       |
       | post /orders
       v
+-----------------+
|   slim api      |
|  (commands)     |
+--------+--------+
         |
         v
+-----------------+
|  postgresql     |
|  (write db)     |
+--------+--------+
         |
         | event: ordercreated
         v
+-----------------+
|   rabbitmq      |
|   (queue)       |
+--------+--------+
         |
         v
+-----------------+
|   consumer      |
|   (listener)    |
+--------+--------+
         |
         v
+-----------------+
|   mongodb       |
|   (read db)     |
+--------+--------+
         |
         | get /orders
         ^
+-----------------+
|   slim api      |
|   (queries)     |
+-----------------+
</code></pre>
<p>When a client creates an order, the request hits our Slim API. The API executes a command that saves the order to PostgreSQL. At this point, the order exists in the write database, but nowhere else.</p>
<p>Next, the command handler dispatches an event. This event gets published to RabbitMQ, where it sits in a queue waiting to be processed. The API responds to the client immediately without waiting for anything else to happen.</p>
<p>Meanwhile, a separate consumer process runs continuously, listening to the RabbitMQ queue. When it sees the order created event, it pulls the relevant data and writes it to MongoDB in a structure optimized for reading.</p>
<p>Now when a client queries for orders, the API reads directly from MongoDB. No joins, no complex queries. The data is already shaped exactly how we need it.</p>
<h3 id="heading-the-directory-structure">The Directory Structure</h3>
<pre><code class="lang-plaintext">event-driven-cqrs-php/
│
├── api/
│   ├── src/
│   │   ├── Application/         ← HTTP layer
│   │   │   ├── Actions/         (Request handlers)
│   │   │   ├── Command/         (Write operations)
│   │   │   └── Query/           (Read operations)
│   │   │
│   │   ├── Domain/              ← Business logic
│   │   │   └── Order/
│   │   │       ├── Event/       (Domain events)
│   │   │       └── Order.php    (Entity)
│   │   │
│   │   └── Infrastructure/      ← Technical details
│   │       ├── Consumer/        (RabbitMQ listener)
│   │       ├── Persistence/     (PostgreSQL)
│   │       └── Query/           (MongoDB)
│   │
│   └── bin/
│       └── consumer.php         ← Background worker
│
└── docker/
    ├── php/
    └── postgres/
</code></pre>
<p>The project follows a clean architecture with three main layers.</p>
<p>The Application layer contains everything related to handling requests. Actions are the HTTP handlers that receive requests and return responses. Commands represent write operations with their handlers. Queries represent read operations with their handlers.</p>
<p>The Domain layer holds the business logic. This is where the Order entity lives, along with domain events like OrderCreated and OrderStatusChanged. The domain doesn't know anything about databases or HTTP. It just models what an order is and what can happen to it.</p>
<p>The Infrastructure layer handles all the technical details. Database connections, repository implementations, event listeners, and the RabbitMQ consumer all live here. This layer is the bridge between your domain and the outside world.</p>
<h3 id="heading-the-flow-in-detail">The Flow in Detail</h3>
<p>Let's trace what happens step by step when you create an order:</p>
<pre><code class="lang-plaintext">1. Client Request
   POST /orders
   {
     "customer_name": "John Doe",
     "items": [...]
   }

2. Action Handler
   ↓
   CreateOrderCommand

3. Command Handler
   ↓
   Save to PostgreSQL
   ↓
   Dispatch OrderCreated Event

4. Event Listener
   ↓
   Publish to RabbitMQ
   ↓
   [API responds to client]

5. Consumer (background)
   ↓
   Read from RabbitMQ
   ↓
   Write to MongoDB

6. Query Time
   GET /orders
   ↓
   Read from MongoDB
   ↓
   Return response
</code></pre>
<p>The request comes in as a POST to <code>/orders</code> with JSON data. An Action handler receives it and creates a CreateOrderCommand with the request data. This command gets passed to a CreateOrderCommandHandler.</p>
<p>The command handler does two things. First, it saves the order to PostgreSQL using a repository. Second, it dispatches an OrderCreated event using League Event dispatcher.</p>
<p>An event listener immediately catches this event and publishes a message to RabbitMQ. The message contains the order ID and relevant data. At this point, the API request is done and sends a response back to the client.</p>
<p>The consumer process, which runs separately in its own container, picks up the message from RabbitMQ. It reads the order data and writes it to MongoDB in a denormalized format. Now the read model is updated.</p>
<p>When someone queries for orders, they hit GET <code>/orders</code>. A Query handler receives the request and fetches data directly from MongoDB. Fast, simple, no complex queries needed.</p>
<h3 id="heading-database-structures">Database Structures</h3>
<p>Here's how the same order looks in each database:</p>
<p><strong>PostgreSQL (Write Model - Normalized):</strong></p>
<pre><code class="lang-plaintext">orders table:
┌─────────────────────────────────────┬─────────────┬─────────┬────────────┐
│ id                                  │ customer_id │ total   │ status     │
├─────────────────────────────────────┼─────────────┼─────────┼────────────┤
│ 550e8400-e29b-41d4-a716-446655440000│ 123         │ 1049.99 │ pending    │
└─────────────────────────────────────┴─────────────┴─────────┴────────────┘

order_items table:
┌──────────┬────────────────────────────────────┬──────────┬──────────┐
│ order_id │ product_id                         │ quantity │ price    │
├──────────┼────────────────────────────────────┼──────────┼──────────┤
│ 550e...  │ prod_001                           │ 1        │ 999.99   │
│ 550e...  │ prod_002                           │ 2        │ 25.00    │
└──────────┴────────────────────────────────────┴──────────┴──────────┘
</code></pre>
<p><strong>MongoDB (Read Model - Denormalized):</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"_id"</span>: <span class="hljs-string">"550e8400-e29b-41d4-a716-446655440000"</span>,
  <span class="hljs-attr">"customer_name"</span>: <span class="hljs-string">"John Doe"</span>,
  <span class="hljs-attr">"customer_email"</span>: <span class="hljs-string">"john@example.com"</span>,
  <span class="hljs-attr">"items"</span>: [
    {<span class="hljs-attr">"product"</span>: <span class="hljs-string">"Laptop"</span>, <span class="hljs-attr">"quantity"</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">"price"</span>: <span class="hljs-number">999.99</span>},
    {<span class="hljs-attr">"product"</span>: <span class="hljs-string">"Mouse"</span>, <span class="hljs-attr">"quantity"</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">"price"</span>: <span class="hljs-number">25.00</span>}
  ],
  <span class="hljs-attr">"total_amount"</span>: <span class="hljs-number">1049.99</span>,
  <span class="hljs-attr">"status"</span>: <span class="hljs-string">"pending"</span>,
  <span class="hljs-attr">"created_at"</span>: <span class="hljs-string">"2024-01-15T10:30:00Z"</span>
}
</code></pre>
<p>PostgreSQL keeps things normalized for data integrity. MongoDB has everything pre-joined and ready to display.</p>
<h3 id="heading-why-this-structure-works">Why This Structure Works</h3>
<p>The separation of concerns is clear. Commands never touch MongoDB. Queries never touch PostgreSQL. Events flow in one direction from write to read.</p>
<p>You can scale each piece independently. Need to handle more reads? Add MongoDB replicas. Need to process events faster? Run multiple consumer instances. The write database can stay small and focused.</p>
<p>Testing becomes easier too. You can test command handlers without worrying about the read model. You can test query handlers without setting up event processing. Each piece has one job and does it well.</p>
<p>Let's dive in and see how this works in practice.</p>
<h2 id="heading-looking-at-the-code">Looking at the Code</h2>
<p>Now let's see how this actually works in practice. I'll walk you through the complete flow from creating an order to querying it back.</p>
<p>The full source code is available here: <a target="_blank" href="https://github.com/sahdoio/event-driven-cqrs-php?tab=readme-ov-file">https://github.com/sahdoio/event-driven-cqrs-php?tab=readme-ov-file</a></p>
<p>Clone the project into your machine and then start the docker containers.</p>
<h3 id="heading-the-docker-setup">The Docker Setup</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763918281692/c6166d2d-66a1-4c1d-a2dd-8951ef8e5ac5.png" alt class="image--center mx-auto" /></p>
<p>To start the application setup type on the project root folder:</p>
<pre><code class="lang-bash">make go
</code></pre>
<h3 id="heading-the-command-what-we-want-to-do">The Command: What We Want to Do</h3>
<p>When someone creates an order, we start with a command object. It's just a simple data container:</p>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CreateOrderCommand</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params">
        <span class="hljs-keyword">public</span> readonly <span class="hljs-keyword">string</span> $customerName,
        <span class="hljs-keyword">public</span> readonly <span class="hljs-keyword">string</span> $customerEmail,
        <span class="hljs-keyword">public</span> readonly <span class="hljs-keyword">array</span> $items,
        <span class="hljs-keyword">public</span> readonly <span class="hljs-keyword">float</span> $totalAmount
    </span>) </span>{}
}
</code></pre>
<p>Nothing fancy. Just the data needed to create an order.</p>
<h3 id="heading-the-command-handler-doing-the-work">The Command Handler: Doing the Work</h3>
<p>The command handler receives this command and does two things:</p>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CreateOrderHandler</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params">
        <span class="hljs-keyword">private</span> readonly PostgresOrderRepository $orderRepository,
        <span class="hljs-keyword">private</span> readonly EventDispatcher $eventDispatcher
    </span>) </span>{}

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span>(<span class="hljs-params">CreateOrderCommand $command</span>): <span class="hljs-title">string</span>
    </span>{
        <span class="hljs-comment">// 1. Create and save the order to PostgreSQL</span>
        $orderId = Uuid::uuid7();
        $order = <span class="hljs-keyword">new</span> Order(
            $orderId,
            $command-&gt;customerName,
            $command-&gt;customerEmail,
            $command-&gt;items,
            $command-&gt;totalAmount,
            OrderStatus::PENDING
        );

        <span class="hljs-keyword">$this</span>-&gt;orderRepository-&gt;save($order);

        <span class="hljs-comment">// 2. Dispatch an event</span>
        $event = <span class="hljs-keyword">new</span> OrderCreated(
            $orderId-&gt;toString(),
            $order-&gt;getCustomerName(),
            $order-&gt;getCustomerEmail(),
            $order-&gt;getItems(),
            $order-&gt;getTotalAmount(),
            $order-&gt;getStatus()-&gt;value,
            $order-&gt;getCreatedAt()-&gt;format(<span class="hljs-string">'Y-m-d H:i:s'</span>)
        );

        <span class="hljs-keyword">$this</span>-&gt;eventDispatcher-&gt;dispatch($event);

        <span class="hljs-keyword">return</span> $orderId-&gt;toString();
    }
}
</code></pre>
<p>First it creates the Order entity and saves it to PostgreSQL. Then it creates an event and dispatches it. The handler doesn't know what happens with that event. Its job is done.</p>
<h3 id="heading-the-repository-talking-to-postgresql">The Repository: Talking to PostgreSQL</h3>
<p>The repository handles the database details:</p>
<pre><code class="lang-php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">save</span>(<span class="hljs-params">Order $order</span>): <span class="hljs-title">void</span>
</span>{
    $sql = <span class="hljs-string">"INSERT INTO orders (id, customer_name, customer_email, items, total_amount, status, created_at, updated_at)
            VALUES (:id, :customer_name, :customer_email, :items, :total_amount, :status, :created_at, :updated_at)
            ON CONFLICT (id) DO UPDATE SET ..."</span>;

    $stmt = $pdo-&gt;prepare($sql);
    $stmt-&gt;execute([
        <span class="hljs-string">'id'</span> =&gt; $order-&gt;getId()-&gt;toString(),
        <span class="hljs-string">'customer_name'</span> =&gt; $order-&gt;getCustomerName(),
        <span class="hljs-string">'customer_email'</span> =&gt; $order-&gt;getCustomerEmail(),
        <span class="hljs-string">'items'</span> =&gt; json_encode($order-&gt;getItems()),
        <span class="hljs-string">'total_amount'</span> =&gt; $order-&gt;getTotalAmount(),
        <span class="hljs-string">'status'</span> =&gt; $order-&gt;getStatus()-&gt;value,
        <span class="hljs-string">'created_at'</span> =&gt; $order-&gt;getCreatedAt()-&gt;format(<span class="hljs-string">'Y-m-d H:i:s'</span>),
        <span class="hljs-string">'updated_at'</span> =&gt; $order-&gt;getUpdatedAt()?-&gt;format(<span class="hljs-string">'Y-m-d H:i:s'</span>),
    ]);
}
</code></pre>
<p>Standard database insertion. The order is now in PostgreSQL.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763918112401/eaccc206-5c63-4bb7-9559-1d0408475b82.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763918689036/c33f732e-a036-427c-9848-0143295d7b9a.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-publishing-to-rabbitmq">Publishing to RabbitMQ</h3>
<p>When the event dispatcher fires the OrderCreated event, the RabbitMQEventPublisher catches it:</p>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RabbitMQEventPublisher</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Listener</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__invoke</span>(<span class="hljs-params"><span class="hljs-keyword">object</span> $event</span>): <span class="hljs-title">void</span>
    </span>{
        $eventName = method_exists($event, <span class="hljs-string">'eventName'</span>) ? $event-&gt;eventName() : get_class($event);
        $payload = method_exists($event, <span class="hljs-string">'toArray'</span>) ? $event-&gt;toArray() : [<span class="hljs-string">'event'</span> =&gt; $eventName];

        $message = <span class="hljs-keyword">$this</span>-&gt;context-&gt;createMessage(json_encode([
            <span class="hljs-string">'event'</span> =&gt; $eventName,
            <span class="hljs-string">'payload'</span> =&gt; $payload,
            <span class="hljs-string">'timestamp'</span> =&gt; time(),
        ]));

        <span class="hljs-keyword">$this</span>-&gt;producer-&gt;send($queue, $message);
    }
}
</code></pre>
<p>It serializes the event to JSON and sends it to RabbitMQ. At this point, the HTTP request is done. The API returns a response to the client.</p>
<h3 id="heading-the-consumer-processing-events">The Consumer: Processing Events</h3>
<p>Meanwhile, a separate PHP process runs continuously:</p>
<pre><code class="lang-php"><span class="hljs-comment">// bin/consumer.php</span>
$consumer = $container-&gt;get(MessageConsumer::class);
<span class="hljs-keyword">echo</span> <span class="hljs-string">"Message Consumer started\n"</span>;
<span class="hljs-keyword">echo</span> <span class="hljs-string">"Listening for events on RabbitMQ...\n"</span>;
$consumer-&gt;consume();
</code></pre>
<p>This consumer sits there waiting for messages:</p>
<pre><code class="lang-php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">consume</span>(<span class="hljs-params"></span>): <span class="hljs-title">void</span>
</span>{
    <span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {
        $message = <span class="hljs-keyword">$this</span>-&gt;consumer-&gt;receive(<span class="hljs-number">1000</span>);

        <span class="hljs-keyword">if</span> ($message === <span class="hljs-literal">null</span>) {
            <span class="hljs-keyword">continue</span>;
        }

        <span class="hljs-keyword">try</span> {
            $data = json_decode($message-&gt;getBody(), <span class="hljs-literal">true</span>);
            $event = $data[<span class="hljs-string">'event'</span>];
            $payload = $data[<span class="hljs-string">'payload'</span>];

            <span class="hljs-keyword">$this</span>-&gt;eventRouter-&gt;dispatch($event, $payload);
            <span class="hljs-keyword">$this</span>-&gt;consumer-&gt;acknowledge($message);

            <span class="hljs-keyword">echo</span> sprintf(<span class="hljs-string">"Processed event: %s\n"</span>, $event);
        } <span class="hljs-keyword">catch</span> (\<span class="hljs-built_in">Exception</span> $e) {
            <span class="hljs-keyword">$this</span>-&gt;consumer-&gt;reject($message);
            <span class="hljs-keyword">echo</span> sprintf(<span class="hljs-string">"Error processing event: %s\n"</span>, $e-&gt;getMessage());
        }
    }
}
</code></pre>
<p>When it receives a message, it extracts the event name and payload, then routes it to the appropriate listener.</p>
<h3 id="heading-the-event-listener-updating-mongodb">The Event Listener: Updating MongoDB</h3>
<p>The OrderCreatedListener handles the event:</p>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderCreatedListener</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">EventListenerInterface</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">subscribedTo</span>(<span class="hljs-params"></span>): <span class="hljs-title">string</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">'order.created'</span>;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span>(<span class="hljs-params"><span class="hljs-keyword">array</span> $payload</span>): <span class="hljs-title">void</span>
    </span>{
        $collection = <span class="hljs-keyword">$this</span>-&gt;mongoConnection-&gt;getDatabase()-&gt;selectCollection(<span class="hljs-string">'orders'</span>);

        $collection-&gt;insertOne([
            <span class="hljs-string">'_id'</span> =&gt; $payload[<span class="hljs-string">'order_id'</span>],
            <span class="hljs-string">'customer_name'</span> =&gt; $payload[<span class="hljs-string">'customer_name'</span>],
            <span class="hljs-string">'customer_email'</span> =&gt; $payload[<span class="hljs-string">'customer_email'</span>],
            <span class="hljs-string">'items'</span> =&gt; $payload[<span class="hljs-string">'items'</span>],
            <span class="hljs-string">'total_amount'</span> =&gt; $payload[<span class="hljs-string">'total_amount'</span>],
            <span class="hljs-string">'status'</span> =&gt; $payload[<span class="hljs-string">'status'</span>],
            <span class="hljs-string">'created_at'</span> =&gt; $payload[<span class="hljs-string">'created_at'</span>],
            <span class="hljs-string">'updated_at'</span> =&gt; <span class="hljs-literal">null</span>,
        ]);
    }
}
</code></pre>
<p>It takes the event payload and writes it directly to MongoDB. Now the read model is updated.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763919386363/e9c4554d-502d-4b47-8ed2-04abe07546e6.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-the-complete-flow">The Complete Flow</h3>
<p>Let me put it all together:</p>
<p><strong>Step 1:</strong> Client sends POST request to create an order<br /><strong>Step 2:</strong> CreateOrderHandler receives CreateOrderCommand<br /><strong>Step 3:</strong> Handler creates Order entity and saves to PostgreSQL<br /><strong>Step 4:</strong> Handler dispatches OrderCreated event<br /><strong>Step 5:</strong> RabbitMQEventPublisher catches event and sends to RabbitMQ<br /><strong>Step 6:</strong> API responds to client (order created)<br /><strong>Step 7:</strong> Consumer picks up message from RabbitMQ<br /><strong>Step 8:</strong> Consumer routes event to OrderCreatedListener<br /><strong>Step 9:</strong> Listener inserts order into MongoDB<br /><strong>Step 10:</strong> Read model is now updated</p>
<p>When someone queries for orders, they hit a different endpoint that reads directly from MongoDB. No PostgreSQL involved. No events. Just a simple query.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763918179008/2606bf78-1485-4ebe-bf37-70be2f6bc20b.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-dependency-injection-setup">Dependency Injection Setup</h3>
<p>Everything is wired together in the dependencies file:</p>
<pre><code class="lang-php">EventDispatcher::class =&gt; <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">ContainerInterface $c</span>) </span>{
    $dispatcher = <span class="hljs-keyword">new</span> EventDispatcher();
    $dispatcher-&gt;subscribeTo(<span class="hljs-string">'order.created'</span>, $c-&gt;get(RabbitMQEventPublisher::class));
    $dispatcher-&gt;subscribeTo(<span class="hljs-string">'order.updated'</span>, $c-&gt;get(RabbitMQEventPublisher::class));
    <span class="hljs-keyword">return</span> $dispatcher;
},

CreateOrderHandler::class =&gt; <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">ContainerInterface $c</span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> CreateOrderHandler(
        $c-&gt;get(PostgresOrderRepository::class),
        $c-&gt;get(EventDispatcher::class)
    );
},
</code></pre>
<p>The container builds all the pieces and injects dependencies where needed. Command handlers get repositories and event dispatchers. Query handlers get MongoDB connections. The consumer gets the event router with all listeners registered.</p>
<h3 id="heading-what-makes-this-work">What Makes This Work</h3>
<p>The separation is clean. Commands know nothing about MongoDB. Queries know nothing about PostgreSQL or events. The event system connects them without coupling them together.</p>
<p>You can test each piece independently. Mock the repository to test the command handler. Mock MongoDB to test the query handler. The consumer can be tested separately from the API.</p>
<p>And you can scale each piece differently. Need more API capacity? Add more PHP containers. Need faster event processing? Run multiple consumer instances. MongoDB getting hammered? Add replicas. They all scale independently.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>CQRS isn't a silver bullet. It adds complexity that you need to justify with real benefits. Most applications don't need it.</p>
<p>But when you do need it, when your reads and writes have fundamentally different needs, when you need to scale them independently, CQRS gives you a clean way to handle that complexity.</p>
<p>The code in this project shows that implementing CQRS doesn't have to be complicated. Commands write to PostgreSQL. Queries read from MongoDB. Events keep them in sync through RabbitMQ. Each piece does one thing well.</p>
<p>The real lesson is knowing when to use patterns like this. Don't reach for CQRS because it's interesting or because you read about it in an article. Use it when you have the specific problems it solves.</p>
<p>The project is available on GitHub if you want to dig deeper. Clone it, run it, break it, see how it works. Understanding comes from doing.</p>
<p><a target="_blank" href="https://github.com/sahdoio/event-driven-cqrs-php?tab=readme-ov-file">https://github.com/sahdoio/event-driven-cqrs-php?tab=readme-ov-file</a></p>
<h2 id="heading-references">References</h2>
<p>Young, G. (2010). <em>CQRS Documents</em>. Retrieved from <a target="_blank" href="https://cqrs.wordpress.com/wp-content/uploads/2010/11/cqrs_documents.pdf">https://cqrs.wordpress.com/wp-content/uploads/2010/11/cqrs_documents.pdf</a></p>
<p>Fowler, M. (2011). <em>CQRS</em>. martinfowler.com. Retrieved from <a target="_blank" href="https://martinfowler.com/bliki/CQRS.html">https://martinfowler.com/bliki/CQRS.html</a></p>
<p>Microsoft. (n.d.). <em>Command and Query Responsibility Segregation (CQRS) pattern</em>. Azure Architecture Center. Retrieved from <a target="_blank" href="https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs">https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs</a></p>
<p>Vernon, V. (2013). <em>Implementing Domain-Driven Design</em>. Addison-Wesley Professional.</p>
]]></content:encoded></item><item><title><![CDATA[Dependency Injection Vs Dependency Invertion]]></title><description><![CDATA[Dependency Injection (DI) and Dependency Inversion (DIP) are two key concepts in software development that often get confused. While they’re related, they focus on different things. Dependency Injection deals with how objects get their dependencies, ...]]></description><link>https://read.sahdo.io/dependency-injection-vs-dependency-invertion</link><guid isPermaLink="true">https://read.sahdo.io/dependency-injection-vs-dependency-invertion</guid><category><![CDATA[DIP]]></category><category><![CDATA[DI]]></category><category><![CDATA[oop]]></category><category><![CDATA[PHP]]></category><dc:creator><![CDATA[Lucas Sahdo]]></dc:creator><pubDate>Tue, 25 Feb 2025 23:26:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1739200775858/2d835d50-426f-4f05-9c63-78f0dc6f71df.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dependency Injection (DI) and Dependency Inversion (DIP) are two key concepts in software development that often get confused. While they’re related, they focus on different things. Dependency Injection deals with how objects get their dependencies, making systems easier to test and more modular. Dependency Inversion, part of the SOLID principles, focuses on ensuring high-level code depends on abstractions, not concrete implementations.</p>
<p>In this article, we’ll break down how these ideas work together to boost flexibility, maintainability, and scalability in your code.</p>
<h2 id="heading-dependency-injection">Dependency Injection</h2>
<p>The hability to inject dependencies trough the controller. According to laravel website,at the service container section we have this:</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Dependency injection is a fancy phrase that essentially means this: class dependencies are "injected" into the class via the constructor or, in some cases, "setter" methods.</div>
</div>

<p>For example, imagine that you have a <code>UserService</code> and you want to connect to the database through a <code>Database</code> class. I’m going to create a class without using DI and I’ll create a new instance of the database class inside the <code>UserService</code> class:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span> 
</span>{
    <span class="hljs-keyword">private</span> MySQLDatabase $database;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-comment">// Warning: Creating a new instance inside the class !!!</span>
        <span class="hljs-keyword">$this</span>-&gt;database = <span class="hljs-keyword">new</span> MySQLDatabase();
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getUser</span>(<span class="hljs-params">$id</span>): <span class="hljs-title">string</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;database-&gt;query(<span class="hljs-string">"SELECT * FROM users WHERE id = <span class="hljs-subst">$id</span>"</span>);
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MySQLDatabase</span> 
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">query</span>(<span class="hljs-params">$sql</span>): <span class="hljs-title">string</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">"User data for ID: <span class="hljs-subst">$sql</span>"</span>;
    }
}

$userService = <span class="hljs-keyword">new</span> UserService();
<span class="hljs-keyword">echo</span> $userService-&gt;getUser(<span class="hljs-number">1</span>);
</code></pre>
<p>The result would be:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740572913480/2588e2d3-06c2-413a-94ac-7f09beea9ea6.png" alt class="image--center mx-auto" /></p>
<p>That defines an <strong>Association</strong> in Object-Oriented Programming (OOP). Specifically, it is a <strong>Composition Association</strong> since the <code>UserService</code> class holds an instance of the <code>MySQLDatabase</code> class and controls its lifecycle.</p>
<blockquote>
<p>Composition is a strong form of association. In it, the "container" (or "whole") class <strong>owns</strong> the instance of the "part" class. This means that:</p>
<ul>
<li><p>If the "container" class is destroyed, the instances of the "part" class are also destroyed.</p>
</li>
<li><p>The "part" class <strong>cannot exist independently</strong> of the "container" class.</p>
</li>
</ul>
</blockquote>
<p>Now let’s rewrite the class like below, now applying DI (Dependency Injection):</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span> 
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> MySQLDatabase $database</span>) </span>{}

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getUser</span>(<span class="hljs-params">$id</span>): <span class="hljs-title">string</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;database-&gt;query(<span class="hljs-string">"SELECT * FROM users WHERE id = <span class="hljs-subst">$id</span>"</span>);
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MySQLDatabase</span> 
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">query</span>(<span class="hljs-params">$sql</span>): <span class="hljs-title">string</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">"User data for ID: <span class="hljs-subst">$sql</span>"</span>;
    }
}

<span class="hljs-comment">// depedency from outside</span>
$mysqlDatabase = <span class="hljs-keyword">new</span> MySQLDatabase();
<span class="hljs-comment">// user service receives depedency from outside</span>
$userService = <span class="hljs-keyword">new</span> UserService($mysqlDatabase);

<span class="hljs-keyword">echo</span> $userService-&gt;getUser(<span class="hljs-number">1</span>);
</code></pre>
<p>Using <strong>Dependency Injection (DI)</strong> in this example improves flexibility, testability, and maintainability, but it does not fully decouple the system on its own. While DI allows the database dependency to be passed from the outside instead of being instantiated within <code>UserService</code>, the class is still tied to a specific implementation (<code>MySQLDatabase</code>). This means <strong>DI alone does not eliminate dependency on concrete implementations</strong>, it just makes injecting dependencies more manageable.</p>
<h2 id="heading-the-coupling-problem-dip-can-handle-it">The Coupling Problem: DIP Can Handle It</h2>
<p>Although <code>UserService</code> now receives the dependency from the outside, it is still tightly coupled to <code>MySQLDatabase</code> because the dependency type is explicitly defined as <code>MySQLDatabase</code>. If we needed to switch to <code>PostgreSQLDatabase</code> or <code>MongoDBDatabase</code>, we would still have to modify <code>UserService</code>, which violates the <strong>Open/Closed Principle (OCP)</strong>. This is where the <strong>Dependency Inversion Principle (DIP)</strong> comes in, promoting the use of <strong>interfaces</strong> instead of concrete implementations.</p>
<p>According to Robert C. Martin (Uncle Bob) blog, definition of DIP:</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><em>Depend in the direction of abstraction. High level modules should not depend upon low level details.</em></div>
</div>

<p>In our example, by depending on an interface (<code>DatabaseInterface</code>), <code>UserService</code> becomes agnostic to the actual database implementation. This allows us to inject any database class that implements <code>DatabaseInterface</code>, enabling true flexibility and reducing coupling.</p>
<p>Here's the refactored version following <strong>DIP</strong>:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">DatabaseInterface</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">query</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> $sql</span>): <span class="hljs-title">string</span></span>;
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MySQLDatabase</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">DatabaseInterface</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">query</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> $sql</span>): <span class="hljs-title">string</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">"User data from MySQL for ID: <span class="hljs-subst">$sql</span>"</span>;
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PostgreSQLDatabase</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">DatabaseInterface</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">query</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> $sql</span>): <span class="hljs-title">string</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">"User data from PostgreSQL for ID: <span class="hljs-subst">$sql</span>"</span>;
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span>
</span>{
    <span class="hljs-keyword">private</span> DatabaseInterface $database;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params">DatabaseInterface $database</span>)
    </span>{
        <span class="hljs-keyword">$this</span>-&gt;database = $database;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getUser</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $id</span>): <span class="hljs-title">string</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;database-&gt;query(<span class="hljs-string">"SELECT * FROM users WHERE id = <span class="hljs-subst">$id</span>"</span>);
    }
}

<span class="hljs-comment">// Injecting MySQLDatabase</span>
$mysqlDatabase = <span class="hljs-keyword">new</span> MySQLDatabase();
$userServiceMySQL = <span class="hljs-keyword">new</span> UserService($mysqlDatabase);
<span class="hljs-keyword">echo</span> $userServiceMySQL-&gt;getUser(<span class="hljs-number">1</span>); 

<span class="hljs-comment">// Injecting PostgreSQLDatabase</span>
$postgresDatabase = <span class="hljs-keyword">new</span> PostgreSQLDatabase();
$userServicePostgres = <span class="hljs-keyword">new</span> UserService($postgresDatabase);
<span class="hljs-keyword">echo</span> $userServicePostgres-&gt;getUser(<span class="hljs-number">2</span>);
</code></pre>
<p>The result would be:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740574671953/394d42eb-929f-458c-b3ea-1aef75076e5d.png" alt class="image--center mx-auto" /></p>
<p>Do you see? Now <code>UserService</code> is completely agnostic to the database implementation. It doesn’t need to know any details about it, only how to use it through the interface.</p>
<p>The <strong>Dependency Inversion Principle (DIP)</strong> makes decoupling ideal because <code>UserService</code> no longer needs to know about the specific database implementation it interacts with. Instead of depending on a concrete class like <code>MySQLDatabase</code>, it relies on an abstraction (<code>DatabaseInterface</code>), allowing any database implementation to be injected without modifying <code>UserService</code>.</p>
<p>This makes future changes seamless, if a different database like <code>MongoDBDatabase</code> or <code>PostgreSQLDatabase</code> is needed, it can be integrated simply by implementing the interface. As a result, the system becomes more flexible, maintainable, and aligned with <strong>SOLID principles</strong>, particularly <strong>Open/Closed (OCP)</strong> and <strong>Dependency Inversion (DIP)</strong>.</p>
<h2 id="heading-a-good-use-case-the-service-layer-repository-pattern">A Good Use Case: The Service Layer / Repository Pattern</h2>
<p>I know the examples are <strong>too simple</strong>, and one thing I don’t like about them is that the <strong>query method is inside the</strong> <code>UserService</code> class. That’s not ideal in real-world applications.</p>
<p>Usually, service layers, if this is about a Layered Architecture, should not access DB implementations through database classes directly. Using a Repository might be better here since it acts as an abstraction over database operations, separating business logic from data access.</p>
<p>This makes it easier to switch database implementations without modifying service logic, improving maintainability, scalability, and testability. Here is an example using all the concepts examplained until now and adding the repository layer.</p>
<p>The structure would be:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740587320173/6ac650af-5ebb-47b4-9dae-85cb21092c3a.png" alt class="image--center mx-auto" /></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🔴</div>
<div data-node-type="callout-text">Remember, this is a very basic example using <strong>pure PHP and PDO</strong>. I didn’t test this last code with a real database. It is only an <strong>example for reference</strong>. In a real-world application, you will have <strong>more tools provided by frameworks like Laravel or Symfony</strong> to handle dependency injection, database abstraction, and other advanced features.</div>
</div>

<h3 id="heading-appdatabasedatabaseinterfacephp"><code>/app/Database/DatabaseInterface.php</code></h3>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">declare</span>(strict_types=<span class="hljs-number">1</span>);

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Database</span>;

<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">DatabaseInterface</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">query</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> $sql, <span class="hljs-keyword">array</span> $params = []</span>): <span class="hljs-title">array</span></span>;
}
</code></pre>
<h3 id="heading-appdatabasemysqldatabasephp"><code>/app/Database/MySQLDatabase.php</code></h3>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">declare</span>(strict_types=<span class="hljs-number">1</span>);

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Database</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">PDO</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">PDOException</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">RuntimeException</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">DatabaseInterface</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MySQLDatabase</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">DatabaseInterface</span>
</span>{
    <span class="hljs-keyword">private</span> PDO $connection;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">$this</span>-&gt;connection = <span class="hljs-keyword">new</span> PDO(<span class="hljs-string">"mysql:host=localhost;dbname=app"</span>, <span class="hljs-string">"user"</span>, <span class="hljs-string">"password"</span>);
            <span class="hljs-keyword">$this</span>-&gt;connection-&gt;setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        } <span class="hljs-keyword">catch</span> (PDOException $e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">RuntimeException</span>(
                <span class="hljs-string">"Database connection failed: "</span> . $e-&gt;getMessage(), 
                (<span class="hljs-keyword">int</span>) $e-&gt;getCode(), 
                $e
            );
        }
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">query</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> $sql, <span class="hljs-keyword">array</span> $params = []</span>): <span class="hljs-title">array</span>
    </span>{
        <span class="hljs-keyword">try</span> {
            $stmt = <span class="hljs-keyword">$this</span>-&gt;connection-&gt;prepare($sql);
            $stmt-&gt;execute($params);
            <span class="hljs-keyword">return</span> $stmt-&gt;fetch(PDO::FETCH_ASSOC) ?: [];
        } <span class="hljs-keyword">catch</span> (PDOException $e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">RuntimeException</span>(
                <span class="hljs-string">"Database query failed: "</span> . $e-&gt;getMessage(), 
                (<span class="hljs-keyword">int</span>) $e-&gt;getCode(), 
                $e
            );
        }
    }
}
</code></pre>
<h3 id="heading-appdatabasepostgresqldatabasephp"><code>/app/Database/PostgreSQLDatabase.php</code></h3>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">declare</span>(strict_types=<span class="hljs-number">1</span>);

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Database</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">PDO</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">PDOException</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">RuntimeException</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">DatabaseInterface</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PostgreSQLDatabase</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">DatabaseInterface</span>
</span>{
    <span class="hljs-keyword">private</span> PDO $connection;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">$this</span>-&gt;connection = <span class="hljs-keyword">new</span> PDO(<span class="hljs-string">"pgsql:host=localhost;dbname=app"</span>, <span class="hljs-string">"user"</span>, <span class="hljs-string">"password"</span>);
            <span class="hljs-keyword">$this</span>-&gt;connection-&gt;setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        } <span class="hljs-keyword">catch</span> (PDOException $e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">RuntimeException</span>(
                <span class="hljs-string">"Database connection failed: "</span> . $e-&gt;getMessage(), 
                (<span class="hljs-keyword">int</span>) $e-&gt;getCode(), 
                $e
            );
        }
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">query</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> $sql, <span class="hljs-keyword">array</span> $params = []</span>): <span class="hljs-title">array</span>
    </span>{
        <span class="hljs-keyword">try</span> {
            $stmt = <span class="hljs-keyword">$this</span>-&gt;connection-&gt;prepare($sql);
            $stmt-&gt;execute($params);
            <span class="hljs-keyword">return</span> $stmt-&gt;fetch(PDO::FETCH_ASSOC) ?: [];
        } <span class="hljs-keyword">catch</span> (PDOException $e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">RuntimeException</span>(
                <span class="hljs-string">"Database query failed: "</span> . $e-&gt;getMessage(), 
                (<span class="hljs-keyword">int</span>) $e-&gt;getCode(), 
                $e
            );
        }
    }
}
</code></pre>
<h3 id="heading-apprepositoryuserrepositoryinterfacephp"><code>/app/Repository/UserRepositoryInterface.php</code></h3>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">declare</span>(strict_types=<span class="hljs-number">1</span>);

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Repository</span>;

<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">UserRepositoryInterface</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getUserById</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $id</span>): ?<span class="hljs-title">array</span></span>;
}
</code></pre>
<h3 id="heading-apprepositorydbuserrepositoryphp"><code>/app/Repository/DbUserRepository.php</code></h3>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">declare</span>(strict_types=<span class="hljs-number">1</span>);

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Repository</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">DatabaseInterface</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Repository</span>\<span class="hljs-title">UserRepositoryInterface</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DbUserRepository</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">UserRepositoryInterface</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> DatabaseInterface $database</span>) </span>{}

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getUserById</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $id</span>): ?<span class="hljs-title">array</span>
    </span>{
        $sql = <span class="hljs-string">"SELECT * FROM users WHERE id = :id"</span>;
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;database-&gt;query($sql, [<span class="hljs-string">'id'</span> =&gt; $id]);
    }
}
</code></pre>
<h3 id="heading-apprepositoryinmemoryuserrepositoryphp"><code>/app/Repository/InMemoryUserRepository.php</code></h3>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">declare</span>(strict_types=<span class="hljs-number">1</span>);

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Repository</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Repository</span>\<span class="hljs-title">UserRepositoryInterface</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">InMemoryUserRepository</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">UserRepositoryInterface</span>
</span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">array</span> $users = [
        <span class="hljs-number">1</span> =&gt; [<span class="hljs-string">'id'</span> =&gt; <span class="hljs-number">1</span>, <span class="hljs-string">'name'</span> =&gt; <span class="hljs-string">'Alice'</span>, <span class="hljs-string">'email'</span> =&gt; <span class="hljs-string">'alice@example.com'</span>],
        <span class="hljs-number">2</span> =&gt; [<span class="hljs-string">'id'</span> =&gt; <span class="hljs-number">2</span>, <span class="hljs-string">'name'</span> =&gt; <span class="hljs-string">'Bob'</span>, <span class="hljs-string">'email'</span> =&gt; <span class="hljs-string">'bob@example.com'</span>]
    ];

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getUserById</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $id</span>): ?<span class="hljs-title">array</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;users[$id] ?? <span class="hljs-literal">null</span>;
    }
}
</code></pre>
<h3 id="heading-appserviceuserservicephp"><code>/app/Service/UserService.php</code></h3>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">declare</span>(strict_types=<span class="hljs-number">1</span>);

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Service</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Repository</span>\<span class="hljs-title">UserRepositoryInterface</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> UserRepositoryInterface $userRepository</span>) </span>{}

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getUserDetails</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $id</span>): ?<span class="hljs-title">array</span>
    </span>{
        $user = <span class="hljs-keyword">$this</span>-&gt;userRepository-&gt;getUserById($id);
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">empty</span>($user)) {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
        }
        $user[<span class="hljs-string">'email'</span>] = substr($user[<span class="hljs-string">'email'</span>], <span class="hljs-number">0</span>, <span class="hljs-number">3</span>) . <span class="hljs-string">'****@'</span> . explode(<span class="hljs-string">'@'</span>, $user[<span class="hljs-string">'email'</span>])[<span class="hljs-number">1</span>];
        <span class="hljs-keyword">return</span> $user;
    }
}
</code></pre>
<p>This <code>UserService</code> might look like <strong>just a proxy</strong> because it simply fetches user details, making it seem redundant. However, in real-world scenarios, <strong>service layers handle way more than just CRUD operations</strong>.</p>
<p>In complex applications, the service layer is responsible for <strong>business rules, validations, aggregating data from multiple sources, handling transactions, caching, and orchestrating different dependencies</strong>. The example here is intentionally simple to <strong>demonstrate the structure flow</strong>, making it easier to understand in the context of an article.</p>
<p>While in this case, it only retrieves a user and masks their email, <strong>in production systems</strong>, services could be responsible for <strong>permission checks, event dispatching, sending notifications, or applying domain logic before returning data</strong>. <strong>So even though it looks unnecessary now, service layers become crucial as business complexity grows.</strong></p>
<h3 id="heading-publicindexphp"><code>/public/index.php</code></h3>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">declare</span>(strict_types=<span class="hljs-number">1</span>);

<span class="hljs-keyword">require</span> <span class="hljs-keyword">__DIR__</span> . <span class="hljs-string">'/../vendor/autoload.php'</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">MySQLDatabase</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">PostgreSQLDatabase</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Repository</span>\<span class="hljs-title">UserRepository</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Repository</span>\<span class="hljs-title">InMemoryUserRepository</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Service</span>\<span class="hljs-title">UserService</span>;

$mysqlDatabase = <span class="hljs-keyword">new</span> MySQLDatabase();
$userRepository = <span class="hljs-keyword">new</span> UserRepository($mysqlDatabase);
$userService = <span class="hljs-keyword">new</span> UserService($userRepository);
print_r($userService-&gt;getUserDetails(<span class="hljs-number">1</span>));

$postgresDatabase = <span class="hljs-keyword">new</span> PostgreSQLDatabase();
$userRepositoryPostgres = <span class="hljs-keyword">new</span> UserRepository($postgresDatabase);
$userServicePostgres = <span class="hljs-keyword">new</span> UserService($userRepositoryPostgres);
print_r($userServicePostgres-&gt;getUserDetails(<span class="hljs-number">2</span>));

$inMemoryRepo = <span class="hljs-keyword">new</span> InMemoryUserRepository();
$userServiceMemory = <span class="hljs-keyword">new</span> UserService($inMemoryRepo);
print_r($userServiceMemory-&gt;getUserDetails(<span class="hljs-number">1</span>));
</code></pre>
<p>In <strong>traditional frameworks like Laravel, Symfony, or Slim</strong>, we typically wouldn't manually instantiate dependencies like in this example. Instead, we would leverage the <strong>Dependency Injection (DI) container</strong> to handle object resolution automatically.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>The idea is simple: reduce dependency between components. DI handles how dependencies are provided, while DIP ensures code relies on abstractions, not implementations. Together, they keep things modular and adaptable.</p>
<p>They also tie into Agile values, like adaptability and incremental development, helping teams build systems that evolve easily with changing needs.</p>
<p>In short, DI and DIP work hand-in-hand, laying the groundwork for strong, scalable, and modern software designs.</p>
<h2 id="heading-references">References</h2>
<ul>
<li><p><strong>SOLID Principles</strong> – https://blog.cleancoder.com/uncle-bob/2020/10/18/Solid-Relevance.html</p>
</li>
<li><p><strong>Laravel Dependency Injection Container</strong> – <a target="_blank" href="https://laravel.com/docs/container">https://laravel.com/docs/container</a></p>
</li>
<li><p><strong>Symfony Dependency Injection Component</strong> – https://symfony.com/doc/current/components/dependency_injection.html</p>
</li>
<li><p><strong>Slim Framework Dependency Injection</strong> – https://www.slimframework.com/docs/v4/concepts/di.html</p>
</li>
</ul>
]]></content:encoded></item></channel></rss>