<?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" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Optivem Journal: Clean Architecture]]></title><description><![CDATA[Clean Architecture]]></description><link>https://journal.optivem.com/s/clean-architecture</link><image><url>https://substackcdn.com/image/fetch/$s_!0CjJ!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9abead4c-3f54-46b1-96aa-7033849416df_200x200.png</url><title>Optivem Journal: Clean Architecture</title><link>https://journal.optivem.com/s/clean-architecture</link></image><generator>Substack</generator><lastBuildDate>Thu, 08 Oct 2026 10:17:49 GMT</lastBuildDate><atom:link href="https://journal.optivem.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Valentina Jemuović, Optivem]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[optivem@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[optivem@substack.com]]></itunes:email><itunes:name><![CDATA[Valentina Jemuović]]></itunes:name></itunes:owner><itunes:author><![CDATA[Valentina Jemuović]]></itunes:author><googleplay:owner><![CDATA[optivem@substack.com]]></googleplay:owner><googleplay:email><![CDATA[optivem@substack.com]]></googleplay:email><googleplay:author><![CDATA[Valentina Jemuović]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Clean Architecture: Use Cases with Command Bus]]></title><description><![CDATA[Code Example]]></description><link>https://journal.optivem.com/p/clean-architecture-when-do-you-need-command-bus</link><guid isPermaLink="false">https://journal.optivem.com/p/clean-architecture-when-do-you-need-command-bus</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 02 Oct 2026 06:01:08 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/2e0be3c8-3120-4995-8e0b-f2cc5fd5c342_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128640; Register: <strong><a href="https://optivem.thinkific.com/products/courses/2026-oct-webinar-atdd">Why Your E2E Tests Didn&#8217;t Replace Manual QA</a></strong> (Oct 7)</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-oct-webinar-atdd&quot;,&quot;text&quot;:&quot;Register for free&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/courses/2026-oct-webinar-atdd"><span>Register for free</span></a></p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>You&#8217;ve split your application into one class per use case.</p><p>Then you gave every use case the same interface &#8212; the Command Handler pattern:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;79d2ddce-1619-417b-8af5-7efff8eb75e0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public interface UseCase&lt;TRequest, TResponse&gt; {
    Result&lt;TResponse, UseCaseError&gt; execute(TRequest request);
}</code></pre></div><p>One request in. One result out.</p><p>But you run into issues:</p><h3>&#10071;Problem #1: Cross-cutting concerns duplicated across use cases</h3><p>Logging, transactions, authorization &#8212; many use cases need them, so you end up copying them into each one:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;ade5d40f-738a-4676-b8b0-ccd52e45db37&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public final class PlaceOrder
        implements UseCase&lt;PlaceOrderRequest, PlaceOrderResponse&gt; {

    public Result&lt;PlaceOrderResponse, UseCaseError&gt; execute(
        PlaceOrderRequest request
    ) {
        log.info("PlaceOrder started");
        authorize(Permission.PLACE_ORDER);
        return transactional(() -&gt; {
            // ... the actual use case logic
        });
    }
}</code></pre></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;bfd31d88-8b49-48b6-b068-1f58c4dda643&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public final class CancelOrder
        implements UseCase&lt;CancelOrderRequest, CancelOrderResponse&gt; {

    public Result&lt;CancelOrderResponse, UseCaseError&gt; execute(
        CancelOrderRequest request
    ) {
        log.info("CancelOrder started");
        authorize(Permission.CANCEL_ORDER);
        return transactional(() -&gt; {
            // ... the actual use case logic
        });
    }
}</code></pre></div><p>The same lines repeat in many use cases, and every new use case has to remember them.</p><h3>&#10071;Problem #2 (secondary): Constructors with lots of injection</h3><p>Every use case is its own class, so every controller needs each one it calls injected:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;a72705fd-6f91-4130-9468-33cf78cca590&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public OrderController(
    PlaceOrder placeOrder,
    ViewOrderDetails viewOrderDetails,
    CancelOrder cancelOrder,
    PublishCoupon publishCoupon,
    BrowseCoupons browseCoupons,
    // ... and more with every new use case
) { ... }</code></pre></div><p>And each endpoint calls its own use case directly:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;0e473989-1c16-411a-a091-467916ac0f17&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">@PostMapping("/orders")
public ResponseEntity&lt;?&gt; placeOrder(@RequestBody PlaceOrderRequest request) {
    return mapResult(placeOrder.execute(request));
}</code></pre></div><p>The constructor grows with every use case you add. The wiring becomes noise, and every controller test has to supply them all.</p><h2>&#10060; Poor Solution: Base Classes</h2><p>The tempting fix for <strong>Problem #1</strong> is a BaseUseCase that every use case extends. But as it grows with logging, authorization, transactions, and error handling, every use case inherits all of it &#8212; whether it needs it or not.</p><p>And it does nothing for <strong>Problem #2</strong>: the controller still needs every use case injected.</p><h2>&#9989; Effective Solution: Command Bus &#8212; in Practice</h2>
      <p>
          <a href="https://journal.optivem.com/p/clean-architecture-when-do-you-need-command-bus">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[DDD: Stop Making Your Domain a Data Bag]]></title><description><![CDATA[Your domain needs more than getters]]></description><link>https://journal.optivem.com/p/ddd-stop-making-your-domain-a-data-bag</link><guid isPermaLink="false">https://journal.optivem.com/p/ddd-stop-making-your-domain-a-data-bag</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 18 Sep 2026 06:00:53 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d0cdf30b-791f-44da-8efc-12dbced5ef9e_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>ATDD in Legacy Code: catch regression bugs before production.</strong><br>Hands-on work with your teams. Limited spaces for 2027.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://calendly.com/valentinajemuovic/call&quot;,&quot;text&quot;:&quot;Let's talk&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://calendly.com/valentinajemuovic/call"><span>Let's talk</span></a></p><div><hr></div><p>&#128197; Join our next live course: <strong><a href="https://optivem.thinkific.com/products/courses/2026-oct-atdd">ATDD</a>, <a href="https://optivem.thinkific.com/products/courses/2026-oct-ca">Clean Architecture</a>, <a href="https://optivem.thinkific.com/products/courses/2026-oct-pipelines">Pipelines</a><br></strong>Early bird: <strong>&#8364;100 off with code EARLYBIRD100</strong></p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>Your SubscriptionService has 800 lines.</p><p>It activates subscriptions, handles cancellations, validates plan changes, checks expiration...</p><p>Meanwhile <code>Subscription</code> is just getters and setters.</p><p>That&#8217;s the problem.</p><h2><strong>&#10060;</strong>Your Domain Is Just Data (Anemic Domain)</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;0c00cd6a-ac8a-4139-ac7e-836fc8e49777&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public class Subscription {
    private String customerId;
    private SubscriptionPlan plan;
    private SubscriptionStatus status;
    private LocalDate startedAt;
    private LocalDate expiresAt;
    private BigDecimal price;

    public String getCustomerId() {
        return customerId;
    }

    public void setCustomerId(String customerId) {
        this.customerId = customerId;
    }

    public SubscriptionPlan getPlan() { ... }

    public void setPlan(SubscriptionPlan plan) { ... }

    public SubscriptionStatus getStatus() { ... }

    public void setStatus(SubscriptionStatus status) { ... }

    public LocalDate getStartedAt() { ... }

    public void setStartedAt(LocalDate startedAt) { ... }

    public LocalDate getExpiresAt() { ... }

    public void setExpiresAt(LocalDate expiresAt) { ... }

    public BigDecimal getPrice() { ... }

    public void setPrice(BigDecimal price) { ... }
}</code></pre></div><p>There isn&#8217;t much here.</p><p>The <code>Subscription</code> holds data.</p><p>The business rules live somewhere else:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;ea5dff97-ecf9-468d-869a-dd75aca96edf&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public class SubscriptionService {

    public void cancel(Subscription subscription) {
        if (subscription.getStartedAt().plusDays(7).isBefore(LocalDate.now())) {
            throw new IllegalStateException(
                "Free cancellation is only available during the 7-day trial"
            );
        }

        subscription.setStatus(SubscriptionStatus.CANCELLED);
    }

    public void changePlan(
        Subscription subscription,
        SubscriptionPlan newPlan
    ) {
        if (newPlan == subscription.getPlan()) {
            throw new IllegalArgumentException(
                "Subscription is already on this plan"
            );
        }

        subscription.setPlan(newPlan);
        subscription.setPrice(newPlan.getPrice());
    }

    public void renew(Subscription subscription) {
        if (subscription.getStatus() != SubscriptionStatus.ACTIVE) {
            throw new IllegalStateException(
                "Only active subscriptions can be renewed"
            );
        }

        subscription.setExpiresAt(
            subscription.getExpiresAt().plusMonths(1)
        );
    }
}</code></pre></div><p>At first, this doesn&#8217;t look terrible.</p><p>But keep adding business rules.</p><p>Soon you have:</p><pre><code><code>SubscriptionService
&#9500;&#9472;&#9472; startTrial()
&#9500;&#9472;&#9472; cancel()
&#9500;&#9472;&#9472; changePlan()
&#9500;&#9472;&#9472; renew()
&#9500;&#9472;&#9472; pause()
&#9500;&#9472;&#9472; resume()
&#9500;&#9472;&#9472; extendTrial()
&#9500;&#9472;&#9472; changeBillingCycle()
&#9500;&#9472;&#9472; updatePaymentMethod()
&#9492;&#9472;&#9472; ...</code></code></pre><p>And your <code>Subscription</code> is still just data.</p><p>That&#8217;s where the real problems appear:</p><ul><li><p><strong>Rules get scattered</strong> across services</p></li><li><p><strong>The same rules get duplicated</strong> in different places</p></li><li><p><strong>Rules can be bypassed</strong> by calling setters directly</p></li><li><p><strong>Changing a business rule means hunting through services</strong> to find everywhere it is implemented</p></li><li><p><strong>The Subscription object can&#8217;t protect its own state</strong></p></li></ul><h2>&#9989;Your Domain Owns the Rules (Rich Domain)</h2>
      <p>
          <a href="https://journal.optivem.com/p/ddd-stop-making-your-domain-a-data-bag">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Clean Architecture: One Class Per Use Case Is NOT Enough]]></title><description><![CDATA[Code Example]]></description><link>https://journal.optivem.com/p/clean-architecture-one-class-per-use-case-is-not-enough</link><guid isPermaLink="false">https://journal.optivem.com/p/clean-architecture-one-class-per-use-case-is-not-enough</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 10 Sep 2026 06:01:29 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d70a3b4c-f61a-453c-9f44-26a6dfc6378b_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>ATDD in Legacy Code: catch regression bugs before production.</strong><br>Hands-on work with your teams. Limited spaces for 2027.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://calendly.com/valentinajemuovic/call&quot;,&quot;text&quot;:&quot;Let's talk&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://calendly.com/valentinajemuovic/call"><span>Let's talk</span></a></p><div><hr></div><p>&#128197; Join our next live course: <strong><a href="https://optivem.thinkific.com/products/courses/2026-oct-atdd">ATDD</a>, <a href="https://optivem.thinkific.com/products/courses/2026-oct-ca">Clean Architecture</a>, <a href="https://optivem.thinkific.com/products/courses/2026-oct-pipelines">Pipelines</a><br></strong>Early bird: <strong>&#8364;100 off with code EARLYBIRD100</strong></p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>You already know a fat service class is a bad place for all your business logic.</p><p>So you split it up.</p><p>One class per use case, like in<span> </span><a href="https://github.com/sandromancuso/cleancoders_openchat/tree/openchat-unclebob/src/main/java/org/openchat/usecases">Uncle Bob's example</a><span>:</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;20f8f782-68e0-4ba8-899f-d11ba615db39&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public class PlaceOrder {
    public PlaceOrderResponse placeOrder(PlaceOrderRequest request) { ... }
}

