<?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]]></title><description><![CDATA[TDD | Hexagonal Architecture | Clean Architecture]]></description><link>https://journal.optivem.com</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</title><link>https://journal.optivem.com</link></image><generator>Substack</generator><lastBuildDate>Mon, 05 Oct 2026 14:04:16 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[Why Your E2E Tests Didn't Replace Manual QA]]></title><description><![CDATA[Your team writes code faster with AI. But releases are still slow.]]></description><link>https://journal.optivem.com/p/why-your-e2e-tests-didnt-replace-manual-qa</link><guid isPermaLink="false">https://journal.optivem.com/p/why-your-e2e-tests-didnt-replace-manual-qa</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Tue, 29 Sep 2026 06:03:48 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/0c8b7ae9-0951-47be-8eac-c907c8dd6336_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128075; <em>Hello, this is Valentina with the free edition 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 team automated testing.</p><p>So why does every release still wait for QA?</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><p>You invested in E2E test automation to get faster releases and fewer bugs.</p><p>Instead, what you got is a test suite that breaks with every UI change, a team that doesn&#8217;t trust it, and a manual regression pass that still sits between &#8220;done&#8221; and &#8220;released.&#8221;</p><p>Now AI is helping your developers write code faster, and the queue in front of QA is only getting longer.</p><p>This isn&#8217;t a people problem, and more automation of the same kind won&#8217;t fix it. It comes down to how the tests are designed.</p><h2>What You&#8217;ll Learn</h2><ul><li><p>Why E2E automation rarely replaces manual QA &#8212; and the three design decisions behind it</p></li><li><p>What changes when tests are written in business language &#8212; your product owner can read them, QA can trust them, and UI changes stop breaking them</p></li><li><p>How to start small &#8212; with one critical use case, without stopping delivery or rewriting everything</p></li><li><p>How to get it funded &#8212; by presenting it to your CEO in terms of release speed, cost of bugs and time spent on repeat work</p></li></ul><h2>Who Should Join</h2><p>This is for you if:</p><ul><li><p>You lead an engineering team (as engineering manager, head of engineering or CTO)</p></li><li><p>Releases are slowed down by manual testing</p></li><li><p>Your team tried test automation and it didn&#8217;t deliver what was promised</p></li></ul><h2>Event Details</h2><p>&#128197; <strong>Date &amp; Time:</strong> Wed, Oct 7, 2026 5:30PM<br>&#128205; <strong>Where:</strong> Live online<br>&#128176; <strong>Cost:</strong> Free</p><p>&#128279; <strong>Register here:</strong> <a href="https://optivem.thinkific.com/products/courses/2026-oct-webinar-atdd">Why Your E2E Tests Didn&#8217;t Replace Manual QA</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://optivem.thinkific.com/products/courses/2026-oct-webinar-atdd" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iWry!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png 424w, https://substackcdn.com/image/fetch/$s_!iWry!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png 848w, https://substackcdn.com/image/fetch/$s_!iWry!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png 1272w, https://substackcdn.com/image/fetch/$s_!iWry!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iWry!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:675,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:180163,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-oct-webinar-atdd&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://journal.optivem.com/i/217951574?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!iWry!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png 424w, https://substackcdn.com/image/fetch/$s_!iWry!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png 848w, https://substackcdn.com/image/fetch/$s_!iWry!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png 1272w, https://substackcdn.com/image/fetch/$s_!iWry!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.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>Meet Your Coach</h2><p>Valentina Jemuovi&#263; is a technical coach and the founder of Optivem. She coaches engineering teams in ATDD and TDD, helping them deliver software faster with fewer bugs. She works in Java, .NET and TypeScript, and shares her work with ~30K followers on LinkedIn.</p><h2>Coding Got Faster &#8212; Releases Are Still Slow</h2><p>If you want to eliminate manual regression testing and get faster delivery with fewer bugs&#8230;</p><p>&#128204; <strong>Register here:</strong> <a href="https://optivem.thinkific.com/products/courses/2026-oct-webinar-atdd">Why Your E2E Tests Didn&#8217;t Replace Manual QA</a></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>]]></content:encoded></item><item><title><![CDATA[Spec-Driven Development: Make Specs Enforceable with ATDD]]></title><description><![CDATA[AI can ignore your spec &#8212; it can&#8217;t ignore a failing test.]]></description><link>https://journal.optivem.com/p/spec-driven-development-make-specs</link><guid isPermaLink="false">https://journal.optivem.com/p/spec-driven-development-make-specs</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 25 Sep 2026 15:21:52 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/55e22a9e-b029-48c9-9d23-99ba2c8693b7_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>I help teams adopt Spec-Driven Development, so that they can write code faster with AI agents. I have a few spots left for 2027.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://calendly.com/valentinajemuovic/call&quot;,&quot;text&quot;:&quot;Book a call&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://calendly.com/valentinajemuovic/call"><span>Book a call</span></a></p><p>&#8220;Spec-driven development&#8221; is everywhere in AI tooling right now: Kiro, GitHub&#8217;s spec-kit, Tessl. Write the spec first, then let the agent build against it. Birgitta B&#246;ckeler reviewed all three in <a href="https://martinfowler.com/articles/exploring-gen-ai/sdd-3-tools.html">Understanding Spec-Driven-Development: Kiro, spec-kit, and Tessl</a> on martinfowler.com and found the catch.</p><p>In every one of them, &#8220;spec&#8221; means a Markdown document the agent is supposed to read. <strong>Nothing checks that the code matches it, and the agents don&#8217;t always follow it.</strong> As she put it: &#8220;I frequently saw the agent ultimately not follow all the instructions.&#8221;</p><p>The problem isn&#8217;t the tools. It&#8217;s that the spec is a Markdown file, and <strong>you can&#8217;t run a Markdown file against the code.</strong> To find out whether the agent followed it, a person has to read the code line by line. That&#8217;s the work you hoped the spec would save.</p><p>We&#8217;ve had a spec without this problem since 1999. Kent Beck&#8217;s first Extreme Programming book, <a href="https://dl.acm.org/doi/abs/10.5555/318762">Extreme Programming Explained</a> (1999), already had acceptance tests, and the <a href="http://www.extremeprogramming.org/rules/functionaltests.html">XP rules</a> on extremeprogramming.org spell out how they work: acceptance tests &#8220;are created from user stories&#8221; and &#8220;should be automated so they can be run often.&#8221;</p><p>In 2010, Jez Humble and Dave Farley&#8217;s <a href="https://www.amazon.com/dp/B003YMNVC0">Continuous Delivery</a> gave automated acceptance tests their own chapter: &#8220;Acceptance tests are a crucial stage in the deployment pipeline: They take delivery teams beyond basic continuous integration. Once you have automated acceptance tests in place, you are testing the business acceptance criteria of your application, that is, validating that it provides users with valuable functionality.&#8221;</p><p>An acceptance test is the spec and the check in one: it describes what the system should do, and it fails until the system does it.</p><p><strong>An agent can ignore a Markdown file.</strong></p><p><strong>It can&#8217;t ignore a test that won&#8217;t pass.</strong></p><p>An acceptance test doesn&#8217;t call the system directly. It calls a DSL, and the DSL calls two kinds of drivers. System drivers talk to the system through its UI or API. External-system drivers talk to the external systems it depends on, such as an ERP.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ol6t!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ol6t!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png 424w, https://substackcdn.com/image/fetch/$s_!ol6t!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png 848w, https://substackcdn.com/image/fetch/$s_!ol6t!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png 1272w, https://substackcdn.com/image/fetch/$s_!ol6t!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ol6t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png" width="602" height="608.2019230769231" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1471,&quot;width&quot;:1456,&quot;resizeWidth&quot;:602,&quot;bytes&quot;:145963,&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/217383701?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.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_!ol6t!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png 424w, https://substackcdn.com/image/fetch/$s_!ol6t!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png 848w, https://substackcdn.com/image/fetch/$s_!ol6t!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png 1272w, https://substackcdn.com/image/fetch/$s_!ol6t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.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><p>In acceptance test-driven development (ATDD), the Red step is where you write that test, before any system code exists. When an AI agent does the work, the Red step is split into four small steps, and a human reviews each one.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-OUs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-OUs!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png 424w, https://substackcdn.com/image/fetch/$s_!-OUs!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png 848w, https://substackcdn.com/image/fetch/$s_!-OUs!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png 1272w, https://substackcdn.com/image/fetch/$s_!-OUs!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-OUs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png" width="602" height="1147.3557692307693" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2775,&quot;width&quot;:1456,&quot;resizeWidth&quot;:602,&quot;bytes&quot;:275044,&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/217383701?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.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_!-OUs!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png 424w, https://substackcdn.com/image/fetch/$s_!-OUs!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png 848w, https://substackcdn.com/image/fetch/$s_!-OUs!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png 1272w, https://substackcdn.com/image/fetch/$s_!-OUs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.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>In every box, the AI writes the code, a human reviews it, and then it&#8217;s committed.</p><h2>Before Red: the acceptance criteria</h2><p>Example: placing an order in an online shop.</p><p>It starts with a ticket. Its acceptance criteria are agreed before any code is written:</p><pre><code>Scenario: Calculate the base price from unit price and quantity
  Given a product with unit price 20.00
  When I place an order for 5 of that product
  Then the order is placed successfully
  And the order&#8217;s base price is 100.00</code></pre><h2>Inside the Red step</h2><p><strong>1. Write Acceptance Tests</strong></p><p>The agent turns the acceptance criteria into a test. The test is written in a DSL, the test code&#8217;s own vocabulary (<code>placeOrder().withQuantity(...)</code>). Here&#8217;s the acceptance test, from <code>PlaceOrderPositiveTest.java</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;e8dd1e8a-3469-4dc3-9b7d-ce0e682d802c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">@TestTemplate
