<?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>Sun, 23 Aug 2026 08:44:25 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: 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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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>