public class ViewOrderDetails {
    public ViewOrderDetailsResponse getOrder(String orderNumber) { ... }
}

public class CancelOrder {
    public void cancelOrder(String orderNumber) { ... }
}

public class PublishCoupon {
    public Coupon createCoupon(String couponCode, BigDecimal discountRate,
                                Instant validFrom, Instant validTo, Integer usageLimit) { ... }
}

public class BrowseCoupons {
    public List&lt;Coupon&gt; getAllCoupons() { ... }
}</code></pre></div><p>Looks better.</p><p>But splitting the service into separate classes didn&#8217;t fix everything.</p><p>Here&#8217;s what&#8217;s still wrong:</p><h3>&#10060; Entities leak across the boundary &#8212; but only sometimes</h3><p><code>PublishCoupon</code> returns a <code>Coupon</code>.</p><p><code>BrowseCoupons</code> returns <code>List&lt;Coupon&gt;</code>.</p><p>But <code>PlaceOrder</code> and <code>ViewOrderDetails</code> return response DTOs.</p><p>So... can entities cross the use case boundary or not?</p><p>You can&#8217;t tell. It depends on which class you&#8217;re looking at.</p><h3>&#10060; A bug is hiding in PublishCoupon</h3><p>If you accidently swap <code>validFrom</code> and <code>validTo</code> &#8212; it still compiles, because they&#8217;re the same type.</p><p>But now your coupon has the wrong validity period &#8212; it&#8217;s backwards.</p><p><code>CouponController</code> already has a <code>PublishCouponRequest</code> with all the values. Instead of passing that request to <code>PublishCoupon</code>, it pulls the values out and passes them one by one.</p><p>That's how you end up with a bug like this.</p><h3>&#10060; Expected outcomes are thrown, not declared</h3><p>Order not found, order can&#8217;t be delivered &#8212; are thrown as exceptions instead of declared.</p><p>These aren't bugs. They're things that can happen when the use case runs.</p><p>But they&#8217;re in a shared exception handler that has grown a case for every use case's failures.</p><p>The method says &#8220;I return an order.&#8221; It doesn't tell you that &#8220;order not found&#8221; or &#8220;order can't be delivered&#8221; can happen.</p><h3>&#10060; Calling each use case is different</h3><p><code>PlaceOrder</code> takes a request object, <code>ViewOrderDetails</code>/<code>CancelOrder</code> take a raw <code>String</code>, <code>PublishCoupon</code> takes five loose parameters, <code>BrowseCoupons</code> takes nothing.</p><p>There&#8217;s no obvious answer to: &#8220;How do I call a use case?&#8221;</p><h3>&#10060; The class says one thing, the method says another</h3><p>ViewOrderDetails.getOrder()</p><p>The class says &#8220;view the order details&#8221;, but the method says &#8220;get the order.&#8221;</p><p>Same problem here: <code>PublishCoupon.createCoupon()</code></p><p>The class says &#8220;publish a coupon&#8221;, but the method says &#8220;create a coupon&#8221;.</p>
      <p>
          <a href="https://journal.optivem.com/p/clean-architecture-one-class-per-use-case-is-not-enough">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Clean Architecture: Use Cases Should NOT Catch Stripe Exceptions]]></title><description><![CDATA[Error Handling - Application layer]]></description><link>https://journal.optivem.com/p/clean-architecture-use-cases-should-not-catch-stripe-exceptions</link><guid isPermaLink="false">https://journal.optivem.com/p/clean-architecture-use-cases-should-not-catch-stripe-exceptions</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 27 Aug 2026 06:01:02 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/ffd1a862-8f06-4cdd-b6bf-98e116e8ed6d_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>ATDD in Legacy Code: catch regression bugs before production.</strong><br>Hands-on work with your teams. Limited spaces for 2027. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://calendly.com/valentinajemuovic/call&quot;,&quot;text&quot;:&quot;Let&#8217;s talk &#8594;&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://calendly.com/valentinajemuovic/call"><span>Let&#8217;s talk &#8594;</span></a></p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><blockquote><p>&#8220;What should I do when a use case fails?&#8221;</p></blockquote><p>But a use case can fail for completely different reasons.</p><p>Maybe:</p><ul><li><p>the customer doesn&#8217;t exist</p></li><li><p>the order hasn&#8217;t been paid for</p></li><li><p>the order cannot be checked out</p></li><li><p>the payment provider is unavailable</p></li></ul><p>Should the use case handle all of them?</p><h2>Error Handling at the Application layer</h2><p>This layer contains your use cases:</p><ul><li><p>checkout order</p></li><li><p>refund payment</p></li><li><p>create shipment</p></li><li><p>reserve seat</p></li></ul><p>This is where application rules are enforced.</p><p>Examples:</p><ul><li><p>cannot checkout if another checkout is already in progress</p></li><li><p>cannot place an order for a customer that doesn&#8217;t exist</p></li><li><p>cannot apply the same coupon twice</p></li></ul><p>These are rules that the use case needs to check because because the information isn't inside the <code>Order</code> itself.</p><p>E.g. an <code>Order</code> can&#8217;t know whether the customer exists or another checkout is already in progress, or whether a coupon had already been used. The use case can check that information.</p><p>These are not technical failures. A database connection dropping is a completely different kind of problem.</p><h2>&#10060; Use cases should NOT handle infrastructure exceptions</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;d56c13cd-735d-40ef-a4f4-ae4ac43d75c5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public void checkout(Order order) {

    try {
        paymentGateway.charge(order);
        orderRepository.save(order);
        shippingGateway.notifyWarehouse(order);

    } catch (StripeException e) {
        throw new RuntimeException("Payment failed");
    } catch (SQLException e) {
        throw new RuntimeException("Database error");
    } catch (HttpException e) {
        throw new RuntimeException("Warehouse call failed");
    }
}</code></pre></div><p>Now the use case is tied to three unrelated technologies:</p><ul><li><p><code>StripeException</code> &#8212; Stripe&#8217;s SDK</p></li><li><p><code>SQLException</code> &#8212; JDBC / the database</p></li><li><p><code>HttpException</code> &#8212; the HTTP client</p></li></ul><p>Swap Stripe for PayPal, JDBC for an ORM, or the HTTP client for a message queue, and you now have to change the use case too.</p><p><strong>But the checkout rules haven&#8217;t changed.</strong></p><p>That&#8217;s the problem.</p><div><hr></div><h2>&#9989; Use cases should ONLY handle application errors</h2>
      <p>
          <a href="https://journal.optivem.com/p/clean-architecture-use-cases-should-not-catch-stripe-exceptions">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Clean Architecture: Stop Avoiding Every Framework Dependency]]></title><description><![CDATA[Code Example]]></description><link>https://journal.optivem.com/p/clean-architecture-stop-avoiding-every-framework-dependency</link><guid isPermaLink="false">https://journal.optivem.com/p/clean-architecture-stop-avoiding-every-framework-dependency</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 06 Aug 2026 06:01:16 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/50e055a1-dc1f-46d0-a7e6-fa07781c625c_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128197; Join me: <strong><a href="https://optivem.thinkific.com/products/live_events/clean-architecture-for-backend-developers">Clean Architecture for Backend Developers</a></strong> on Wed 26th Aug, 5:00 - 6:30 PM (CEST)</p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>The first rule Uncle Bob lists in <a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html">The Clean Architecture</a> is:</p><blockquote><p>&#8220;Independent of Frameworks. The architecture does not depend on the existence of some library of feature laden software. This allows you to use such frameworks as tools, rather than having to cram your system into their limited constraints.&#8221;</p></blockquote><p>Frameworks as tools. That&#8217;s reasonable.</p><p>But it usually gets repeated as something much stricter.</p><p>No annotations. No libraries. No framework classes.</p><p>Nothing.</p><p>The domain must be completely pure.</p><p><strong>I disagree.</strong></p><p>Not because frameworks should control your business logic.</p><p>They shouldn&#8217;t.</p><p>But not every framework dependency is automatically a problem.</p><p>Some dependencies genuinely make your code harder to change.</p><p>Others simply make your code easier to write.</p><p><strong>There&#8217;s a huge difference between:</strong></p><ul><li><p><code>@Service</code></p></li><li><p><code>@Component</code></p></li><li><p><code>@Repository</code></p></li></ul><p>and</p><ul><li><p><code>@Entity</code></p></li><li><p><code>@Id</code></p></li><li><p><code>@Column</code></p></li><li><p><code>@ManyToOne</code></p></li></ul><p>The first barely affects your domain. The second fundamentally shapes it.</p><p>The strict reading treats them the same.</p><p>A class with an annotation, a service managed by Spring, a repository injected by a framework &#8212; all bad.</p><p>The result?</p><p>They end up building systems that are technically <strong>&#8220;pure&#8221; but harder to work with</strong>.</p><p>Because the goal of architecture is not to remove every dependency. It is to remove the dependencies that dictate how your business rules are written.</p><h2>A framework dependency is not automatically harmful</h2>
      <p>
          <a href="https://journal.optivem.com/p/clean-architecture-stop-avoiding-every-framework-dependency">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Fragile Unit Tests in Clean Architecture]]></title><description><![CDATA[So tightly coupled to the domain that they break on every refactor - even when behavior never changes]]></description><link>https://journal.optivem.com/p/fragile-unit-tests-in-clean-architecture</link><guid isPermaLink="false">https://journal.optivem.com/p/fragile-unit-tests-in-clean-architecture</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 31 Jul 2026 06:01:03 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d1843b9b-a5e3-499b-87b7-fe60c121f3d4_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128197; Join me: <strong><a href="https://optivem.thinkific.com/products/live_events/clean-architecture-for-backend-developers">Clean Architecture for Backend Developers</a></strong> on Wed 26th Aug, 5:00 - 6:30 PM (CEST)</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/live_events/clean-architecture-for-backend-developers&quot;,&quot;text&quot;:&quot;&#127942; Register now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/live_events/clean-architecture-for-backend-developers"><span>&#127942; Register now</span></a></p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h2>How Uncle Bob writes unit tests in Clean Architecture</h2><p>Years ago, I wrote unit tests in Clean Architecture the way Uncle Bob does. Unit tests target use cases and verify the stateful outcomes on repositories and gateways through test doubles.</p><p>In the Clean Coders <a href="https://cleancoders.com/episode/comparativeDesign-episode-1">comparative design series</a>, Uncle Bob shows that he writes <a href="https://github.com/sandromancuso/cleancoders_openchat/tree/openchat-unclebob/src/test/java/org/openchat/usecases">unit tests targeting use cases</a>, not targeting the domain. That&#8217;s why for each use case class he has a corresponding unit test class - a 1:1 mapping between use cases and unit tests.</p><p><em>You&#8217;ll notice that even though he targets use cases and not the domain, the tests are still coupled to the domain - they assert directly on domain entities. That turns out to be a challenge, as we&#8217;ll see later in this article.</em></p><p>E.g. for the use case <code>PostDocument</code> here&#8217;s the unit test <code>PostDocumentTest</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;d84945b2-716d-4cd9-94ad-d0a6aaa02982&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">@Test