@Channel({ChannelType.UI, ChannelType.API})
void shouldCalculateBasePriceAsProductOfUnitPriceAndQuantity() {
    scenario
            .given().product()
                .withUnitPrice(20.00)
            .when().placeOrder()
                .withQuantity(5)
            .then().shouldSucceed()
            .and().order()
                .hasBasePrice(100.00);
}</code></pre></div>
      <p>
          <a href="https://journal.optivem.com/p/spec-driven-development-make-specs">
              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[Slow releases. Production bugs. And you already automated.]]></title><description><![CDATA[You automated, and manual regression testing is still there. Those two facts have the same cause.]]></description><link>https://journal.optivem.com/p/slow-releases-production-bugs</link><guid isPermaLink="false">https://journal.optivem.com/p/slow-releases-production-bugs</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Wed, 16 Sep 2026 07:33:58 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/12031a27-f1a8-4135-82ae-da60813a8961_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><span>&#128075; </span><em>Hello, this is Valentina with the free edition 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 there are hundreds of things to check, QA can't check everything.</p><blockquote><p>&#8220;That feature hasn&#8217;t changed.&#8221;</p><p>&#8220;Let&#8217;s just test the critical flows.&#8221;</p></blockquote><p>Some tests get skipped.</p><p>That&#8217;s when production bugs slip through.</p><h2>&#10060; &#8220;Just hire more QA&#8221;</h2><p>But every release adds more features.</p><p>More features = more things to regression test.</p><p>QA can&#8217;t keep up.</p><h2>&#10060; &#8220;But we already automated!&#8221;</h2><p><strong>You tried e2e tests.</strong></p><p>Run it &#8594; red.</p><p>Run it again &#8594; green.</p><p>Eventually, nobody trusts the tests.</p><p>Developers start ignoring them.</p><p><strong>You tried unit tests.</strong></p><p>They're green.</p><p>But something still breaks in production.</p><p>Why?</p><h2>Does Checkout Work?</h2><p>Checkout has these requirements:</p><ul><li><p>Customer can add a product to the cart.</p></li><li><p>Customer can pay by card.</p></li><li><p>Order is created after payment.</p></li><li><p>Customer can&#8217;t order an out-of-stock product.</p></li></ul><h3>&#9888;&#65039;Unit tests</h3><p>Test individual pieces, but not the whole flow.</p><blockquote><p><code>CartService</code> adds a product.</p><p><code>PaymentService</code> charges the card.</p><p><code>OrderService</code> creates the order.</p></blockquote><p>They can all pass while the <strong>checkout flow still breaks.</strong></p><h3>&#9888;&#65039;E2E test</h3><p>It tests the whole flow.</p><p>But the behaviour is buried inside UI steps.</p><blockquote><p>Open checkout &#8594; add product &#8594; enter card &#8594; click Pay &#8594; see &#8220;Order confirmed&#8221;</p></blockquote><p>So <strong>what is this test actually checking?</strong></p><blockquote><p>Can the customer pay by card?</p><p>Is an order created?</p><p>Is the cart emptied?</p><p>Does the confirmation page appear?</p></blockquote><p>You have to read the steps and figure it out.</p><p>Now imagine 200 tests like that.</p><p><strong>You can&#8217;t easily tell:</strong></p><blockquote><p>&#8220;Which test proves that an out-of-stock product can&#8217;t be ordered?&#8221;</p><p>&#8220;Do we have a test for refunds?&#8221;</p><p>&#8220;Do we test that an order is only created after successful payment?&#8221;</p></blockquote><h3>&#9989; Acceptance test</h3><p>The behavior being checked is obvious.</p><blockquote><p>Given a product is out of stock<br>When a customer tries to order it<br>Then the order is rejected</p></blockquote><p>The requirement and the test are the same.</p><p><strong>You can see exactly what behavior you&#8217;re checking.</strong></p><p>And because the test isn't tied to the UI, changing the checkout screen doesn't mean rewriting the test.</p><div><hr></div><p><strong>ATDD in Legacy Code: catch regression bugs before production.</strong><br><span>Hands-on work with your teams. Limited spaces for 2027.</span></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>]]></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[Refactoring Should NOT Touch Your Acceptance Tests]]></title><description><![CDATA[A &#8220;refactor&#8221; PR that changes the acceptance test or the DSL isn't a refactor &#8212; it's a behavioral change]]></description><link>https://journal.optivem.com/p/refactoring-should-not-touch-your-acceptance-tests</link><guid isPermaLink="false">https://journal.optivem.com/p/refactoring-should-not-touch-your-acceptance-tests</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 03 Sep 2026 06:00:37 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/e53e08ea-33ea-4d0b-930c-cf47d5ae8edf_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><span>&#128197; Join our next live course: </span><strong><a href="https://optivem.thinkific.com/products/courses/2026-oct-atdd"><span>ATDD</span></a><span>, </span><a href="https://optivem.thinkific.com/products/courses/2026-oct-ca"><span>Clean Architecture</span></a><span>, </span><a href="https://optivem.thinkific.com/products/courses/2026-oct-pipelines"><span>Pipelines</span></a><span><br></span></strong><span>Early bird: </span><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 open a PR labelled:</p><blockquote><p>&#8220;API redesign &#8212; no behavior change&#8221;</p></blockquote><p>Looks fine.</p><p>Merged.</p><p>The one-line acceptance test change goes unnoticed.</p><p>Six months later, a regression bug slips through.</p><p>The acceptance test that was supposed to catch it didn&#8217;t.</p><p>Why?</p><p>Because the test was quietly changed at the same time as the code.</p><p><strong>That&#8217;s the problem.</strong></p><p>If the behavior didn't change, the acceptance test shouldn't need to change.</p><p>The driver should handle it.</p><h2>&#9989;The test stays the same &#8212; the driver changes</h2><p>Look at <code>PlaceOrderPositiveTest.java</code> in the shop&#8217;s <code>system-test</code> module:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;c4f330db-8b1e-4443-9670-00731a7d11f5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">@TestTemplate