public void canPostAnyDocument() throws Exception {
    LocalDateTime now = LocalDateTime.now();
    Document createdDocument = postDocument.post(&#8220;username&#8221;, &#8220;text&#8221;);
    Document fetchedDocument = UseCaseContext.repository.getDocument(createdDocument.id);
    assertThat(fetchedDocument.username).isEqualTo(&#8220;username&#8221;);
    assertThat(fetchedDocument.text).isEqualTo(&#8220;text&#8221;);
    assertThat(fetchedDocument.id).isEqualTo(createdDocument.id);
    assertThat(fetchedDocument.dateTime).isEqualTo(createdDocument.dateTime);
}</code></pre></div><h2>Real-life example: eShop - placing orders</h2><p>Now let&#8217;s illustrate Uncle Bob&#8217;s approach to unit tests on a more realistic example - the eShop.</p><p>A customer places an order. The <code>PlaceOrder</code> use case takes a SKU and a quantity, looks up the product, checks stock, calculates the total order price, saves the order, notifies the customer, and returns the order number. It reaches the outside world through <strong>repository interfaces</strong> and <strong>gateway interfaces</strong>.</p><p>The domain holds two entities - <code>Order</code> and <code>Product</code> - plus value objects that own invariants a primitive can&#8217;t: <code>Quantity</code> (must be positive), <code>Money</code> (currency and arithmetic), <code>OrderNumber</code> (the format we generate). Those <strong>repository and gateway interfaces</strong> are part of the domain too - abstractions the use case owns and depends on, implemented out at the infrastructure edge.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rzfc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56400865-1465-41ff-9d6a-a6991928657d_1488x940.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rzfc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56400865-1465-41ff-9d6a-a6991928657d_1488x940.png 424w, https://substackcdn.com/image/fetch/$s_!rzfc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56400865-1465-41ff-9d6a-a6991928657d_1488x940.png 848w, https://substackcdn.com/image/fetch/$s_!rzfc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56400865-1465-41ff-9d6a-a6991928657d_1488x940.png 1272w, https://substackcdn.com/image/fetch/$s_!rzfc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56400865-1465-41ff-9d6a-a6991928657d_1488x940.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rzfc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56400865-1465-41ff-9d6a-a6991928657d_1488x940.png" width="1456" height="920" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/56400865-1465-41ff-9d6a-a6991928657d_1488x940.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:920,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:85232,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://journal.optivem.com/i/208109632?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56400865-1465-41ff-9d6a-a6991928657d_1488x940.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rzfc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56400865-1465-41ff-9d6a-a6991928657d_1488x940.png 424w, https://substackcdn.com/image/fetch/$s_!rzfc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56400865-1465-41ff-9d6a-a6991928657d_1488x940.png 848w, https://substackcdn.com/image/fetch/$s_!rzfc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56400865-1465-41ff-9d6a-a6991928657d_1488x940.png 1272w, https://substackcdn.com/image/fetch/$s_!rzfc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56400865-1465-41ff-9d6a-a6991928657d_1488x940.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Unit testing the PlaceOrder use case</h2><p>So when we adopt Clean Architecture, we write unit tests against the use case. The test class comes out like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;763e2c89-3522-4a99-aeb5-e85cbdaf78e5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">class PlaceOrderUnitTest {

    ...

    @BeforeEach
    void setUp() {
        orderRepository = new FakeOrderRepository();
        productGateway = new StubProductGateway();
        orderNumberGateway = new StubOrderNumberGateway();
        notificationGateway = new SpyNotificationGateway();

        placeOrder = new PlaceOrder(
            orderRepository, productGateway, orderNumberGateway, notificationGateway);
    }

    @Test
    void calculatesTheTotal() {
        productGateway.willReturn(new Product(&#8220;ABC&#8221;, Money.of(20), Quantity.of(10)));
        orderNumberGateway.willReturn(OrderNumber.of(&#8220;ORD-1001&#8221;));

        var request = new PlaceOrderRequest();
        request.setSku(&#8220;ABC&#8221;);
        request.setQuantity(4);

        var result = placeOrder.execute(request);

        assertThat(result.isSuccess()).isTrue();
        var addedOrder = orderRepository.getOrder(OrderNumber.of(&#8220;ORD-1001&#8221;));
        assertThat(addedOrder.getSku()).isEqualTo(&#8220;ABC&#8221;);
        assertThat(addedOrder.getTotalPrice()).isEqualTo(Money.of(80));
    }

    @Test
    void rejectsAnOrderThatExceedsStock() {
        productGateway.willReturn(new Product(&#8220;ABC&#8221;, Money.of(20), Quantity.of(10)));

        var request = new PlaceOrderRequest();
        request.setSku(&#8220;ABC&#8221;);
        request.setQuantity(15);

        var result = placeOrder.execute(request);

        assertThat(result.isSuccess()).isFalse();
        assertThat(orderRepository.isEmpty()).isTrue();
    }

    ...
}</code></pre></div><h3>&#128683;Maintenance nightmare - refactoring the domain breaks unit tests</h3><p>The tests are green. 100% code coverage, 100% mutation coverage.</p><p>But then, there comes a day when we need to refactor the domain. That&#8217;s when the nightmare starts...</p>
      <p>
          <a href="https://journal.optivem.com/p/fragile-unit-tests-in-clean-architecture">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Clean Architecture vs Vertical Slice Architecture]]></title><description><![CDATA[&#8220;Why should I jump between ten different files just to understand one feature? Put everything for that feature in one place.&#8221;]]></description><link>https://journal.optivem.com/p/clean-architecture-vs-vertical-slice-architecture</link><guid isPermaLink="false">https://journal.optivem.com/p/clean-architecture-vs-vertical-slice-architecture</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 24 Jul 2026 14:17:10 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/044b3992-ecc8-49da-990b-1b7dd0a6a646_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128197; Join me: <strong><a href="https://optivem.thinkific.com/products/live_events/clean-architecture-for-backend-developers">Clean Architecture for Backend Developers</a></strong> on Wed 26th Aug, 5:00 - 6:30 PM (CEST)</p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>Every few months someone declares that Clean Architecture is dead.</p><p>The new answer?</p><p>Vertical Slice Architecture.</p><p>Usually it goes something like this:</p><blockquote><p>&#8220;Why should I jump between ten different files just to understand one feature? Put everything for that feature in one place.&#8221;</p></blockquote><p>Instead of splitting code into controllers, use cases, domain objects and repositories...</p><p>...put everything for <code>PlaceOrder</code> in one place.</p><p>One handler.</p><p>One folder.</p><p>One feature.</p><h2>"Everything is in one place"</h2><p>This is the promise of Vertical Slice Architecture.</p><pre><code><code>PlaceOrder
&#9500;&#9472;&#9472; HTTP Request Validation
&#9500;&#9472;&#9472; Inventory Rules
&#9500;&#9472;&#9472; Discount Rules
&#9500;&#9472;&#9472; Price Calculation
&#9500;&#9472;&#9472; SQL Query
&#9500;&#9472;&#9472; ORM Mapping
&#9500;&#9472;&#9472; SAP Integration
&#9492;&#9472;&#9472; HTTP Response Mapping</code></code></pre><p>Finance wants to change the discount rules.</p><p>Which parts are relevant?</p><pre><code><code>PlaceOrder
&#9500;&#9472;&#9472; HTTP Request Validation
&#9500;&#9472;&#9472; Inventory Rules
&#9500;&#9472;&#9472; Discount Rules &#11088;
&#9500;&#9472;&#9472; Price Calculation
&#9500;&#9472;&#9472; SQL Query
&#9500;&#9472;&#9472; ORM Mapping
&#9500;&#9472;&#9472; SAP Integration
&#9492;&#9472;&#9472; HTTP Response Mapping</code></code></pre><p>Only one item matters. </p><p>The rest is noise.</p><p>But to find it, you have to going through request validation, persistence, mappings and integrations that have <strong>nothing to do with discount rules</strong>.</p><p>That&#8217;s the mental overhead.</p><p>Because the business logic is <strong>mixed in</strong> with everything else.</p><h2>Don&#8217;t mix different concerns</h2><p>With Clean Architecture:</p><pre><code><code>Presentation Layer
&#9500;&#9472;&#9472; REST API Controllers
&#9500;&#9472;&#9472; HTTP Request Validation
&#9492;&#9472;&#9472; HTTP Response Mapping

Application Layer
&#9500;&#9472;&#9472; Place Order Use Case
&#9492;&#9472;&#9472; Cancel Order Use Case

Domain Layer
&#9500;&#9472;&#9472; Inventory Rules
&#9500;&#9472;&#9472; Discount Rules
&#9492;&#9472;&#9472; Price Calculation

Infrastructure Layer
&#9500;&#9472;&#9472; SQL Query
&#9500;&#9472;&#9472; ORM Mapping
&#9492;&#9472;&#9472; SAP Integration</code></code></pre><p>There&#8217;s more files, but&#8230;</p><p>Finance wants to change the discount rules.</p><p>You <strong>immediately know where to look.</strong></p><pre><code><code>Domain Layer
&#9500;&#9472;&#9472; Inventory Check
&#9500;&#9472;&#9472; Discount Rules &#11088;
&#9492;&#9472;&#9472; Price Calculation</code></code></pre><p>Done.</p><p>You don't have to mentally filter out SQL, HTTP, ORM mappings and SAP integration first.</p><p>Because the business logic is <strong>isolated</strong>.</p><h2>The context you DON&#8217;T need</h2><h3>&#10060; Vertical Slice</h3><p>&#128269;&#65038; <strong>Find discount rule</strong></p><ul><li><p>Open PlaceOrder</p></li><li><p>Scroll past request validation</p></li><li><p>Scroll past database code</p></li><li><p>Scroll past SAP mapping</p></li><li><p>Finally change discount rule</p></li></ul><h3>&#9989; Clean Architecture</h3><p>&#128269;&#65038; <strong>Find discount rule</strong></p><ul><li><p>Open Domain Layer</p></li><li><p>Change discount rule</p></li></ul><h1>&#128161;Real-life Example: Ordering System</h1><p>Finance doesn't just ask for one discount rule.</p><p>The <strong>rules start growing</strong>:</p><ul><li><p>Premium customers get 10% off</p></li><li><p>Orders above &#8364;1,000 get another 5% discount</p></li><li><p>Some products are excluded</p></li><li><p>Discounts cannot be combined</p></li><li><p>Regional promotions override standard discounts</p></li></ul><p>Now you need to change the discount calculation.</p><h2>&#10060; Vertical Slice Architecture</h2>
      <p>
          <a href="https://journal.optivem.com/p/clean-architecture-vs-vertical-slice-architecture">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Clean Architecture Mistake: ORM ≠ Domain]]></title><description><![CDATA[Do NOT confuse ORM entities with domain entities.]]></description><link>https://journal.optivem.com/p/clean-architecture-mistake-orm-domain</link><guid isPermaLink="false">https://journal.optivem.com/p/clean-architecture-mistake-orm-domain</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 09 Jul 2026 06:02:30 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d3e29bda-7d3c-42c3-8b02-21b4c8c978d0_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>Your ORM has entities.</p><p>Clean Architecture talks about entities.</p><p>They sound like the same thing.</p><p>They&#8217;re not.</p><h2>&#10060; Your ORM Becomes Your Domain</h2><p>You create an ORM entity (JPA entity) because you need to store orders.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;88aa993c-13cb-4128-8a41-e2ab127df434&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">@Entity
class Order {
    @Id
    private Long id;

    private OrderStatus status;
    private int quantity;
    private BigDecimal unitPrice;
}</code></pre></div><p>And an ORM repository (JPA repository) to save and load it:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;dfa1068a-99a5-49fa-9d99-2d84818374ff&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">interface OrderJpaRepository extends JpaRepository&lt;Order, Long&gt; {
    // findById and save come out-of-the-box:
    // Optional&lt;Order&gt; findById(Long id);
    // Order save(Order order);
}</code></pre></div><p>So far, so good.</p><p>Its job is persistence.</p><p><span>Then you need to place an order &#8212; and you expect an order to be in a valid state before it is saved. So you give </span><code>Order</code><span> a constructor that refuses to build an invalid order &#8212; e.g. one with a non-positive quantity or a null unit price:</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;2bc989b2-2f18-4eae-9844-bd79e39900a3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">Order(int quantity, BigDecimal unitPrice) {
    if (quantity &lt;= 0) {
        throw new RuntimeException("quantity must be positive");
    }

    if (unitPrice == null) {
        throw new RuntimeException("unit price is required");
    }

    this.status = OrderStatus.PLACED;
    this.quantity = quantity;
    this.unitPrice = unitPrice;
}</code></pre></div><p>Now an <code>Order</code> cannot exist in a broken state.</p><p>Your use case builds one through it:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;093d091d-d908-44f9-a9ba-14f4bd7e6d00&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">class PlaceOrder {

    void execute(int quantity, BigDecimal unitPrice) {
        var order = new Order(quantity, unitPrice);
        orderRepository.save(order);
    }
}</code></pre></div><p>And your use case depends on it.</p><p>Your ORM entity has become your domain entity.</p><p>It looks fine. It compiles. It runs.</p><p>Except &#8212; declaring that constructor removed Java&#8217;s free no-arg one. And JPA cannot live without one.</p><p>That&#8217;s worse than a plain compile error. It compiles, it starts up, and it even saves correctly &#8212; then breaks the first time someone reads the order back. Nothing warns you until then.</p><p>So you&#8217;re forced to add it back:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;38e5a664-86e3-42f6-ac42-6c624af01000&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">protected Order() {}   // required by JPA &#8212; status null, quantity 0, unitPrice null</code></pre></div><p>You wrote a constructor to reject invalid orders.</p><p>JPA makes you add a no-arg one next to it &#8212; one that builds an order with a null status, zero quantity, no price.</p><p>Marking it <code>protected</code> doesn&#8217;t help. When Hibernate loads a row, it ignores every access modifier by using reflection: it invokes that no-arg constructor, then sets the fields one by one.</p><p>Your validating constructor never runs. Your checks never execute.</p><p>An order no business would accept &#8212; and Hibernate builds one on every load.</p><p>Here is the problem.</p><p>An object can only enforce its invariants in one place: its <strong>constructor</strong>. JPA bypasses it &#8212; it forces a no-arg constructor, then sets the fields by reflection, skipping your real constructor on every load.</p><p>So the real problem is not that &#8220;your domain is coupled.&#8221;</p><p>It&#8217;s this:</p><p><strong>This entity cannot guarantee it's always valid (enforce business rules).</strong></p><p>Your validation only runs when you call the constructor yourself. JPA never calls it &#8212; on every load it constructs the object empty and sets the fields directly.</p><p>And every transition you validate later &#8212; cancel, ship, and so on &#8212; then runs on an object that was never validated in the first place.</p><p>The code compiles, it runs, and every invariant you thought you wrote is optional.</p><p>And it doesn&#8217;t stop at <code>Order</code>.</p><p>Use a <code>Money</code> value object instead of <code>BigDecimal</code> &#8212; immutable, always valid, exactly what DDD wants:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;f619f5cc-1462-4dd2-bff8-c18deaef63f2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">final class Money {
    private final BigDecimal amount;

    Money(BigDecimal amount) {
        if (amount == null) {
            throw new RuntimeException("amount is required");
        }

        this.amount = amount;
    }
}</code></pre></div><p><span>To persist it, JPA needs it as </span><code>@Embeddable</code><span>. And </span><code>@Embeddable</code><span> demands the same price: drop </span><code>final</code><span>, add a no-arg constructor.</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;d049ea88-4152-4265-9ef5-0a4d3550ac94&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">@Embeddable
class Money {                    // no longer final
    private BigDecimal amount;   // no longer final

    protected Money() {}         // required by JPA &#8212; amount null

    Money(BigDecimal amount) {
        if (amount == null) {
            throw new RuntimeException("amount is required");
        }

        this.amount = amount;
    }
}</code></pre></div><p>Your always-valid <code>Money</code> can now be built with a null amount too.</p><p>The trap isn&#8217;t contained to one class. It spreads to every value object you embed.</p><p>Notice what&#8217;s happening. You&#8217;re no longer modelling the domain &#8212; you&#8217;re shaping it to fit the ORM. You drop <code>final</code>, add constructors you&#8217;d never write, expose state you meant to hide &#8212; not because the business asked for it, but because the ORM expects it that way.</p><p>You end up violating the very principles the domain is supposed to protect, encapsulation first among them, just to keep the persistence framework happy.</p><p>That is <strong>not</strong> Clean Architecture.</p><div><hr></div><p>Want to avoid the ORM trap?</p><p>&#128197; Join me: <strong><a href="https://optivem.thinkific.com/products/live_events/clean-architecture-for-backend-developers">Clean Architecture for Backend Developers</a></strong> on Wed 26th Aug, 5:00 - 6:30 PM (CEST)</p><div><hr></div><h2>&#9989;Domain &#8800; ORM</h2>
      <p>
          <a href="https://journal.optivem.com/p/clean-architecture-mistake-orm-domain">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Your Architecture Doesn't Rot Overnight]]></title><description><![CDATA[As deadlines become tighter, convenience starts winning.]]></description><link>https://journal.optivem.com/p/your-architecture-doesnt-rot-overnight</link><guid isPermaLink="false">https://journal.optivem.com/p/your-architecture-doesnt-rot-overnight</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Tue, 07 Jul 2026 06:00:37 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/c5857b79-d8b2-4a92-bb8d-b34384b1752a_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><span>&#128075; </span><em><span>Hello, this is Valentina with the free edition of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply </span><a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a><span>.</span></em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h2>Every project starts simple</h2><p>Most enterprise applications begin with relatively simple requirements.</p><p>Creating records.</p><p>Updating data.</p><p>Calling external systems.</p><p>Displaying results.</p><p>At this stage, <strong>almost any architecture works</strong>. The codebase is small, everyone understands it, and adding new features is straightforward.</p><p>Then the project grows.</p><h2>&#8220;Just this once&#8221;</h2><p>A business rule needs data that&#8217;s already available in the repository.</p><p>Instead of passing it back to the use case, the rule is implemented directly in the repository.</p><p><strong>It&#8217;s only a few lines.</strong></p><p>No one wants to create another object or move data around.</p><p>The feature ships.</p><p>A few weeks later, another feature needs something similar.</p><p>The repository already knows about the data, so another business rule is added there.</p><p>Still reasonable.</p><p>Still working.</p><p>Still &#8220;just this once.&#8221;</p><h2>Convenience becomes the architecture</h2><p>As deadlines become tighter, convenience starts winning.</p><p>A controller performs a quick permission check because it&#8217;s only needed there.</p><p>A service calls another service because it avoids duplicating code.</p><p>An external API client starts making business decisions because it already has the response.</p><p>A repository calculates values because it has all the necessary information.</p><p><strong>None of this breaks the application.</strong></p><p>But it weakens the boundaries between business logic and infrastructure.</p><h2>The cost doesn&#8217;t appear immediately</h2><p>This is why architectural decay is so difficult to notice.</p><p>The application still works.</p><p>Tests still pass.</p><p>Deployments continue.</p><p>The problem appears months later.</p><p>A business rule needs changing.</p><p>Now the team isn&#8217;t sure where that rule actually lives.</p><p>Part of it is in a controller.</p><p>Part is inside a repository.</p><p>Another piece exists in an API adapter.</p><p>There&#8217;s similar logic somewhere else too&#8212;but no one knows whether it&#8217;s safe to change.</p><p>A feature that should have taken an hour becomes an afternoon of investigation.</p><p>Not because the rule is complicated.</p><p>Because the architecture is a <strong>Big Ball of Mud.</strong></p><p>You&#8217;re told: &#8220;Why is it taking so long? It&#8217;s just a simple requirement.&#8221;</p><h2>The trap is delaying</h2><p>Most teams recognize when the codebase is becoming harder to change.</p><p>They simply postpone doing anything about it.</p><p>&#8220;We&#8217;ll clean it up later.&#8221;</p><p>&#8220;We&#8217;ll refactor after this release.&#8221;</p><p>&#8220;We don&#8217;t have time right now.&#8221;</p><p>Meanwhile, every new feature adds another dependency, another shortcut, another place where business logic leaks into infrastructure.</p><p>Eventually, everyone agrees the system needs refactoring.<br>It&#8217;s just too difficult to start.</p><p>Join the live training session:</p><p><strong><a href="https://optivem.thinkific.com/products/live_events/clean-architecture-for-backend-developers">Clean Architecture for Backend Developers</a></strong></p><p><span>&#128467; Aug 26<br>&#9200; 5:00&#8211;6:30 PM (CEST)</span></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/live_events/clean-architecture-for-backend-developers&quot;,&quot;text&quot;:&quot;&#127942;Register now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/live_events/clean-architecture-for-backend-developers"><span>&#127942;Register now</span></a></p>]]></content:encoded></item><item><title><![CDATA[Clean Architecture: Controllers Should NOT Catch SQL Exceptions]]></title><description><![CDATA[Error Handling - API layer]]></description><link>https://journal.optivem.com/p/clean-architecture-error-handling-api-layer</link><guid isPermaLink="false">https://journal.optivem.com/p/clean-architecture-error-handling-api-layer</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 29 May 2026 14:25:43 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/37ecdadf-8f7d-4131-8262-fa2b9de94335_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><blockquote><p>&#8220;How to handle errors properly?&#8221;</p></blockquote><p>You&#8217;ve seen:</p><ul><li><p><code>SQLException</code> and <code>DataAccessException</code> caught <em>inside controllers</em></p></li><li><p><code>catch (Exception e)</code> returning a hand-rolled <code>500</code></p></li><li><p>giant <code>try/catch</code> blocks wrapping every endpoint</p></li><li><p><code>RuntimeException</code> thrown and swallowed at random</p></li></ul><p>And nobody agrees on the &#8220;right&#8221; way to do it.</p><p>Start where those mistakes happen &#8212; the API layer &#8212; and get one rule right: a controller turns errors into HTTP responses, and it should never reach for a database or framework exception to do it.</p><h2>Error Handling at the API layer</h2><p>This layer should turn errors into HTTP responses.</p><p>Examples:</p><ul><li><p>HTTP status codes</p></li><li><p>error messages</p></li><li><p>API response bodies</p></li></ul><h2>&#10060; Controllers should NOT handle database/framework errors</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;6c3d3463-9464-4781-8777-2fb547cb1a6f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">@GetMapping("/orders")
public ResponseEntity&lt;?&gt; getOrders() {

    try {
        return ResponseEntity.ok(orderRepository.findAll());
    } catch (DataAccessException e) {
        return ResponseEntity.status(500).body("Database error");
    }
}</code></pre></div><p>That&#8217;s the wrong place for database exception handling.</p><div><hr></div><p>&#128073; I&#8217;m running a live workshop where we walk through a working e-shop example with a pipeline architecture, so you can see how it works in practice.</p><p><strong>No rebuild tricks. No &#8220;it passed CI but broke anyway&#8221; surprises.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-06-pipeline-workshop&quot;,&quot;text&quot;:&quot;Join Pipelines Workshop &#8594;&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/courses/2026-06-pipeline-workshop"><span>Join Pipelines Workshop &#8594;</span></a></p><p>&#8364;100 off with code EARLYBIRD100 &#8212; limited spots.</p><div><hr></div><h2>&#9989; The API layer should turn errors into responses &#8212; via a global exception handler</h2>
      <p>
          <a href="https://journal.optivem.com/p/clean-architecture-error-handling-api-layer">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Clean Architecture: Do NOT Inject Loggers Everywhere]]></title><description><![CDATA[Code Example]]></description><link>https://journal.optivem.com/p/clean-architecture-do-not-inject-loggers-everywhere</link><guid isPermaLink="false">https://journal.optivem.com/p/clean-architecture-do-not-inject-loggers-everywhere</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 07 May 2026 06:01:18 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/79b41787-cb3a-4231-8d5c-25c0cd0285a2_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>Logging injected directly into use cases, services and handlers is justified as:</p><ul><li><p>&#8220;we need debugging&#8221;</p></li><li><p>&#8220;we need traceability&#8221;</p></li><li><p>&#8220;we need observability&#8221;</p></li></ul><p>But&#8230;</p><h2>&#10060;Logger in every use case</h2><ul><li><p>every use case now knows about logging</p></li><li><p>business logic is mixed with runtime reporting</p></li><li><p>logs become part of how code is &#8220;explained&#8221;</p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;425f953c-ab2e-49cb-a093-2a839d031481&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">class CreateOrderUseCase {

    private final Logger logger;

    public CreateOrderUseCase(Logger logger) {
        this.logger = logger;
    }

    public OrderResult execute(CreateOrderCommand cmd) {

        logger.info("CreateOrder started");

        Order order = new Order(cmd.userId());

        logger.info("Order created", Map.of(
            "orderId", order.getId()
        ));

        return new OrderResult(order.getId());
    }
}</code></pre></div><h2>&#9888;&#65039;Structured logging doesn&#8217;t fix the real issue</h2><p>After a while, debugging becomes painful, so you switch to structured logging:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;37f17337-3ff0-4a17-bb19-7a01b9d983c0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">logger.info("OrderCreated", Map.of(
    "userId", cmd.userId(),
    "orderId", order.getId()
));</code></pre></div><p>This is better:</p><ul><li><p>easier to search</p></li><li><p>consistent format</p></li><li><p>better for tools</p></li></ul><p>But the real problem doesn&#8217;t change.</p><p>You still have logging calls inside every use case.</p><p>You&#8217;ve just improved <em>how logs look</em>.</p><div><hr></div><p>&#128640; <strong>Register now</strong>: <a href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop">ATDD &#8211; Acceptance Testing Workshop</a><br>Get 100 EUR off with code <strong>DISCOUNT_100</strong></p><div><hr></div><h2>&#9989;Infrastructure Layer: Logging outside the use case</h2>
      <p>
          <a href="https://journal.optivem.com/p/clean-architecture-do-not-inject-loggers-everywhere">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[DDD + Clean Architecture: Where to Put Validation Logic]]></title><description><![CDATA[Should validation go in the API, application layer, or domain?]]></description><link>https://journal.optivem.com/p/ddd-clean-architecture-where-to-put-validation-logic</link><guid isPermaLink="false">https://journal.optivem.com/p/ddd-clean-architecture-where-to-put-validation-logic</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 16 Apr 2026 06:00:41 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/57dd65ab-d445-4842-98a2-a8975e1a3406_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><blockquote><p>&#8220;Where should validation go?&#8221;</p></blockquote><p>You&#8217;ve seen:</p><ul><li><p>validation in controllers</p></li><li><p>validation in services</p></li><li><p>validation duplicated in multiple places</p></li></ul><p>And no one is really sure what&#8217;s &#8220;correct&#8221;.</p><p>So let&#8217;s make it concrete.</p><h2>1. Validation at the API layer (input checks)</h2><p>This is the first place data enters your system.</p><p>Here you check things like:</p><ul><li><p>required fields exist</p></li><li><p>types are correct</p></li><li><p>format is valid (email, date, UUID, etc.)</p></li></ul><p>Example:</p><ul><li><p><code>email</code> is missing &#8594; reject request</p></li><li><p><code>age</code> is a string &#8594; reject request</p></li><li><p><code>orderDate</code> is <code>"not-a-date"</code> &#8594; reject request</p></li></ul><p>The API layer&#8217;s job is to <strong>fail fast with clear errors</strong> for malformed input, so the application and domain layers can work with clean, well-typed data.</p><h2>2. Validation in the Application Layer (policies - use case rules)</h2><p>This is where most real mistakes happen.</p><p>This layer handles rules like:</p><ul><li><p>cannot create shipment if stock is insufficient</p></li><li><p>order cannot be placed if cart is empty</p></li><li><p>cannot refund payment if transaction is already settled beyond refund window</p></li><li><p>cannot process payment if currency is not supported for merchant</p></li><li><p>cannot cancel order after it has been shipped</p></li><li><p>cannot apply discount code if it is expired or not eligible</p></li></ul><p>These are not input checks anymore.</p><p>They are <strong>policies</strong>. A policy is a rule about whether an action is allowed right now, given the current state of the world.</p><p>Every one of these needs <strong>something outside the aggregate itself</strong> to evaluate &#8212; inventory, the clock, a merchant config, a discount catalog. In DDD terms, they cross the aggregate boundary.</p><p>That&#8217;s why they don&#8217;t belong in the domain object. An <code>Order</code> alone doesn&#8217;t know whether stock exists. The application layer orchestrates: it fetches what&#8217;s needed, evaluates the policy, and either proceeds or rejects.</p><p>Example flow:</p><ul><li><p>API sends a valid, well-formed request</p></li><li><p>application checks the relevant policies</p></li><li><p>if a policy fails &#8594; reject here, before touching the domain</p></li></ul><p>This is where you stop things that are technically valid, but not allowed in this situation.</p><div><hr></div><p>&#9889; <strong>Register now</strong>: <a href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop">ATDD &#8211; Acceptance Testing Workshop</a><br>Get 100 EUR off with code <strong>EARLYBIRD100</strong></p><div><hr></div><h2>3. Validation in the Domain Layer (invariants)</h2><p>This is the strictest level.</p><p>These are rules that must <em>never</em> be broken, no matter where the code is called from.</p><p>An invariant is a rule that must <strong>always</strong> hold for a given object, no matter who calls it, from where, at what time.</p><p>Examples:</p><ul><li><p>order must always have at least one line item</p></li><li><p>order total cannot be negative</p></li><li><p>payment cannot be &#8220;SUCCESSFUL&#8221; without a transaction reference</p></li><li><p>reserved quantity cannot exceed available stock</p></li><li><p>order status must follow valid transitions: <code>CREATED &#8594; PAID &#8594; SHIPPED &#8594; DELIVERED</code></p></li><li><p>order cannot have <code>SHIPPED</code> status without a tracking number</p></li></ul><p>If this is broken, your system is already inconsistent.</p><p>So this validation:</p><ul><li><p>is enforced when creating or modifying core objects</p></li><li><p>is tied directly to the business model itself</p></li></ul><p><strong>The key principle: the domain never trusts its callers.</strong></p><p>Even if the API already checked something, the domain re-checks any invariant. Why? Because the domain can be invoked from anywhere &#8212; a job, an event, a test, another bounded context. It cannot assume a well-behaved API sits in front of it.</p><h2>What you should NOT do</h2><p>This is where most real-world code goes wrong:</p>
      <p>
          <a href="https://journal.optivem.com/p/ddd-clean-architecture-where-to-put-validation-logic">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Clean Architecture: DTOs Are NOT Just Wrappers]]></title><description><![CDATA[Code Example]]></description><link>https://journal.optivem.com/p/clean-architecture-dtos-are-not-just-wrappers</link><guid isPermaLink="false">https://journal.optivem.com/p/clean-architecture-dtos-are-not-just-wrappers</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 03 Apr 2026 06:01:42 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/983a1287-3fd1-45a2-8765-55c7ffe65c66_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>Developers like DTOs. And wrappers. And more wrappers.</p><p>Until your &#8220;simple&#8221; API layer turns into <strong>a tower of classes no one can read</strong>, tests become painful, and adding a new field feels like a chore.</p><p>DTOs and mappers are tools &#8212; not dogma. Clean Architecture doesn&#8217;t say &#8220;wrap everything 5 times.&#8221; It says: be intentional about what crosses boundaries.</p><h2>The Problem</h2><p>Many codebases follow a pattern like this:</p><pre><code><code>Domain objects
   &#8595;
Domain DTO
   &#8595;
Application DTO
   &#8595;
API DTO
   &#8595;
JSON</code></code></pre><p>That&#8217;s 3&#8211;4 layers of wrapping <strong>for the same data</strong>, plus mappers everywhere.</p><p>To be clear &#8212; sometimes multiple layers <em>are</em> justified. If you're serving both GraphQL and REST from the same domain, or maintaining a published API contract that must change independently from your domain, extra DTOs earn their keep. The problem isn't layers &#8212; it's layers that exist "just in case."</p><p><strong>The result:</strong></p><ul><li><p>Lots of boilerplate</p></li><li><p>Hard to understand what actually changes the domain</p></li><li><p>Fragile tests because every layer must be mocked</p></li><li><p>Accidental complexity: devs can&#8217;t tell which layer matters for business rules</p></li></ul><div><hr></div><p>&#128640; <strong>Register now</strong>: <a href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop">Acceptance Testing Workshop</a><br>Get 100 EUR off with code <strong>EARLYBIRD100</strong></p><div><hr></div><h1>&#128161;Code Example</h1><h2>Over-Wrapping Everything &#10060;</h2>
      <p>
          <a href="https://journal.optivem.com/p/clean-architecture-dtos-are-not-just-wrappers">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[DDD + Clean Architecture: Stop Putting Business Logic in the Application Layer]]></title><description><![CDATA[Your Clean Architecture isn&#8217;t clean if the domain is empty]]></description><link>https://journal.optivem.com/p/ddd-clean-architecture-dont-put-business-logic-in-application-layer</link><guid isPermaLink="false">https://journal.optivem.com/p/ddd-clean-architecture-dont-put-business-logic-in-application-layer</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 27 Feb 2026 07:01:53 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/e4338280-54a0-4076-9ab2-e0613f86fdee_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>When people try Clean Architecture&#8230; they often end up with bloated application layers and empty domain layers.</p><pre><code><code>Application Layer:
   - Validate input
   - Load entities
   - Perform business logic
   - Call repositories
   - Call gateways
   - Publish events

Domain Layer:
   - Entity with getters and setters</code></code></pre><p>That&#8217;s an anemic domain, not a rich domain.</p><p>The <strong>domain doesn&#8217;t actually do anything</strong>. All the rules are in the application layer.</p><p>In Clean Architecture, we&#8217;re supposed to have a rich domain, which fits in with DDD.</p><h2>&#10060;Empty Domain</h2><p>If you&#8217;re used to CRUD, you probably see entities as simple data structures with public getters and setters.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;7171367a-d899-48c1-b8e3-74dc3dbae677&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">class Order {
    private int id;
    private List&lt;Item&gt; items;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public List&lt;Item&gt; getItems() {
        return items;
    }

    public void setItems(List&lt;Item&gt; items) {
        this.items = items;
    }
}</code></pre></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;151262ff-fbf1-414f-94f2-6640ae6cbe20&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">class Item {
    private String sku;
    private int quantity;
    private boolean expired;

    public String getSku() {
        return sku;
    }

    public void setSku(String sku) {
        this.sku = sku;
    }

    public int getQuantity() {
        return quantity;
    }

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }

    public boolean isExpired() {
        return expired;
    }

    public void setExpired(boolean expired) {
        this.expired = expired;
    }
}</code></pre></div><p>Looks fine&#8230; but the business rules live elsewhere (application layer).</p><p>Your application service does this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;7d18ff13-2e49-4f84-9741-b01afe415658&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public void addItemToOrder(Order order, Item item) {
    if (item.isExpired()) {
        throw new ExpiredException();
    }

    List&lt;Item&gt; items = order.getItems();
    if (items == null) {
        items = new ArrayList&lt;&gt;();
    }

    if (items.size() &gt; 10) {
        throw new LimitExceededException();
    }

    items.add(item);
    order.setItems(items);
    orderRepository.save(order);
}</code></pre></div><p>All the rules (expiration check, limit check) live in the <strong>application layer</strong>.</p><p>The entity is just a data container.</p><p>Basically, you&#8217;ve moved all the complexity into a layer that&#8217;s supposed to orchestrate, not decide.</p><h2>&#10060;Bloated Application Layer</h2>
      <p>
          <a href="https://journal.optivem.com/p/ddd-clean-architecture-dont-put-business-logic-in-application-layer">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Clean Architecture: Use Cases Are NOT Services]]></title><description><![CDATA[Code Example]]></description><link>https://journal.optivem.com/p/clean-architecture-use-cases-are-not-services</link><guid isPermaLink="false">https://journal.optivem.com/p/clean-architecture-use-cases-are-not-services</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 22 Jan 2026 07:02:09 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/449d2cd4-6960-4674-b109-3f4dfcea1290_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128197; Join me for <strong><a href="https://optivem.thinkific.com/products/live_events/2026-02-25-acceptance-testing-live-training">Acceptance Testing (Live Training)</a></strong> on Wed 25th Feb (17:00 - 19:00 CET) <em>(100% discount for Optivem Journal members)</em></p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>Most &#8220;Clean Architecture&#8221; codebases still revolve around <code>SomethingService</code>.</p><ul><li><p><code>UserService</code></p></li><li><p><code>OrderService</code></p></li><li><p><code>PaymentService</code></p></li></ul><p>And inside those services?</p><ul><li><p>business rules</p></li><li><p>orchestration</p></li><li><p>validation</p></li><li><p>database calls</p></li><li><p>sometimes HTTP logic too</p></li></ul><p>Everything&#8230; everywhere&#8230; with multiple use cases all in one file.</p><h2>Why &#8220;Services&#8221; Are a Dead End</h2><p>Services usually start like this:</p><blockquote><p>&#8220;We&#8217;ll put business logic in services so controllers stay thin.&#8221;</p></blockquote><p>And for a while, it works.</p><p>But over time:</p><ul><li><p>Services grow without a clear responsibility</p></li><li><p>Methods multiply</p></li><li><p>Everything depends on everything</p></li><li><p>Tests become slow, fragile, and heavily mocked</p></li></ul><p>You don&#8217;t know <em>what</em> the system actually does.</p><p>Only <em>what data it has</em>.</p><h2>What a Use Case Actually Is</h2><p>A <strong>use case</strong> represents <strong>one business functionality</strong>.</p><p>Not a resource.<br>Not an entity.<br>Not a technical operation.</p><p>Examples:</p><ul><li><p><code>RegisterUser</code></p></li><li><p><code>PlaceOrder</code></p></li><li><p><code>ApproveLoan</code></p></li><li><p><code>CancelSubscription</code></p></li><li><p><code>GenerateInvoice</code></p></li></ul><p>Each use case answers one question:</p><blockquote><p><em>What does the business want to achieve in this scenario?</em></p></blockquote><h2>Services vs Use Cases</h2><p>Services (group data):</p><ul><li><p>Organized around entities</p></li><li><p>Grow horizontally</p></li></ul><p>Use Cases (express behavior)</p><ul><li><p>Organized around behavior</p></li><li><p>Stay focused</p></li></ul><p>If you have a <code>UserService</code>, ask yourself:</p><blockquote><p>What is the <em>use case</em> here?</p></blockquote><p>Because <strong>&#8220;user&#8221; is not a behavior</strong>.</p><h1>&#128161;Code Example</h1>
      <p>
          <a href="https://journal.optivem.com/p/clean-architecture-use-cases-are-not-services">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Clean Architecture: The Dependency Rule Isn’t Optional]]></title><description><![CDATA[Why your domain logic keeps breaking (and how to fix it)]]></description><link>https://journal.optivem.com/p/clean-architecture-on-the-backend-dependency-rule</link><guid isPermaLink="false">https://journal.optivem.com/p/clean-architecture-on-the-backend-dependency-rule</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 18 Dec 2025 07:02:20 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/73456eaa-ae23-46e3-9cce-d63d3101d7df_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128073; Reserve Your Spot for the <strong><a href="https://atdd.optivem.com/">2026 ATDD Accelerator</a></strong></p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>I was trying to add a small feature to our order system.</p><p>Simple, right? Just a validation: &#8220;Orders over $10,000 require manager approval.&#8221;</p><p>Two hours later, I was <strong>deep in a mess</strong>:</p><ul><li><p>Domain entities glued to JPA annotations.</p></li><li><p>Use cases directly calling repositories.</p></li><li><p>Controllers scattered across three packages.</p></li></ul><p>I thought: <strong>&#8220;How is anyone supposed to change this without breaking everything?&#8221;</strong></p><p>Dependencies were everywhere, pointing the wrong way. Every small change became a massive headache.</p><p><strong>The Daily Pain</strong></p><ul><li><p>Changing a business rule? Better hope no one touched your domain logic with a framework dependency.</p></li><li><p>Writing a test? Say hello to spinning up DBs, mocking everything, or manually handling try/catch just to check if something works.</p></li><li><p>Refactoring? One wrong move and the system falls apart.</p></li></ul><p>I realized: <strong>if your core logic depends on infrastructure or frameworks, everything else becomes fragile</strong>.</p><h2>What Does &#8220;Depend&#8221; Mean?</h2><p>You&#8217;ve got a dependency anytime you:</p><ul><li><p>Import a class</p></li><li><p>Call a method</p></li><li><p>Reference a concrete implementation</p></li><li><p>Require a framework to compile</p></li></ul><p>Red flags I see all the time:</p><ul><li><p>A use case importing Spring annotations &#10060;</p></li><li><p>Domain entities referencing ORM &#10060;</p></li><li><p>REST controllers sneaking into inner layers &#10060;</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4jWB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cbf5c1-8611-46f2-92c6-a459c15fbf39_1025x1067.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4jWB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cbf5c1-8611-46f2-92c6-a459c15fbf39_1025x1067.png 424w, https://substackcdn.com/image/fetch/$s_!4jWB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cbf5c1-8611-46f2-92c6-a459c15fbf39_1025x1067.png 848w, https://substackcdn.com/image/fetch/$s_!4jWB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cbf5c1-8611-46f2-92c6-a459c15fbf39_1025x1067.png 1272w, https://substackcdn.com/image/fetch/$s_!4jWB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cbf5c1-8611-46f2-92c6-a459c15fbf39_1025x1067.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4jWB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cbf5c1-8611-46f2-92c6-a459c15fbf39_1025x1067.png" width="1025" height="1067" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c6cbf5c1-8611-46f2-92c6-a459c15fbf39_1025x1067.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1067,&quot;width&quot;:1025,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:113005,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://journal.optivem.com/i/177321063?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cbf5c1-8611-46f2-92c6-a459c15fbf39_1025x1067.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4jWB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cbf5c1-8611-46f2-92c6-a459c15fbf39_1025x1067.png 424w, https://substackcdn.com/image/fetch/$s_!4jWB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cbf5c1-8611-46f2-92c6-a459c15fbf39_1025x1067.png 848w, https://substackcdn.com/image/fetch/$s_!4jWB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cbf5c1-8611-46f2-92c6-a459c15fbf39_1025x1067.png 1272w, https://substackcdn.com/image/fetch/$s_!4jWB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6cbf5c1-8611-46f2-92c6-a459c15fbf39_1025x1067.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h3>Use Case Depends on Infrastructure &#10060;</h3>
      <p>
          <a href="https://journal.optivem.com/p/clean-architecture-on-the-backend-dependency-rule">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Clean Architecture on the Backend: Data Flow]]></title><description><![CDATA[How does data flow through the system?]]></description><link>https://journal.optivem.com/p/clean-architecture-on-the-backend</link><guid isPermaLink="false">https://journal.optivem.com/p/clean-architecture-on-the-backend</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 31 Oct 2025 07:01:09 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/21fa073d-f09e-4873-a9b3-c98f46817c93_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128640; Join the <strong><a href="https://atdd.optivem.com/">ATDD Accelerator waitlist</a></strong></p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>Data flows from external sources (like the UI) to the core logic and back out:</p><p><strong>1. Frontend: Send request to Backend</strong></p><p>The user enters the SKU and quantity, then clicks &#8220;Place Order.&#8221; The frontend sends this data to the backend as a REST API request.</p><p><strong>2. Backend: Presentation Layer &#8594; Use Case</strong></p><p>The REST API receives the JSON request, converts it into a Request DTO, and delegates the work to the Use Case. The controller stays minimal and doesn&#8217;t contain any business logic.</p><p><strong>3. Backend: Use Case &#8594; Domain Layer</strong></p><p>The Use Case performs basic validation of the Request DTO, and calls repositories to retrieve entities. The Use Case may create or save entities via repositories and can call methods on those entities to execute business logic.</p><p><strong>4. Backend: Domain Layer &#8594; Use Case</strong></p><p>The Domain Layer returns entities and computed results &#8212; such as the product details, total order price, and order status &#8212; back to the Use Case.</p><p><strong>5. Backend: Use Case &#8594; Presentation Layer</strong></p><p>The Use Case prepares a Response DTO with the processed results from the Domain Layer and sends it to the REST API.</p><p><strong>6. Backend: Presentation Layer &#8594; Client</strong></p><p>The REST API formats the response and returns it to the client (Frontend) for display.</p><p><strong>7. Frontend: Present to user</strong></p><p>The frontend receives the backend response and displays the order confirmation, showing details like the product, quantity, total price, and order status.</p><h2>Data Flow</h2><p>Data flow defines how information moves between the layers of your system &#8212; from the user interface, through the business logic, and back out again.</p><ul><li><p>Each layer has a <strong>single responsibility</strong>.</p></li><li><p>Dependencies always point <strong>inward</strong>, toward the core logic.</p></li><li><p>You can change external details (UI, database, frameworks) without breaking your domain rules.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ioo3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e70013f-a78c-492b-9412-afa9548ac4b7_1025x1086.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ioo3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e70013f-a78c-492b-9412-afa9548ac4b7_1025x1086.png 424w, https://substackcdn.com/image/fetch/$s_!Ioo3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e70013f-a78c-492b-9412-afa9548ac4b7_1025x1086.png 848w, https://substackcdn.com/image/fetch/$s_!Ioo3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e70013f-a78c-492b-9412-afa9548ac4b7_1025x1086.png 1272w, https://substackcdn.com/image/fetch/$s_!Ioo3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e70013f-a78c-492b-9412-afa9548ac4b7_1025x1086.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ioo3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e70013f-a78c-492b-9412-afa9548ac4b7_1025x1086.png" width="1025" height="1086" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2e70013f-a78c-492b-9412-afa9548ac4b7_1025x1086.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1086,&quot;width&quot;:1025,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:90677,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://journal.optivem.com/i/177269159?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e70013f-a78c-492b-9412-afa9548ac4b7_1025x1086.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Ioo3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e70013f-a78c-492b-9412-afa9548ac4b7_1025x1086.png 424w, https://substackcdn.com/image/fetch/$s_!Ioo3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e70013f-a78c-492b-9412-afa9548ac4b7_1025x1086.png 848w, https://substackcdn.com/image/fetch/$s_!Ioo3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e70013f-a78c-492b-9412-afa9548ac4b7_1025x1086.png 1272w, https://substackcdn.com/image/fetch/$s_!Ioo3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e70013f-a78c-492b-9412-afa9548ac4b7_1025x1086.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Real-life Example: Ordering System</h2><p>Applying Clean Architecture on the backend (for an e-commerce system).</p><h2>1. Frontend: Send request to Backend</h2><p>On the Frontend, user inputs the SKU and quantity, and clicks button to &#8220;Place Order&#8220;. Then Frontend calls the Backend REST API. </p><p>Let&#8217;s see what are the Clean Architecture Layers are on the Backend.</p><h2>2. Backend: Presentation Layer &#8594; Use Case</h2><p>On the Backend, the Presentation Layer is generally the REST API (or it might be SOAP Service, or Message Consumer, etc.).</p><p>The REST API has received the request as JSON. We have frameworks that automatically convert the JSON into a Request DTO. In the example below, the Request DTO is the PlaceOrderRequest.</p><p>We keep the REST API controller minimal. We just pass the Request DTO to the use case. In this case, we pass the PlaceOrderRequest to the PlaceOrderUseCase. </p><p><em>The reason is because the REST API controller should not have knowledge of application/business logic, it just delegates.</em></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;3a5a38bb-4f4f-4a54-80c8-d9cb5cae59d4&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">@RestController
@RequestMapping("/api/orders")
public class OrderController {

    @PostMapping
    public ResponseEntity&lt;PlaceOrderResponse&gt; placeOrder(@RequestBody PlaceOrderRequest request) {
        var response = placeOrderUseCase.execute(request);
        ...
    }
}</code></pre></div>
      <p>
          <a href="https://journal.optivem.com/p/clean-architecture-on-the-backend">
              Read more
          </a>
      </p>
   ]]></content:encoded></item></channel></rss>