@Channel({ChannelType.UI, ChannelType.API})
void shouldCalculateBasePriceAsProductOfUnitPriceAndQuantity() {
    scenario
            .given().product()
                .withUnitPrice(20.00)
            .when().placeOrder()
                .withQuantity(5)
            .then().shouldSucceed()
            .and().order()
                .hasBasePrice(100.00);
}</code></pre></div><p>This test has no idea whether the order is placed by <code>POST /api/orders</code> or <code>POST /api/purchases</code>.</p><p>It has no idea whether the UI&#8217;s place-order button is selected by <code>[aria-label="Place Order"]</code> or by some new CSS class.</p><p>It also has no idea whether the ERP returns the product&#8217;s price as <code>price</code> or <code>unitPrice</code>.</p>
      <p>
          <a href="https://journal.optivem.com/p/refactoring-should-not-touch-your-acceptance-tests">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Don't Let AI Multiply Bad Tests]]></title><description><![CDATA[AI learns from the tests you already have. If those are bad, you now get bad tests much faster.]]></description><link>https://journal.optivem.com/p/dont-let-ai-multiply-bad-tests</link><guid isPermaLink="false">https://journal.optivem.com/p/dont-let-ai-multiply-bad-tests</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Tue, 01 Sep 2026 08:22:21 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/a4619885-fb6d-424e-8fd7-2fc25f1c9bbd_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>ATDD in Legacy Code: catch regression bugs before production &#8212; in banking, insurance, and healthcare systems.</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 courses: <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><span>&#128075; </span><em>Hello, this is Valentina with the free edition 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;AI can write the tests.&#8221;</p></blockquote><p>Finally.</p><p>All those tests nobody has time to write.</p><p>No more days spent waiting for manual QA before every release.</p><p><strong>Six months later&#8230;</strong></p><p>More code.</p><p>More PRS.</p><p><strong>But releases aren&#8217;t faster.</strong></p><p>QA is still testing everything manually before anything ships &#8212; exactly as they were last year.</p><p>And the bugs? Still there.</p><p>Now management wants to know:</p><p><strong>What did we actually get for the money?</strong></p><p>You have plenty of numbers to show them.</p><p>More code.<br>More PRs.<br>More tests.</p><p>But none of those numbers answer the question.</p><p>Because more code isn't the same as safer releases.</p><h2>AI copies whatever it finds</h2><p>Ask AI to write a test.</p><p><strong>But what if your e2e suite is full of tests coupled to the UI?</strong></p><p>Click the button.<br>Fill the field.<br>Check the screen.</p><p><strong>AI just writes hundreds more tests like that.</strong></p><p>Faster.</p><p>It doesn't stop and ask:</p><blockquote><p>&#8220;Is this a good way to test this application?&#8221;</p></blockquote><p>It looks at the tests you already have and learns:</p><blockquote><p>&#8220;This is how tests are written here.&#8221;</p></blockquote><h2>You had a speed limit</h2><p>Before AI, writing tests was slow.</p><p>That was annoying.</p><p>But it also limited how many bad tests you could create.</p><p>Your team could only write so many brittle E2E tests before the sprint ended.</p><p><strong>AI removed that limit.</strong></p><p>Now you can write hundreds in the time it used to take to write a few.</p><p>But the tests aren&#8217;t any better.</p><p>They&#8217;re still coupled to the UI.</p><p>Still break when the UI changes.</p><p>Still create more work for the team.</p><h2>Before AI writes 100 more tests</h2><p>AI writing tests isn&#8217;t the problem.</p><p>AI writing bad tests faster is.</p><p>The problem is giving it a bad test suite to learn from.</p><p>&#10060; If your tests are all coupled to the UI, AI will keep generating tests like:</p><p><code>click("Place Order")</code></p><p><strong>&#9989; Instead, fix the test, so that it's coupled to the domain rather than the UI:</strong></p><p><code>shop.placeOrder()</code></p><p>Now AI can do what it&#8217;s good at:</p><p><strong>Write hundreds more. Fast.</strong></p><div><hr></div><h3>&#9889;Join our next live courses</h3><ul><li><p><strong><a href="https://optivem.thinkific.com/products/courses/2026-oct-atdd">ATDD: Stop Shipping Bugs</a></strong></p></li><li><p><strong><a href="https://optivem.thinkific.com/products/courses/2026-oct-ca">Clean Architecture: In Practice</a></strong></p></li><li><p><strong><a href="https://optivem.thinkific.com/products/courses/2026-oct-pipelines">Pipelines: Stop Release Nightmares</a></strong></p></li></ul><p>Early bird: <strong>&#8364;100 off with code EARLYBIRD100</strong></p><div><hr></div><p><strong>ATDD in Legacy Code: catch regression bugs before production &#8212; in banking, insurance, and healthcare systems.</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><p></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[Senior Engineer ≠ Years of Experience]]></title><description><![CDATA[&#8220;I've been doing this for 15 years, so I already know.&#8221;]]></description><link>https://journal.optivem.com/p/senior-engineer-years-of-experience</link><guid isPermaLink="false">https://journal.optivem.com/p/senior-engineer-years-of-experience</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Mon, 24 Aug 2026 06:01:00 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/9edc6505-bd88-4093-baf1-1a1d8348f025_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><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><p>Someone can spend 20 years getting better at software engineering.</p><p>Or they can spend 20 years repeating the same mistakes.</p><h2>The &#8220;I&#8217;ve Been Doing This for 20 Years&#8221; Developer</h2><p>There&#8217;s a particular kind of senior engineer who is difficult to work with.</p><p>They&#8217;ve been doing this for a long time.<br>They&#8217;ve seen everything.<br>They have an answer for everything.</p><p>And every new problem gets solved with something they&#8217;ve already done before.</p><p>You suggest a different approach.<br>They tell you why it won&#8217;t work.</p><p>You show them an example.<br>They explain why that example is different.</p><p>You point out a problem with their approach.<br>They tell you they&#8217;ve been doing this for 20 years.</p><p>And that&#8217;s supposed to end the discussion.</p><h2>Experience Doesn&#8217;t Fix Bad Habits</h2><p>It makes them harder to see.</p><p>A developer who has spent 10 years writing huge classes doesn&#8217;t necessarily become better at design.</p><p>They might simply become extremely fast at writing huge classes.</p><p>A developer who has spent 10 years avoiding tests doesn&#8217;t become good at testing.</p><p>They become very experienced at working without tests.</p><p>And someone who has spent years solving every problem with another layer, abstraction, framework, or design pattern can become incredibly experienced at overengineering.</p><p>Experience makes you better at whatever you repeatedly practice.</p><p><strong>Including the wrong things.</strong></p><h2>Stop Counting Years</h2><p>Years of experience are a terrible measurement of engineering ability.</p><p>A developer with three years of experience who constantly learns, experiments, gets feedback, and improves their approach can be far more effective than someone with fifteen years who stopped learning five years ago.</p><p>I&#8217;ve seen developers with relatively little experience who can walk into an unfamiliar codebase and start asking the right questions.</p><p>And I&#8217;ve seen extremely experienced developers who can&#8217;t change their approach even when it&#8217;s clearly not working.</p><h2>Good Engineers Say &#8220;I Was Wrong&#8221;</h2><p>The best engineers can look at their own code and say:</p><blockquote><p>&#8220;I wouldn&#8217;t write this that way today.&#8221;</p></blockquote><p>Years of experience can tell you how long someone has been in the game.</p><p>It doesn&#8217;t tell you how well they&#8217;ve played it.</p><p>Getting better means being willing to rethink how you&#8217;ve been doing things.</p><p>Join the live 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>&#128467; Aug 26<br>&#9200; 5:00&#8211;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>]]></content:encoded></item><item><title><![CDATA[Hexagonal Architecture: Don't Follow the Diagram]]></title><description><![CDATA[Don't create a class just because the Hexagonal Architecture diagram has one]]></description><link>https://journal.optivem.com/p/hexagonal-architecture-dont-follow-the-diagram</link><guid isPermaLink="false">https://journal.optivem.com/p/hexagonal-architecture-dont-follow-the-diagram</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 20 Aug 2026 06:01:57 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/8955446c-045c-4fb0-a60d-3f61a6e7b4d2_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 time you see Hexagonal Architecture, it can feel like a lot.</p><p><strong>Schedule a shipment.</strong></p><p>You're used to something like: <code>ShipmentHttpController</code>, talking straight to the ORM and the FedEx API. One class, two integrations.</p><p>Then you look at the Hexagonal Architecture version of the same feature:</p><ul><li><p>driving adapter (<code>ShipmentHttpController</code>)</p></li><li><p>driving port (<code>ScheduleShipmentUseCase</code>)</p></li><li><p>the hexagon itself (<code>Shipment</code>, <code>Address</code>, <code>PackageDetails</code>, <code>ShipmentRepository</code>, <code>CourierGateway</code>)</p></li><li><p>driven adapters (<code>SqlShipmentRepository</code>, <code>FedExCourierGateway</code>).</p></li></ul><p>And here&#8217;s where things can get out of hand.</p><p><strong>You start creating all of these classes before you actually need them.</strong></p><h2>The Starting Point</h2><p><strong>&#128683; Problem: No pain yet</strong></p><p>One feature, one caller, one integration on each side.</p><p><strong>&#9989; Solution: Ship it as a single class</strong></p><p>There&#8217;s nothing to abstract until something actually hurts.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZnVO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZnVO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png 424w, https://substackcdn.com/image/fetch/$s_!ZnVO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png 848w, https://substackcdn.com/image/fetch/$s_!ZnVO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png 1272w, https://substackcdn.com/image/fetch/$s_!ZnVO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZnVO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png" width="1180" height="772" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:772,&quot;width&quot;:1180,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:74274,&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/211436844?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.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_!ZnVO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png 424w, https://substackcdn.com/image/fetch/$s_!ZnVO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png 848w, https://substackcdn.com/image/fetch/$s_!ZnVO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png 1272w, https://substackcdn.com/image/fetch/$s_!ZnVO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.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><p>One class handles the HTTP request, runs the validation, talks to Postgres through JPA, and calls the FedEx SDK directly. No ports, no adapters, no domain model.</p><p>This isn't the design we'd want to keep forever: it&#8217;s slow to test and it&#8217;s going to get worse as more logic lands in it &#8212; but it hasn&#8217;t gotten worse yet.</p><h2>Step 1 &#8212; Driven Ports Keep I/O Out of the Way</h2><p><strong>&#128683; Problem: Tests are slow</strong></p><p>Every test hits a real Postgres instance and the real FedEx test instance, so they&#8217;re slow, and the test instance goes down more often than the code changes.</p><p><strong>&#9989; Solution: Use driven ports for I/O</strong></p>
      <p>
          <a href="https://journal.optivem.com/p/hexagonal-architecture-dont-follow-the-diagram">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[TDD in Legacy Code - Contract Tests - Components]]></title><description><![CDATA[How can Frontend & Backend Teams find out that they can't communicate, instead of waiting for slower Acceptance Tests?]]></description><link>https://journal.optivem.com/p/tdd-in-legacy-code-contract-tests-components</link><guid isPermaLink="false">https://journal.optivem.com/p/tdd-in-legacy-code-contract-tests-components</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 14 Aug 2026 06:02:31 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/fc44c50a-fd15-408f-ba60-845c1b32ded4_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><span>&#128197; Join me: </span><strong><a href="https://optivem.thinkific.com/products/live_events/clean-architecture-for-backend-developers">Clean Architecture for Backend Developers</a></strong><span> on Wed 26th Aug, 5:00 - 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><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 TDD in Legacy Code. This article is part of the <a href="https://journal.optivem.com/p/tdd-in-legacy-code-outline">TDD in Legacy Code</a> series. </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>Component Tests are fast because they stub their dependencies</h2><p>Component Tests give us fast feedback by testing a component <strong>in isolation</strong>.</p><p>The Frontend Team stubs out the Backend in <a href="https://journal.optivem.com/p/component-tests-in-legacy-code-frontend">Frontend Component Tests in Legacy Code</a>; the Backend Team stubs out the External System (the ERP) in <a href="https://journal.optivem.com/p/component-tests-in-legacy-code-backend">Backend Component Tests in Legacy Code</a>.</p><h2>But your stubs might be wrong!</h2><p>When the Frontend stubs the Backend, it makes an assumption: <em>&#8220;</em><code>POST /api/orders</code><em> returns </em><code>201</code><em> with </em><code>{ orderNumber }</code><em>.&#8221;</em></p><p>But what if the Backend Team renames that field, or changes the status code, or moves the endpoint. What happens to the Frontend&#8217;s Component Tests?</p><p><strong>They stay green.</strong> They&#8217;re still testing against the stub which hadn&#8217;t changed &#8212; but the stub&#8217;s assumption is now wrong.</p><p>The bug doesn&#8217;t show up until the Frontend and the Backend actually run together &#8212; when the Acceptance Test fails.</p><h2>How to catch stub mismatch? Contract Tests</h2><p>The Frontend says:</p><blockquote><p>&#8220;This is what I expect from the Backend.&#8221;</p></blockquote><p>Pact <strong>turns that expectation into a contract</strong>.</p><p>The Backend then runs the contract against itself.</p><p>If the Backend no longer matches what the Frontend expects, <strong>the Backend build fails.</strong></p><p>You don&#8217;t write a new test from scratch. You take a Frontend Component Test you already have, and <strong>replace the stub with a contract</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!t7_r!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc587b8f5-09d6-4330-a578-7e3f9c2050a0_1943x620.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!t7_r!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc587b8f5-09d6-4330-a578-7e3f9c2050a0_1943x620.png 424w, https://substackcdn.com/image/fetch/$s_!t7_r!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc587b8f5-09d6-4330-a578-7e3f9c2050a0_1943x620.png 848w, https://substackcdn.com/image/fetch/$s_!t7_r!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc587b8f5-09d6-4330-a578-7e3f9c2050a0_1943x620.png 1272w, https://substackcdn.com/image/fetch/$s_!t7_r!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc587b8f5-09d6-4330-a578-7e3f9c2050a0_1943x620.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!t7_r!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc587b8f5-09d6-4330-a578-7e3f9c2050a0_1943x620.png" width="1456" height="465" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c587b8f5-09d6-4330-a578-7e3f9c2050a0_1943x620.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:465,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:59975,&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/210751980?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc587b8f5-09d6-4330-a578-7e3f9c2050a0_1943x620.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_!t7_r!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc587b8f5-09d6-4330-a578-7e3f9c2050a0_1943x620.png 424w, https://substackcdn.com/image/fetch/$s_!t7_r!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc587b8f5-09d6-4330-a578-7e3f9c2050a0_1943x620.png 848w, https://substackcdn.com/image/fetch/$s_!t7_r!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc587b8f5-09d6-4330-a578-7e3f9c2050a0_1943x620.png 1272w, https://substackcdn.com/image/fetch/$s_!t7_r!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc587b8f5-09d6-4330-a578-7e3f9c2050a0_1943x620.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>Here are the steps to introduce Contract Tests in Legacy Code. You&#8217;ll get tasks to implement in your GitHub Sandbox Project. &#11015;&#65039;&#11015;&#65039;&#11015;&#65039;</p>
      <p>
          <a href="https://journal.optivem.com/p/tdd-in-legacy-code-contract-tests-components">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[The Best Architects Never Stop Coding]]></title><description><![CDATA[You can't feel what your own design costs unless you build something in it. And nobody is going to tell you.]]></description><link>https://journal.optivem.com/p/the-best-architects-never-stop-coding</link><guid isPermaLink="false">https://journal.optivem.com/p/the-best-architects-never-stop-coding</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Tue, 11 Aug 2026 06:00:37 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/a5104542-efe3-46dc-9bf0-98ad16ef7f79_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><p>&#8220;Architects don&#8217;t code.&#8221;</p><p>I&#8217;ve heard this sentence too many times.</p><p>And every time, I worry.</p><p>Because the moment someone thinks coding is beneath them, they are already moving away from the thing they&#8217;re supposed to understand.</p><p>The code.</p><h2>The good ones</h2><p>The best architects I&#8217;ve worked with never stopped writing code.</p><p>They reviewed pull requests.</p><p>They paired with developers when something got stuck.</p><p>They built the first few features of what they designed.</p><p>They understood the pain developers faced because they experienced it themselves.</p><h2>"Coding is below me"</h2><p>In many companies, an &#8220;architect&#8221; is what you get when there is nowhere else to go except management.</p><p>The architect stops coding. Not because the job doesn&#8217;t need it, but because the title says they&#8217;ve outgrown it.</p><p>Then the calendar fills up. Meetings, roadmaps, steering committees.</p><p>A year later they are deciding how the system should be built, without knowing how it is built.</p><h2>&#8220;But nobody writes code by hand anymore&#8221;</h2><p>This is where someone brings up AI.</p><p>If developers are generating most of the code, why should an architect write any?</p><p>Because typing the code was never the point. Knowing how the system is actually built was.</p><p>You can know that without writing every line yourself. You can&#8217;t know it without reading any of it.</p><p>Someone still has to notice that logging is now done three different ways in three different places. Or validation. Or error handling. Nobody told the model how this codebase does any of it, so it picked something reasonable each time.</p><p>What you want is consistency in the design itself: the same kind of problem solved with the same structure everywhere, so that a developer who has read one part of the codebase can predict what the next part looks like. No diagram gives you that. It comes from someone reading the code and saying, we already have a way of doing this, use that one.</p><p>That work is closer to the code than a diagram ever gets you. An architect who only reviews the design, and never looks at what came out the other end, is exactly as far away as before. The tooling is just newer.</p><p>If anything, AI makes it worse. More code arrives, faster, and less of it has been thought about by anyone. Judging whether it fits the system is more work than it used to be, not less.</p><h2>Don&#8217;t become an ivory tower architect</h2><p>The dangerous architect is the one who designs a system they would never have to maintain.</p><p>There is no way to find out what a design costs except to build something in it. Add a feature. Change one that already exists. Fix a bug in it six months later. That&#8217;s when you find out which parts of your own design fight you &#8212; the layer you have to touch every time, the abstraction nobody can extend without asking you first, the test that takes ten lines of setup before it can assert anything.</p><p>And don&#8217;t expect anyone to report it back to you.</p><p>Developers mostly won&#8217;t tell you the design is painful. You&#8217;re the architect, you decided it, and to them it&#8217;s a given &#8212; something to work around, not something to question. The more junior they are, the less likely you are to hear about it at all. What comes back is &#8220;it took a bit longer than we thought.&#8221;</p><p>If you haven&#8217;t opened the codebase in months, you&#8217;re not designing the system anymore.</p><p>You&#8217;re describing one.</p><div><hr></div><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>&#128467; Aug 26<br>&#9200; 5:00&#8211;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>]]></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[Nobody Wanted to Write Tests]]></title><description><![CDATA[&#8220;We don&#8217;t have time to write the code twice.&#8221;]]></description><link>https://journal.optivem.com/p/nobody-wanted-to-write-tests</link><guid isPermaLink="false">https://journal.optivem.com/p/nobody-wanted-to-write-tests</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Tue, 04 Aug 2026 06:02:15 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/e2d564f4-fd9a-4253-b02e-b5c1b3fead4f_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128075; <em>Hello, this is Valentina with the free edition 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 I started my career in software development, I thought I was surrounded by the wrong people.</p><p><strong>Nobody wanted to write tests.</strong></p><p><strong>Nobody cared about clean code.</strong></p><p>Every conversation about improving the codebase turned into an argument.</p><p>I remember thinking:</p><p>&#8220;If I just join a better company, things will be different.&#8221;</p><h2>The next company was supposed to be different</h2><p>I moved to another company.</p><p>Surely this time it would be different.</p><p>More experienced developers.</p><p>Better engineers.</p><p>Except...</p><p>The same problems were there.</p><blockquote><p>&#8220;Tests are a waste of time.&#8221;</p><p>&#8220;We&#8217;ll clean up the code later.&#8221;</p><p>&#8220;We have always done it this way.&#8221;</p></blockquote><h2>Being a Senior Developer didn&#8217;t change anything</h2><p>As a Senior Developer, I though:</p><blockquote><p>&#8220;Now I can finally influence how we build software.&#8221;</p><p>We would write better code.</p><p>We would add tests.</p><p>We would stop rushing changes into production.</p></blockquote><p>I was wrong.</p><p>Even getting people to write one test was a battle.</p><blockquote><p>&#8220;Why would we write twice as much code?&#8221;</p><p>&#8220;Now we have to maintain the application code and all the tests too?&#8221;</p></blockquote><h2>The smaller company as a Tech Lead</h2><p>The next move was to a smaller company, as a Team Lead.</p><p>This time I did things differently.</p><p>I <strong>stopped waiting for permission</strong>. In my own time, over several months, I built a Clean Architecture template &#8212; layered, testable, with unit tests already in place.</p><p>When I recruited developers for the team, I <strong>stopped screening only for years of experience</strong>. I screened for whether they cared about quality.</p><p>Then I built the first module with the template myself, end to end, so there was something real to point at.</p><p>And then I trained the team to do it the same way.</p><h2>Six months later</h2><p>Within six months, that team had shipped more features than the &#8220;senior&#8221; developers at my previous companies had managed in years.</p><p>Same industry. Same kind of problems.</p><p>The difference wasn&#8217;t that these developers were smarter. It was that nobody had to be convinced. The template made the right way the easy way, the hiring made the standard shared, and <strong>the training made it something the whole team could do</strong> &#8212; not something one person kept arguing for.</p><p>That&#8217;s what I had been getting wrong for years.</p><p>You don&#8217;t change a team by caring harder than everyone else. You change it by <strong>building the thing, showing it works</strong>, and bringing people with you.</p><p>The template was what made that possible. So that&#8217;s what I want to show you how to design.</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>&#128467; Aug 26<br>&#9200; 5:00&#8211;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><p></p><p></p><p></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 Is Overhead."]]></title><description><![CDATA[Stop counting lines of code]]></description><link>https://journal.optivem.com/p/clean-architecture-is-overhead</link><guid isPermaLink="false">https://journal.optivem.com/p/clean-architecture-is-overhead</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Tue, 28 Jul 2026 06:01:50 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/8e607007-adb9-4a59-9711-1c4355f9ac77_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128075; <em>Hello, this is Valentina with the free edition 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>&#8220;Clean Architecture is overkill.&#8221;</p><p>I&#8217;ve heard that a lot of times.</p><p>Too many interfaces. Too much abstraction. Too much indirection.</p><p>And some people go further and argue it makes the system harder to maintain, harder to understand.</p><h2>The overhead is real</h2><p>Compared to a plain CRUD application, yes, there are more interfaces. Specifically for anything that is infrastructure: databases, the file system, external REST APIs.</p><p>Instead of directly opening an HTTP client connection, or directly opening a database connection, or directly working with the ORM, you put an interface in between. Your use cases depend on those interfaces, i.e. abstractions of infrastructure, rather than directly depending on the infrastructure details.</p><p>And as a developer you have more files to look at, because when you open your use case class you&#8217;re not going to see the direct call to the HTTP client.</p><p>That&#8217;s overhead. I&#8217;m not going to pretend it isn&#8217;t.</p><h2>But it&#8217;s nothing compared to the size of the codebase</h2><p>For midsize and larger projects, that overhead is nothing compared to the size of the codebase.</p><p>It&#8217;s like someone arguing: let&#8217;s not use Java/C#, let&#8217;s use C/C++ for everything, because C and C++ are faster. Microseconds, milliseconds, whatever the number is. </p><p>And yet developers still use Java and .NET anyway, because the trade-off is worth what they get back.</p><p>Same thing here.</p><p><strong>A handful of extra interface files is not the argument they think it is</strong>.</p><h2>The harder one: domain entities vs ORM entities</h2><p>The complaint that&#8217;s genuinely harder to argue against is this one: you have your domain entities and you have your ORM entities, so now you&#8217;re doing double the work and maintaining double the code.</p><p>Initially, on a really small project, I can see that argument being hard to fight. At that point the domain entities and the ORM entities are most likely one-to-one identical. So yes, it does feel like duplication. It looks like you typed the same class twice for no reason.</p><p>But what happens, quite often, is that enterprise projects <strong>grow in complexity</strong>. And  that&#8217;s exactly when being coupled to the database hurts you.</p><p>The one-to-one mapping stops being one-to-one, and the class they were calling duplication turns out to be the reason you can change the business logic without being <strong>trapped by your database structure</strong>.</p><h2>Stop counting lines of code</h2><p>Instead of asking: &#8220;How many extra files does Clean Architecture create?&#8221; or &#8220;How many extra lines of code does this add?&#8221;&#8230;</p><p>Think about <strong>how hard it is to understand the code</strong>.</p><p>Separation of concerns means separating business logic from infrastructure so that it&#8217;s easier for our brains.</p><p>When you&#8217;re thinking about business requirements, you don&#8217;t want to also be thinking about what the external REST API DTO looks like. Later when you shift to integrating with external systems, that&#8217;s when you think about the nitty-gritty of the I/O and the DTOs. Those are <strong>two separate concerns</strong>. You go into one of them at a time.</p><p>That&#8217;s less effort to understand the code. That&#8217;s the payoff, and it doesn&#8217;t show up anywhere in a line count.</p><p>So next time someone tells you Clean Architecture is overhead, don&#8217;t argue about the number of files. Ask them how many things they have to hold in their head to change one business rule.</p><h2>&#9889;Clean Architecture in practice</h2><p>How do you separate business logic from infrastructure?<br>How to decouple the domain from the ORM?<br>When is an abstraction useful&#8212;and when is it over-engineering?</p><p>Join the live training session:</p><p><span>&#128467; </span><strong><a href="https://optivem.thinkific.com/products/live_events/clean-architecture-for-backend-developers">Clean Architecture for Backend Developers</a></strong><span> (Wed Aug 26)</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><p></p><p></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[Every Developer Is Already an Architect]]></title><description><![CDATA[Most developers think architecture is someone else&#8217;s job.]]></description><link>https://journal.optivem.com/p/every-developer-is-already-an-architect</link><guid isPermaLink="false">https://journal.optivem.com/p/every-developer-is-already-an-architect</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Tue, 21 Jul 2026 13:02:36 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/cc9ff4ab-e13d-4502-a079-a8f662c55d54_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128075; <em>Hello, this is Valentina with the free edition 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 developers think architecture is someone else&#8217;s job.</p><p>It&#8217;s what the architects do.</p><p>Or the tech leads.</p><p>Or the people who spend their days drawing boxes and arrows.</p><p>It&#8217;s not on your job description, so it&#8217;s not your problem.</p><p>Except it is.</p><p>By the time you&#8217;ve been building software for a few years, <strong>you&#8217;re already making architectural decisions.</strong></p><p>You just don&#8217;t call them that.</p><h2>A year later it's called "The Architecture"</h2><p>You&#8217;re implementing a new feature. An order has to notify the warehouse once it&#8217;s paid.</p><p>You need to decide where that notification comes from.</p><p>Do you call the warehouse API straight from the payment handler, because it&#8217;s two lines and the ticket is due Thursday?</p><p>Do you publish an event instead, and let the warehouse subscribe to it?</p><p>Do you put the rule &#8212; <em>an order notifies the warehouse once it&#8217;s paid</em> &#8212; inside <code>Order</code>, or in whichever service happens to be holding it?</p><p>Do you reuse the HTTP client that&#8217;s already wired up in the payment module, or give the warehouse call its own?</p><p>None of these decisions feel like &#8220;architecture.&#8221;</p><p>They seem to be just implementation details.</p><p><strong>Until a year later.</strong></p><p>A year later, a new developer joins and asks why payment knows about the warehouse. Why the notification rule lives in a service instead of in the order. Why there are four HTTP clients configured against the same host.</p><p>Nobody has an answer. The person who made those calls has moved on, or doesn&#8217;t remember, or never thought about it much...</p><p>But the answers have hardened. The direct call became the pattern everyone copied. The rule in the service became the reason <code>Order</code> can&#8217;t be tested without a database. The four clients became the reason a timeout change takes a day.</p><p><strong>That&#8217;s what the team now calls &#8220;the architecture.&#8221;</strong></p><p>Nobody designed it. It accumulated.</p><h2>The line between developer and architect isn't a promotion</h2><p>Architecture isn&#8217;t something you do before writing code.</p><p>It&#8217;s something you do every time you write code.</p><p>So the difference between a developer and an architect isn&#8217;t the title, and it isn&#8217;t permission from someone above you. It&#8217;s whether the decision got made on purpose.</p><p>Sometimes the direct call really is the right answer.</p><p>Picture two developers who both write it. The first one weighed it: the warehouse call is rare, a failed notification is recoverable, an event bus would cost more than it saves right now. The second one wrote it because it was the shortest path on a Thursday.</p><p>Read the code today and you can&#8217;t tell them apart. <strong>It&#8217;s the same two lines.</strong></p><p>The difference shows up the day the assumption breaks &#8212; when the call stops being rare, or a lost notification starts costing real money.</p><p>The first developer knows exactly what they traded away, so they recognize the moment the trade stops paying.</p><p>The second left nothing behind to revisit, so nobody notices. The line just gets copied into the next three features, because by then it looks like a decision somebody made.</p><p>Next time you reach for the shortest path, ask the architect&#8217;s question: <strong>what does this make cheap later, and what does it make expensive?</strong></p><p>You&#8217;re already making the decision.</p><p>Make it deliberately.</p><h2>&#9889;Clean Architecture in practice</h2><p>Which class should own a business rule?<br>What&#8217;s allowed to depend on what?<br>Which calls to the outside world you should be able to swap for a fake when you test?</p><p>Nobody tells you that along with the title.</p><p>Join the live training session:</p><p>&#128467; <strong><a href="https://optivem.thinkific.com/products/live_events/clean-architecture-for-backend-developers">Clean Architecture for Backend Developers</a></strong> (Wed Aug 26)</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[TDD - Stop Mocking JPA: You're Testing the Wrong Thing]]></title><description><![CDATA[Code Example]]></description><link>https://journal.optivem.com/p/tdd-stop-mocking-jpa-youre-testing-wrong-thing</link><guid isPermaLink="false">https://journal.optivem.com/p/tdd-stop-mocking-jpa-youre-testing-wrong-thing</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 17 Jul 2026 06:01:26 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/1c9112a7-5683-4773-a717-4d5de49ab6b7_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>One of the biggest mistakes I see in Spring applications is developers mocking JPA.</p><p>When tests become slow, they replace the database with mocked <code>JpaRepository</code> interfaces.</p><p>The tests are faster.</p><p>But the architecture is now coupled to the ORM.</p><h2>Why Mocking JPA Is a Design Smell</h2><p>Developers mock JPA because they want:</p><ul><li><p>fast tests</p></li><li><p>no database</p></li><li><p>isolated business logic</p></li></ul><p>Those are good goals.</p><p>Mocking JPA isn&#8217;t.</p><p>Why?</p><ul><li><p>You&#8217;re mocking a framework you don&#8217;t own</p></li><li><p>Your tests depend on Spring Data instead of your own abstractions</p></li><li><p>Changing persistence forces you to change unit tests</p></li><li><p>Business logic stays coupled to the ORM</p></li></ul><p>The mock removes the database.</p><p>It doesn&#8217;t remove the dependency on the ORM.</p><h2>The Real Problem</h2><p>One team I worked with couldn&#8217;t upgrade their ORM for months because of a change to how inheritance was mapped.</p><p>The application wasn&#8217;t the problem.</p><p>The business logic wasn&#8217;t the problem.</p><p>The problem was that the ORM had leaked into the application layer and the tests.</p><p>A change in persistence rippled through the entire codebase.</p><p><strong>A concrete example:</strong></p><ul><li><p>In .NET, EF Core's Table-Per-Hierarchy inheritance auto-adds a <code>Discriminator</code> column, and its behavior has shifted across major versions &#8212; nullability, length, how it's configured and queried.</p></li><li><p>An upgrade changes the discriminator, and that change ripples into every piece of code that touches those entities.</p></li><li><p>JPA has the same trap: single-table inheritance auto-creates a <code>DTYPE</code> discriminator column, so a change to the mapping reaches straight into the application layer and its tests.</p></li></ul><h1>&#128161; Code Example</h1>
      <p>
          <a href="https://journal.optivem.com/p/tdd-stop-mocking-jpa-youre-testing-wrong-thing">
              Read more
          </a>
      </p>
   ]]></content:encoded></item></channel></rss>