<?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>Fri, 15 May 2026 19:21:04 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[Hexagonal Architecture: The “Microservices First” Mistake]]></title><description><![CDATA[The Distributed Monolith Trap]]></description><link>https://journal.optivem.com/p/hexagonal-architecture-the-microservices-first-mistake</link><guid isPermaLink="false">https://journal.optivem.com/p/hexagonal-architecture-the-microservices-first-mistake</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 15 May 2026 06:01:27 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/0a32f553-6b84-46b0-9921-b4db69100fbf_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You went straight into microservices because&#8230; well, everyone else was doing it.</p><p>&#8220;Scalable.&#8221;<br>&#8220;Cloud-native.&#8221;<br>&#8220;Future-proof.&#8221;<br>&#8220;Independent deployments.&#8221;</p><p>Suddenly there are:</p><ul><li><p>12 services</p></li><li><p>14 repositories</p></li><li><p>RabbitMQ</p></li><li><p>Kafka</p></li><li><p>Kubernetes</p></li></ul><p>&#8230;before you even fully understand the domain.</p><h2>&#10060; The Distributed Monolith Trap</h2><p>You are guessing domain boundaries, so microservice boundaries based on your guesses.</p><p>The problem is you discover, during the next months, that you&#8217;ve modelled the domain in the wrong way. You discover that your microservices are too tightly coupled, and to implement a simple User Story, you have to coordinate changes across multiple microservices. Changes become expensive.</p><p>Immediately jumping to microservices &#8212; especially the wrong microservices &#8212; is expensive to build and expensive to run.</p><p>You can call it &#8220;microservices&#8221;&#8230;</p><p>&#8230;but it&#8217;s just a distributed monolith.</p><h2>&#9888;&#65039;Fear of the Monolith</h2><p>Some developers hear &#8220;monolith&#8221; and imagine:</p><ul><li><p>massive bloated classes</p></li><li><p>tangled dependencies</p></li><li><p>painful future migrations</p></li></ul><p>But not every monolith has to become a &#8220;big ball of mud.&#8221;</p><div><hr></div><p>&#128640; <strong>Register now</strong>: <a href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop">ATDD &#8211; Acceptance Testing Workshop</a><br>Get 100 EUR off with code <strong>DISCOUNT_100</strong></p><div><hr></div><h2>Hexagonal Architecture Inside the Modules</h2><ul><li><p>Modular Monolith &#8594; all the modules are deployed as a single artifact</p></li><li><p>Microservices &#8594; each microservice is deployed independently</p></li></ul><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fcUr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e52bda1-1819-460e-ba58-bd5cdf13ca17_5222x5422.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fcUr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e52bda1-1819-460e-ba58-bd5cdf13ca17_5222x5422.png 424w, https://substackcdn.com/image/fetch/$s_!fcUr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e52bda1-1819-460e-ba58-bd5cdf13ca17_5222x5422.png 848w, https://substackcdn.com/image/fetch/$s_!fcUr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e52bda1-1819-460e-ba58-bd5cdf13ca17_5222x5422.png 1272w, https://substackcdn.com/image/fetch/$s_!fcUr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e52bda1-1819-460e-ba58-bd5cdf13ca17_5222x5422.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fcUr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e52bda1-1819-460e-ba58-bd5cdf13ca17_5222x5422.png" width="1456" height="1512" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9e52bda1-1819-460e-ba58-bd5cdf13ca17_5222x5422.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1512,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4689510,&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/197195853?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e52bda1-1819-460e-ba58-bd5cdf13ca17_5222x5422.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_!fcUr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e52bda1-1819-460e-ba58-bd5cdf13ca17_5222x5422.png 424w, https://substackcdn.com/image/fetch/$s_!fcUr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e52bda1-1819-460e-ba58-bd5cdf13ca17_5222x5422.png 848w, https://substackcdn.com/image/fetch/$s_!fcUr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e52bda1-1819-460e-ba58-bd5cdf13ca17_5222x5422.png 1272w, https://substackcdn.com/image/fetch/$s_!fcUr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e52bda1-1819-460e-ba58-bd5cdf13ca17_5222x5422.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p>
      <p>
          <a href="https://journal.optivem.com/p/hexagonal-architecture-the-microservices-first-mistake">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Why Developers Hate Meetings]]></title><description><![CDATA[Meetings that go nowhere]]></description><link>https://journal.optivem.com/p/why-developers-hate-meetings</link><guid isPermaLink="false">https://journal.optivem.com/p/why-developers-hate-meetings</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Wed, 13 May 2026 06:02:09 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/1902f0df-2ce0-45ff-8a58-136dee9627cb_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It&#8217;s not because developers hate talking.</p><p>Developers can talk for hours &#8212; just ask them about architecture.</p><h2>The 6-person call where 2 people speak</h2><p>Two people talk the entire time.</p><p>The rest are:</p><ul><li><p>on mute</p></li><li><p>pretending to follow</p></li><li><p>occasionally saying &#8220;yeah&#8221; at the wrong moment</p></li></ul><h2>The developer translation layer</h2><ul><li><p>&#8220;we&#8217;ll figure it out&#8221; &#8594; this is now my problem</p></li><li><p>&#8220;should be simple&#8221; &#8594; it absolutely isn&#8217;t</p></li><li><p>&#8220;just a small change&#8221; &#8594; redesign incoming</p></li><li><p>&#8220;we can iterate&#8221; &#8594; nothing is defined</p></li></ul><p>You get asked for estimates on something that is vaguely described.</p><p>You give a number.</p><p>Everyone writes it down like it&#8217;s a fact.</p><p>Then later the same number becomes:</p><blockquote><p>&#8220;why is this taking longer than expected?&#8221;</p></blockquote><h2>What developers actually need</h2><p>Developers don&#8217;t need more meetings.</p><p>They don&#8217;t need longer discussions either.</p><p>Not 20 minutes of debate.</p><p>Not &#8220;we&#8217;ll figure it out later.&#8221;</p><p>Just enough concrete detail so the work isn&#8217;t being invented mid-implementation.</p><p>A few examples instead of abstract descriptions.</p><p>Most of the frustration comes from building something that wasn&#8217;t fully defined, and then being judged as if it was.</p><h2>Vague requests &#8594; Concrete examples</h2><p>This is <em>exactly</em> where acceptance testing and ATDD help.</p><p>They take requirements that sound like:</p><blockquote><p>&#8220;It should handle refunds properly&#8221;</p></blockquote><p>and turn them into concrete examples developers can implement.</p><p>Acceptance tests turn that into questions like:</p><ul><li><p>Given this input, what should happen?</p></li><li><p>When this happens, what is the expected output?</p></li><li><p>What should <em>not</em> happen?</p></li></ul><div><hr></div><p>&#9889;<strong>Ready to see this in action?</strong></p><p>I&#8217;m running a hands-on <strong><a href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop">ATDD &#8211; Acceptance Testing Workshop</a></strong> on May 25&#8211;26 (4 hours).</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop&quot;,&quot;text&quot;:&quot;Join the workshop &#8594;&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop"><span>Join the workshop &#8594;</span></a></p><p>Limited spots. Register now - 100 EUR off with code <strong>DISCOUNT_100</strong></p>]]></content:encoded></item><item><title><![CDATA[Most Bugs Start Before Coding]]></title><description><![CDATA[The bug wasn&#8217;t in the code]]></description><link>https://journal.optivem.com/p/most-bugs-start-before-coding</link><guid isPermaLink="false">https://journal.optivem.com/p/most-bugs-start-before-coding</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Mon, 11 May 2026 06:41:46 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/33eb2340-814f-41f2-bb91-6d19db72eff8_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>I thought bugs came from bad code.</p><p>But I noticed that a lot of the bugs we were fixing in QA&#8230;<br>weren&#8217;t really coding bugs.</p><p>The implementation matched the ticket.<br>The unit tests passed.<br>The code review passed.</p><p>And somehow, the feature was &#8220;wrong behavior.&#8221;</p><p>Not because the code was broken.</p><p>But because people had imagined different things in their mind &#8212; usually in the same meeting where everyone thought they agreed.</p><p>That mismatch only shows up later.</p><p>QA raises issues.</p><p>The product owner says: &#8220;That&#8217;s not what I expected.&#8221;</p><h2>Acceptance Testing Isn&#8217;t Really About Testing</h2><p>Acceptance testing &#8212; or ATDD &#8212; isn&#8217;t just about &#8220;more tests.&#8221;</p><p>It&#8217;s about creating shared understanding before implementation starts.</p><p>It&#8217;s asking questions like:</p><ul><li><p>What should happen here?</p></li><li><p>What does success actually look like?</p></li><li><p>Can we describe this with real examples?</p></li><li><p>What happens in edge cases?</p></li></ul><p>That&#8217;s where a lot of bugs get prevented.</p><p>Not during coding.</p><p>During alignment.</p><h2>&#9940; Without Acceptance Testing</h2><p>The developer gets a ticket: &#8220;Apply a 10% discount to orders over &#8364;100.&#8221;</p><p>The developer reads the ticket and assumes:</p><ul><li><p>discount applies before tax</p></li><li><p>only for logged-in users</p></li><li><p>applies to the entire cart total</p></li></ul><p>QA, later on, tests and assumes:</p><ul><li><p>discount applies after tax</p></li><li><p>also works for guest users</p></li><li><p>applies per item, not cart total</p></li></ul><p>The product owner expected:</p><ul><li><p>discount applies before tax</p></li><li><p>only for logged-in users</p></li><li><p>based on cart total</p></li></ul><p>Same ticket. Three different interpretations.</p><p>And nobody realises until QA finds it &#8212; or worse, after release.</p><h2>&#9989; With Acceptance Testing</h2><p>Before coding starts, the team writes examples like:</p><ul><li><p>Given the customer is logged in, when the customer makes an order of &#8364;120, then a 10% discount is applied before tax</p></li><li><p>Given the customer is logged in, when the customer makes an order of &#8364;80, no discount is applied</p></li><li><p>Given a guest user, when the guest makes an order of &#8364;150, no discount is applied</p></li></ul><p>Suddenly, there&#8217;s no room for interpretation.</p><p>Everyone is reacting to the same examples &#8212; not their own mental version of the feature.</p><p>That&#8217;s the shift.</p><p>Acceptance testing doesn&#8217;t add &#8220;more testing.&#8221;</p><p>It removes guessing before the code is even written.</p><h2>Why Teams Skip This</h2><p>Teams want to jump straight into implementation.</p><p>Writing code feels productive.</p><p>Clarifying expectations sometimes feels like &#8220;extra process.&#8221;</p><p>Until the feature comes back three times for rework.</p><p>Then suddenly those early conversations don&#8217;t seem so expensive anymore.</p><h2>See It In Practice</h2><p>I&#8217;m running a live, hands-on workshop where we build acceptance tests. We go from vague requirements &#8594; to concrete examples &#8594; to executable acceptance tests.</p><p>&#9889;In 4 hours, you&#8217;ll:</p><ul><li><p>Design acceptance tests using DSL + driver architecture</p></li><li><p>Apply ATDD Cycle with AI to write an acceptance test and implement the change</p></li><li><p>Learn how to introduce acceptance testing in your team</p></li></ul><p>&#128197; May 25-26, 2:00-4:00 PM CET</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop&quot;,&quot;text&quot;:&quot;Join the workshop &#8594;&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop"><span>Join the workshop &#8594;</span></a></p><p>Limited spots. Register now and get 100 EUR off with code <strong>DISCOUNT_100</strong></p>]]></content:encoded></item><item><title><![CDATA[Clean Architecture: Do NOT Inject Loggers Everywhere]]></title><description><![CDATA[Code Example]]></description><link>https://journal.optivem.com/p/clean-architecture-do-not-inject-loggers-everywhere</link><guid isPermaLink="false">https://journal.optivem.com/p/clean-architecture-do-not-inject-loggers-everywhere</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 07 May 2026 06:01:18 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/79b41787-cb3a-4231-8d5c-25c0cd0285a2_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>Logging injected directly into use cases, services and handlers is justified as:</p><ul><li><p>&#8220;we need debugging&#8221;</p></li><li><p>&#8220;we need traceability&#8221;</p></li><li><p>&#8220;we need observability&#8221;</p></li></ul><p>But&#8230;</p><h2>&#10060;Logger in every use case</h2><ul><li><p>every use case now knows about logging</p></li><li><p>business logic is mixed with runtime reporting</p></li><li><p>logs become part of how code is &#8220;explained&#8221;</p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;425f953c-ab2e-49cb-a093-2a839d031481&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">class CreateOrderUseCase {

    private final Logger logger;

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

    public OrderResult execute(CreateOrderCommand cmd) {

        logger.info("CreateOrder started");

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

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

        return new OrderResult(order.getId());
    }
}</code></pre></div><h2>&#9888;&#65039;Structured logging doesn&#8217;t fix the real issue</h2><p>After a while, debugging becomes painful, so you switch to structured logging:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;37f17337-3ff0-4a17-bb19-7a01b9d983c0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">logger.info("OrderCreated", Map.of(
    "userId", cmd.userId(),
    "orderId", order.getId()
));</code></pre></div><p>This is better:</p><ul><li><p>easier to search</p></li><li><p>consistent format</p></li><li><p>better for tools</p></li></ul><p>But the real problem doesn&#8217;t change.</p><p>You still have logging calls inside every use case.</p><p>You&#8217;ve just improved <em>how logs look</em>.</p><div><hr></div><p>&#128640; <strong>Register now</strong>: <a href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop">ATDD &#8211; Acceptance Testing Workshop</a><br>Get 100 EUR off with code <strong>DISCOUNT_100</strong></p><div><hr></div><h2>&#9989;Infrastructure Layer: Logging outside the use case</h2>
      <p>
          <a href="https://journal.optivem.com/p/clean-architecture-do-not-inject-loggers-everywhere">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[I Thought I Was a Developer. I Was Just Retesting.]]></title><description><![CDATA[Every change meant retesting]]></description><link>https://journal.optivem.com/p/i-thought-i-was-a-developer-i-was-just-retesting</link><guid isPermaLink="false">https://journal.optivem.com/p/i-thought-i-was-a-developer-i-was-just-retesting</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Tue, 05 May 2026 06:00:47 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/0369cb47-628c-4d8c-830c-eb4d2adbec6e_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>I thought software development meant building things.</p><p>Then I got my first job.</p><p>And most of my time wasn&#8217;t building.</p><p>It was <strong>retesting</strong>.</p><p>Fix something &#8594; test it<br>Add something &#8594; test it<br>Change something small &#8594; test everything again</p><p>The system felt fragile.<br>Like it was waiting to break.</p><p>No one called it &#8220;testing&#8221;&#8212;I was a developer, after all.<br>But in reality, I was running the same manual checks again and again just to feel confident enough to move forward.</p><p>It was exhausting.</p><h2>The first breakthrough</h2><p>Then I discovered unit tests.</p><p>Write the test once &#8594; run it forever.</p><p>No more clicking through the same flows.<br>No more guessing what broke.</p><p>If something failed, I knew exactly where.</p><p>It felt like I finally unlocked &#8220;real&#8221; development.</p><h2>Everything passed. Things still broke.</h2><p>But then something didn&#8217;t make sense.</p><p>All my tests were passing.</p><p>And production bugs&#8230;<br>weren&#8217;t really going down.</p><p>That was the confusing part.</p><p>If everything was &#8220;tested&#8221;&#8230;<br>why were things still breaking?</p><p>Here&#8217;s what I was missing:</p><p>Unit tests tell you that <strong>pieces</strong> of your system work.</p><p>They don&#8217;t tell you that the <strong>system works</strong>.</p><h2>You don&#8217;t ship code. You ship behavior.</h2><p>You can have 100% unit test coverage&#8230;<br>and still ship a broken feature.</p><p>Because users don&#8217;t interact with your classes.</p><p>They interact with behavior.</p><p>That&#8217;s what led me to acceptance tests.</p><p>Tests that don&#8217;t care how the code works (you can write them even if your code is a Ball of Mud)&#8212;<br>only whether the system behaves correctly from the outside.</p><p>Like a user would experience it.</p><p>Because in the end:</p><p>You don&#8217;t ship code.<br>You ship behavior.</p><h2>See It in Practice</h2><p>Want to see how acceptance tests can save you time, frustration, and endless bug loops? I&#8217;m running a hands-on <strong><a href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop">Acceptance Testing Workshop</a></strong> on May 25&#8211;26 (4 hours).</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop&quot;,&quot;text&quot;:&quot;Join the workshop &#8594;&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop"><span>Join the workshop &#8594;</span></a></p><p>Limited spots. Register now - <strong>100 EUR off with code DISCOUNT_100</strong></p><p></p>]]></content:encoded></item><item><title><![CDATA[Stop Duplicating Acceptance Tests]]></title><description><![CDATA[How to avoid double maintenance costs]]></description><link>https://journal.optivem.com/p/stop-duplicating-acceptance-tests</link><guid isPermaLink="false">https://journal.optivem.com/p/stop-duplicating-acceptance-tests</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 30 Apr 2026 06:01:47 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/c5981a40-eea9-44e2-bd83-620c6fb11704_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>Most developers test the same behavior twice.</p><p>Once through the API.<br>Once through the UI.</p><p>Two separate tests suites.<br>Two sets of assertions.<br>Double the maintenance costs.</p><p>And sooner or later?&#8230;</p><p>The API tests pass.<br>The UI tests fail randomly.<br>Nobody fully trusts either.</p><h2>&#9888;&#65039;Duplication is risky</h2><p>If you test both channels, you end up with two completely separate test suites:</p><ul><li><p>An API test suite that sends HTTP requests and checks JSON responses</p></li><li><p>A UI test suite that drives a browser with Selenium or Playwright</p></li></ul><p>Same scenarios. No shared structure. No shared assertions.</p><p>When a requirement changes, you update two tests instead of one.</p><p>Worse &#8212; the two suites start contradicting each other.</p><p>The API tests check one set of scenarios.<br>The UI tests check a slightly different set.</p><p>Gaps appear. Bugs hide in the gaps.</p><div><hr></div><p><strong>Want to skip the pain and go straight to the solution?</strong></p><p>I&#8217;m running a live, hands-on workshop where we build acceptance tests that run against both the API and UI &#8212; compile-time safe, IDE-guided and refactor-proof.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop&quot;,&quot;text&quot;:&quot;Join the workshop &#8594;&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop"><span>Join the workshop &#8594;</span></a></p><p>Limited spots. Register now with the early bird discount - <strong>100 EUR off with code EARLYBIRD100 </strong></p><div><hr></div><h2>&#128161;What Changed Everything for Me</h2><p>Instead of writing the same test twice&#8230;</p>
      <p>
          <a href="https://journal.optivem.com/p/stop-duplicating-acceptance-tests">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Hexagonal Architecture: Your Driven Ports Are Leaking Infrastructure]]></title><description><![CDATA[Clean Interfaces, Leaky Abstractions. Your driven ports look clean &#8212; but they're leaking infrastructure into your domain. Your domain is coupled to infrastructure!]]></description><link>https://journal.optivem.com/p/hexagonal-architecture-your-driven-ports-are-leaking-infrastructure</link><guid isPermaLink="false">https://journal.optivem.com/p/hexagonal-architecture-your-driven-ports-are-leaking-infrastructure</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 23 Apr 2026 06:00:39 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/84c48044-cd90-4af2-ad07-77166096b59b_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>You followed the pattern.</p><p>You split your domain from infrastructure.<br>You added interfaces.<br>You called them &#8220;driven ports.&#8221;</p><p>It looks clean.</p><p>But then:</p><ul><li><p>Your tests are full of mocks</p></li><li><p>Refactoring breaks everything</p></li><li><p>Your domain still feels&#8230; tied to something</p></li></ul><p>And you can&#8217;t quite explain why.</p><h2>Driven Ports Gone Wrong</h2><p>Most developers know they need interfaces between their domain and infrastructure. But the mistake isn&#8217;t forgetting to add them.</p><p>It&#8217;s adding them at the wrong level of abstraction.</p><p>Your driven port might look clean on the surface &#8212; but if infrastructure concepts are leaking through, your domain is already compromised.</p><h2>What a Driven Port Should Do</h2><p>A driven port isn&#8217;t:</p><ul><li><p>a wrapper around a framework class</p></li><li><p>a thin layer over a database driver</p></li><li><p>a generic interface that exposes how things work underneath</p></li></ul><p>A driven port is:</p><ul><li><p>something the domain needs to get its work done</p></li><li><p>a boundary expressed in domain language</p></li><li><p>a contract that hides <em>how</em> things are implemented</p></li></ul><p>The key question: <strong>does this interface expose what the domain needs, or how the infrastructure works?</strong></p><div><hr></div><p>&#128640; <strong>Register now</strong>: <a href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop">Acceptance Testing Workshop</a><br>Get 100 EUR off with code <strong>EARLYBIRD100</strong></p><div><hr></div><h2>&#10060; Driven Ports That Leak Infrastructure</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;a29ca23f-3f48-405a-8a10-3896668130f9&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">interface OrderRepository {
    ResultSet executeQuery(String sql);
    void executeBatch(String[] statements);
}

interface PaymentGateway {
    HttpResponse post(String url, Map&lt;String, String&gt; headers, String jsonBody);
}

interface NotificationService {
    void sendRawMessage(String host, int port, String payload);
}

interface ProductCatalog {
    Map&lt;String, AttributeValue&gt; getItem(String tableName, Map&lt;String, AttributeValue&gt; key);
}</code></pre></div><p>What&#8217;s wrong here?</p><ul><li><p><code>OrderRepository</code> exposes SQL &#8212; the domain now knows it&#8217;s a relational database</p></li><li><p><code>PaymentGateway</code> exposes HTTP &#8212; the domain now knows it&#8217;s a REST API</p></li><li><p><code>NotificationService</code> exposes host and port &#8212; the domain now knows about transport protocols</p></li><li><p><code>ProductCatalog</code> exposes <code>AttributeValue</code> &#8212; the domain now knows it&#8217;s DynamoDB</p></li></ul><p>Your domain is shaped by your infrastructure. Change the database, the API, or the message broker &#8212; and your domain code has to change too.</p><p>That&#8217;s the opposite of decoupling.</p>
      <p>
          <a href="https://journal.optivem.com/p/hexagonal-architecture-your-driven-ports-are-leaking-infrastructure">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Unit Tests are NOT enough!]]></title><description><![CDATA[You have 100% coverage. All the unit tests are passing. But then, in production, a horrible bug happened.]]></description><link>https://journal.optivem.com/p/unit-tests-are-not-enough-592</link><guid isPermaLink="false">https://journal.optivem.com/p/unit-tests-are-not-enough-592</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Tue, 21 Apr 2026 06:02:43 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f0d842e1-0a1f-49b1-9513-47cb1ac33f8c_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><strong>The Unit Test Passed. The Customer Was Overcharged.</strong></p><p><strong>Your unit tests can&#8217;t catch this category of bug. Here&#8217;s proof.</strong></p><p>Before, I showed you how Marco&#8217;s team shipped a <a href="https://journal.optivem.com/p/unit-tests-passed-the-bug-shipped">broken tax calculation despite having green tests across the board</a>.</p><p>I received this question:</p><p><em>&#8220;Can you show me the actual code? I want to prove this to my team.&#8221;</em></p><p>So here it is. A concrete example you can drop into any conversation about test strategy.</p><h2>The Setup</h2><p>We have a simple e-commerce system. When a customer places an order, the system needs to look up the tax rate from an external Tax API and calculate the total.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;7184c6d1-86db-4218-8bae-50b25475194b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public class TaxGateway {
    private final HttpClient httpClient;

    public double getTaxRate(String countryCode) {
        HttpResponse response = httpClient.get(
            "https://tax-api.example.com/rates/" + countryCode
        );
        JsonObject json = JsonParser.parse(response.getBody());
        return json.getDouble("rate");
    }
}</code></pre></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;d4d70deb-55b1-407d-a362-fad2363bb3a8&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public class OrderService {
    private final TaxGateway taxGateway;

    public Order placeOrder(String countryCode, double unitPrice, int quantity) {
        double basePrice = unitPrice * quantity;
        double taxRate = taxGateway.getTaxRate(countryCode);
        double taxAmount = basePrice * taxRate;
        return new Order(basePrice, taxAmount, basePrice + taxAmount);
    }
}</code></pre></div><p>The code looks correct. <code>OrderService</code> calls <code>TaxGateway</code>, which makes an HTTP call to the external Tax API. Everything is wired up properly.</p><p>But there&#8217;s a hidden bug. The Tax API returns rates as whole numbers &#8212; <code>{ "rate": 8 }</code> for 8%. The <code>TaxGateway</code> reads this value and returns it directly. It should divide by 100 first, but it doesn&#8217;t.</p><h2>The Unit Test: GREEN &#9989;</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;fb847ace-c321-44db-b0a9-bc7aa2b917e6&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">@Test
void shouldApplyTaxRateFromExternalService() {
    when(taxGatewayMock.getTaxRate("US"))
        .thenReturn(0.08);

    Order order = orderService.placeOrder("US", 20.00, 5);

    assertEquals(100.00, order.getBasePrice());
    assertEquals(8.00, order.getTaxAmount());
    assertEquals(108.00, order.getTotalPrice());
}</code></pre></div><p>This test passes. And it <em>should</em> pass &#8212; given the mock&#8217;s return value, the math is perfect.</p><p>The developer sees green. CI sees green. The PR gets approved.</p><p>But the mock is a lie. It returns <code>0.08</code> because that&#8217;s what the developer <em>assumed</em> <code>TaxGateway</code> would return. The mock replaces the entire <code>TaxGateway</code> &#8212; the HTTP call never happens, the JSON parsing never runs, and the missing <code>/ 100.0</code> is never exposed.</p><p>In production, the customer orders $100 worth of products and gets charged <strong>$900</strong> &#8212; because <code>100 * 8 = 800</code> in tax.</p><p>And here&#8217;s the thing: you can&#8217;t fix this with more unit tests. You could unit test every single class &#8212; <code>OrderService</code>, <code>TaxGateway</code> &#8212; and they would all pass. The bug lives at the boundary between your code and the real world, and unit tests <em>must</em> mock that boundary. If the mock is wrong, every test built on top of it is wrong too.</p><h2>The Acceptance Test: RED &#10060;</h2><p>Now here&#8217;s the same scenario written as an acceptance test:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;40fa9b64-0afc-4664-8041-7d119108da56&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">@Test
void shouldCalculateCorrectTotalWithCountryTaxRate() {
    scenario
        .given().product()
            .withUnitPrice("20.00")
        .and().country()
            .withCode("US")
            .withTaxRate("8%")
        .when().placeOrder()
            .withQuantity("5")
            .withCountry("US")
        .then().shouldSucceed()
        .and().order()
            .hasBasePrice("100.00")
            .hasTaxAmount("8.00")
            .hasTotalPrice("108.00");
}</code></pre></div><p>This test runs against the real system &#8212; real backend, real database, real <code>TaxGateway</code> code &#8212; but with a <strong>stub</strong> for the external Tax API. The <code>given().country().withTaxRate("8%")</code> line configures the Tax API Stub to return <code>{ "rate": 8 }</code> &#8212; exactly what the real Tax API would return.</p><p>The real <code>TaxGateway</code> code executes. The HTTP call fires against the Tax API Stub. The JSON is parsed. And the missing <code>/ 100.0</code> is exposed.</p><p><strong>Result: FAILS.</strong></p><pre><code><code>Expected taxAmount: 8.00
Actual taxAmount:   800.00

Expected totalPrice: 108.00
Actual totalPrice:   900.00</code></code></pre><p>The Tax API Stub returned <code>{ "rate": 8 }</code>, just like the real API would. <code>TaxGateway</code> read the value and returned <code>8</code> directly to <code>OrderService</code>, which calculated <code>100 * 8 = 800</code> in tax. In the unit test, the mock hid this by returning <code>0.08</code> directly. In the acceptance test, the real code path runs and the bug is caught.</p><h2>Why Unit Tests Can Never Catch This</h2><p>The bug isn&#8217;t in <code>OrderService</code>. The math is correct. The wiring is correct. The bug is inside <code>TaxGateway</code> &#8212; in how it parses the external API response.</p><p>But the unit test for <code>OrderService</code> mocks out <code>TaxGateway</code> entirely. The HTTP call never fires. The JSON parsing never runs. The bug is invisible.</p><p><strong>The unit test:</strong></p><ul><li><p>Tests <code>OrderService</code> in isolation</p></li><li><p>Replaces <code>TaxGateway</code> with a mock</p></li><li><p>The mock returns what the developer <em>assumes</em> the gateway returns</p></li><li><p>The real HTTP call and JSON parsing never execute</p></li><li><p>Can&#8217;t catch a bug inside the mocked component</p></li></ul><p><strong>The acceptance test:</strong></p><ul><li><p>Tests the full feature against the real system with stubs for external dependencies</p></li><li><p>The real <code>TaxGateway</code> code runs &#8212; HTTP call, JSON parsing, everything</p></li><li><p>The Tax API Stub behaves like the real Tax API &#8212; returning <code>{ "rate": 8 }</code></p></li><li><p>Catches bugs anywhere in the chain, not just in the class under test</p></li></ul><p>Unit tests verify your logic given your assumptions. Acceptance tests verify the feature works regardless of your assumptions. No amount of unit testing can catch a bug that&#8217;s hidden behind a mock.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8jew!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2efd618e-1b86-4668-96bf-44e0fda2ffb0_964x1142.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8jew!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2efd618e-1b86-4668-96bf-44e0fda2ffb0_964x1142.png 424w, https://substackcdn.com/image/fetch/$s_!8jew!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2efd618e-1b86-4668-96bf-44e0fda2ffb0_964x1142.png 848w, https://substackcdn.com/image/fetch/$s_!8jew!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2efd618e-1b86-4668-96bf-44e0fda2ffb0_964x1142.png 1272w, https://substackcdn.com/image/fetch/$s_!8jew!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2efd618e-1b86-4668-96bf-44e0fda2ffb0_964x1142.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8jew!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2efd618e-1b86-4668-96bf-44e0fda2ffb0_964x1142.png" width="964" height="1142" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2efd618e-1b86-4668-96bf-44e0fda2ffb0_964x1142.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1142,&quot;width&quot;:964,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:127085,&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/191975581?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2efd618e-1b86-4668-96bf-44e0fda2ffb0_964x1142.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_!8jew!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2efd618e-1b86-4668-96bf-44e0fda2ffb0_964x1142.png 424w, https://substackcdn.com/image/fetch/$s_!8jew!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2efd618e-1b86-4668-96bf-44e0fda2ffb0_964x1142.png 848w, https://substackcdn.com/image/fetch/$s_!8jew!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2efd618e-1b86-4668-96bf-44e0fda2ffb0_964x1142.png 1272w, https://substackcdn.com/image/fetch/$s_!8jew!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2efd618e-1b86-4668-96bf-44e0fda2ffb0_964x1142.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>But Wait &#8212; How Do We Know the Stub Is Correct?</h2><p>Fair question. If the acceptance test uses a Tax API Stub instead of the real Tax API, couldn&#8217;t the stub itself be wrong?</p><p>That&#8217;s where <strong>contract tests</strong> come in. A contract test runs the same scenarios against both the real Tax API and the Tax API Stub, and verifies they return the same results. If the stub drifts from reality, the contract test fails.</p><p>So the full safety net looks like this:</p><ul><li><p><strong>Unit tests</strong> verify each component&#8217;s logic in isolation</p></li><li><p><strong>Acceptance tests</strong> verify the feature works end-to-end, using stubs for external systems</p></li><li><p><strong>Contract tests</strong> verify the stubs behave like the real external systems</p></li></ul><p>Each layer catches a different category of bug. Skip any one of them, and you have a blind spot.</p><h2>This Isn&#8217;t an Edge Case</h2><p>The bug in our example was a wrong assumption about the API response format. But this is just one of many ways external system integration breaks:</p><ul><li><p><strong>Wrong format assumption</strong> &#8212; The Tax API returns <code>{ "rate": 8 }</code> meaning 8%, but your code treats it as <code>0.08</code>. Your mock returns whatever you assumed, so the unit test passes.</p></li><li><p><strong>Hardcoded value</strong> &#8212; The developer hardcoded <code>return 0.10</code> inside <code>TaxGateway</code> while building the feature, planning to wire up the HTTP call &#8220;later.&#8221; Later never came. The unit test mocks out <code>TaxGateway</code> entirely, so the hardcoded value is never executed.</p></li><li><p><strong>Wrong field</strong> &#8212; The API returns <code>{ "taxRate": 8, "importRate": 12 }</code> and your code reads <code>importRate</code> instead of <code>taxRate</code>. Your mock only returns the field you expected, so the unit test passes.</p></li><li><p><strong>Stale mapping</strong> &#8212; The API used to return <code>{ "rate": 8 }</code> but v2 changed it to <code>{ "tax": { "rate": 8 } }</code>. Your code still reads the top-level field. Your mock still returns the old format, so the unit test passes.</p></li></ul><p>This pattern shows up constantly across all types of external integrations:</p><ul><li><p>A <strong>payment gateway</strong> that returns amounts in cents, but your code treats them as dollars.</p></li><li><p>A <strong>shipping API</strong> that changed its response format in v2, but your mocks still return v1.</p></li><li><p>An <strong>inventory service</strong> that returns <code>"OUT_OF_STOCK"</code> as a string, but your mock returns a boolean <code>false</code>.</p></li><li><p>A <strong>currency API</strong> that returns rates with 6 decimal places, but your mock rounds to 2.</p></li></ul><p>Every one of these passes unit tests &#8212; because the mock <em>is</em> the assumption. If the assumption is wrong, the mock is wrong, and the test is worthless.</p><p>Every one of these ships to production. Every one of these gets caught by acceptance tests.</p><h2>The Takeaway</h2><p>Unit tests answer: <em>&#8220;Does this component do its job correctly, given my assumptions?&#8221;</em></p><p>Acceptance tests answer: <em>&#8220;Does this feature work the way the customer expects?&#8221;</em></p><p>Contract tests answer: <em>&#8220;Are my assumptions about external systems correct?&#8221;</em></p><p>These are fundamentally different questions. If you&#8217;re only asking the first one, you&#8217;re leaving the other two for your customers to answer.</p><h2>Want to learn how to write acceptance tests like this?</h2><p>I&#8217;m running a live workshop: <strong><a href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop">Stop Shipping Bugs: Acceptance Tests Workshop</a>.</strong></p><p>4 hours. Two evenings. Live on Zoom, with me.</p><p>&#9889; I&#8217;ll walk you through the full architecture &#8212; DSL, Drivers, how it all fits together &#8212; and by the end, you&#8217;ll have written a real acceptance test from scenario to assertion.</p><p>&#128197; May 25-26, 2:00-4:00 PM CET</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop&quot;,&quot;text&quot;:&quot;Join the workshop &#8594;&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop"><span>Join the workshop &#8594;</span></a></p><p>Limited spots. Register now with the early bird discount - <strong>100 EUR off with code EARLYBIRD100</strong></p><p>&#8212; Valentina</p>]]></content:encoded></item><item><title><![CDATA[DDD + Clean Architecture: Where to Put Validation Logic]]></title><description><![CDATA[Should validation go in the API, application layer, or domain?]]></description><link>https://journal.optivem.com/p/ddd-clean-architecture-where-to-put-validation-logic</link><guid isPermaLink="false">https://journal.optivem.com/p/ddd-clean-architecture-where-to-put-validation-logic</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 16 Apr 2026 06:00:41 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b4f9ba38-8408-4028-99d3-03025707f349_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><blockquote><p>&#8220;Where should validation go?&#8221;</p></blockquote><p>You&#8217;ve seen:</p><ul><li><p>validation in controllers</p></li><li><p>validation in services</p></li><li><p>validation duplicated in multiple places</p></li></ul><p>And no one is really sure what&#8217;s &#8220;correct&#8221;.</p><p>So let&#8217;s make it concrete.</p><h2>1. Validation at the API layer (input checks)</h2><p>This is the first place data enters your system.</p><p>Here you check things like:</p><ul><li><p>required fields exist</p></li><li><p>types are correct</p></li><li><p>format is valid (email, date, UUID, etc.)</p></li></ul><p>Example:</p><ul><li><p><code>email</code> is missing &#8594; reject request</p></li><li><p><code>age</code> is a string &#8594; reject request</p></li><li><p><code>orderDate</code> is <code>"not-a-date"</code> &#8594; reject request</p></li></ul><p>The API layer&#8217;s job is to <strong>fail fast with clear errors</strong> for malformed input, so the application and domain layers can work with clean, well-typed data.</p><h2>2. Validation in the Application Layer (policies - use case rules)</h2><p>This is where most real mistakes happen.</p><p>This layer handles rules like:</p><ul><li><p>cannot create shipment if stock is insufficient</p></li><li><p>order cannot be placed if cart is empty</p></li><li><p>cannot refund payment if transaction is already settled beyond refund window</p></li><li><p>cannot process payment if currency is not supported for merchant</p></li><li><p>cannot cancel order after it has been shipped</p></li><li><p>cannot apply discount code if it is expired or not eligible</p></li></ul><p>These are not input checks anymore.</p><p>They are <strong>policies</strong>. A policy is a rule about whether an action is allowed right now, given the current state of the world.</p><p>Every one of these needs <strong>something outside the aggregate itself</strong> to evaluate &#8212; inventory, the clock, a merchant config, a discount catalog. In DDD terms, they cross the aggregate boundary.</p><p>That&#8217;s why they don&#8217;t belong in the domain object. An <code>Order</code> alone doesn&#8217;t know whether stock exists. The application layer orchestrates: it fetches what&#8217;s needed, evaluates the policy, and either proceeds or rejects.</p><p>Example flow:</p><ul><li><p>API sends a valid, well-formed request</p></li><li><p>application checks the relevant policies</p></li><li><p>if a policy fails &#8594; reject here, before touching the domain</p></li></ul><p>This is where you stop things that are technically valid, but not allowed in this situation.</p><div><hr></div><p>&#9889; <strong>Register now</strong>: <a href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop">ATDD &#8211; Acceptance Testing Workshop</a><br>Get 100 EUR off with code <strong>EARLYBIRD100</strong></p><div><hr></div><h2>3. Validation in the Domain Layer (invariants)</h2><p>This is the strictest level.</p><p>These are rules that must <em>never</em> be broken, no matter where the code is called from.</p><p>An invariant is a rule that must <strong>always</strong> hold for a given object, no matter who calls it, from where, at what time.</p><p>Examples:</p><ul><li><p>order must always have at least one line item</p></li><li><p>order total cannot be negative</p></li><li><p>payment cannot be &#8220;SUCCESSFUL&#8221; without a transaction reference</p></li><li><p>reserved quantity cannot exceed available stock</p></li><li><p>order status must follow valid transitions: <code>CREATED &#8594; PAID &#8594; SHIPPED &#8594; DELIVERED</code></p></li><li><p>order cannot have <code>SHIPPED</code> status without a tracking number</p></li></ul><p>If this is broken, your system is already inconsistent.</p><p>So this validation:</p><ul><li><p>is enforced when creating or modifying core objects</p></li><li><p>is tied directly to the business model itself</p></li></ul><p><strong>The key principle: the domain never trusts its callers.</strong></p><p>Even if the API already checked something, the domain re-checks any invariant. Why? Because the domain can be invoked from anywhere &#8212; a job, an event, a test, another bounded context. It cannot assume a well-behaved API sits in front of it.</p><h2>What you should NOT do</h2><p>This is where most real-world code goes wrong:</p>
      <p>
          <a href="https://journal.optivem.com/p/ddd-clean-architecture-where-to-put-validation-logic">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Hexagonal Architecture: Ports Are NOT Just Interfaces]]></title><description><![CDATA[Code Example]]></description><link>https://journal.optivem.com/p/hexagonal-architecture-ports-are</link><guid isPermaLink="false">https://journal.optivem.com/p/hexagonal-architecture-ports-are</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 09 Apr 2026 06:02:31 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/0006a123-2c1a-41fd-9df9-a85f7e798fc5_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>When developers hear &#8220;port,&#8221; most think interface.</p><p>Add an interface, call it a port, check the box.</p><p>But here&#8217;s the trap: <strong>just adding interfaces doesn&#8217;t make your domain independent.<br></strong>Abstraction alone doesn&#8217;t automatically enforce architecture.</p><p>If your dependencies point the wrong way, your domain depends on the database, API, or external services &#8212; instead of defining what the system should do.</p><p>In Hexagonal Architecture, the question isn&#8217;t just &#8220;do I have a port?&#8221;</p><p>It&#8217;s: <strong>who does the domain call, and who implements it?</strong></p><p>Do your dependencies point the right way?</p><div><hr></div><p>&#128640; <strong>Register now</strong>: <a href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop">Acceptance Testing Workshop</a><br>Get 100 EUR off with code <strong>EARLYBIRD100</strong></p><div><hr></div><h2>&#10060; Domain depends on Infrastructure</h2><ul><li><p>Domain directly uses infrastructure classes like <code>JpaOrderRepository</code> or <code>PaymentApiClient</code></p></li><li><p>Business logic becomes tied to storage or APIs</p></li><li><p>Changing the database or API can break domain code</p></li></ul><h2><strong>&#9989;</strong> Domain defines the contract, infrastructure implements the contract</h2><ul><li><p>Domain defines driven ports &#8212; interfaces that describe what it needs done</p></li><li><p>Driven adapters implement the details</p></li><li><p>Domain logic is stable, infrastructure can change without affecting it</p></li></ul><p>Driven ports exist to control this dependency direction.</p><p>Abstraction alone doesn&#8217;t enforce architecture &#8212; direction does.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IiW-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd7fc230-cf10-4ca2-8a38-afe964b0307f_1025x1264.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IiW-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd7fc230-cf10-4ca2-8a38-afe964b0307f_1025x1264.png 424w, https://substackcdn.com/image/fetch/$s_!IiW-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd7fc230-cf10-4ca2-8a38-afe964b0307f_1025x1264.png 848w, https://substackcdn.com/image/fetch/$s_!IiW-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd7fc230-cf10-4ca2-8a38-afe964b0307f_1025x1264.png 1272w, https://substackcdn.com/image/fetch/$s_!IiW-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd7fc230-cf10-4ca2-8a38-afe964b0307f_1025x1264.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IiW-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd7fc230-cf10-4ca2-8a38-afe964b0307f_1025x1264.png" width="1025" height="1264" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cd7fc230-cf10-4ca2-8a38-afe964b0307f_1025x1264.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1264,&quot;width&quot;:1025,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:143675,&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/193263907?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd7fc230-cf10-4ca2-8a38-afe964b0307f_1025x1264.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_!IiW-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd7fc230-cf10-4ca2-8a38-afe964b0307f_1025x1264.png 424w, https://substackcdn.com/image/fetch/$s_!IiW-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd7fc230-cf10-4ca2-8a38-afe964b0307f_1025x1264.png 848w, https://substackcdn.com/image/fetch/$s_!IiW-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd7fc230-cf10-4ca2-8a38-afe964b0307f_1025x1264.png 1272w, https://substackcdn.com/image/fetch/$s_!IiW-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd7fc230-cf10-4ca2-8a38-afe964b0307f_1025x1264.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h1>Real-life Example: Ordering System</h1><p>Applying Hexagonal Architecture on the backend (for an e-commerce system).</p>
      <p>
          <a href="https://journal.optivem.com/p/hexagonal-architecture-ports-are">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[TDD: If Your Test Never Fails, It’s Broken]]></title><description><![CDATA[The RED step]]></description><link>https://journal.optivem.com/p/tdd-if-your-test-never-fails-its-broken</link><guid isPermaLink="false">https://journal.optivem.com/p/tdd-if-your-test-never-fails-its-broken</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Tue, 07 Apr 2026 06:02:18 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/ec518127-dfa2-4e37-993b-d671b8e93624_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>Now you&#8217;ve written a test.</p><p>But does it fail?</p><p>If it doesn&#8217;t, you have no proof that your implementation makes a difference.</p><p>A test that always passes is worse than no test.</p><p>It gives you a false sense of security.<br>It hides bugs instead of exposing them.</p><p>That&#8217;s why the <strong>RED step matters</strong>.</p><p>You watch it fail.</p><p>That failure is evidence.</p><p>Later, when it turns GREEN, you know the code caused it &#8212; not luck, not a misconfigured test, not a false positive.</p><h2>&#9889;Can the Test Actually Fail?</h2><p>If you&#8217;ve seen it fail:</p><ul><li><p>It confirms the test is valid.</p></li><li><p>It means that the later GREEN step actually proves your code works.</p></li><li><p>It protects you from false positives &#8212; those sneaky tests that always pass, giving you a fake sense of correctness.</p></li></ul><p>TDD makes it unavoidable.</p><p>No RED? No trust in your code.</p><h2>Why Developers Skip It (And Regret It)</h2><p>It&#8217;s tempting to skip RED:</p><ul><li><p>&#8220;I know this code will work, so why watch it fail?&#8221;</p></li><li><p>&#8220;It&#8217;s faster to just implement and see GREEN.&#8221;</p></li></ul><p>But skipping RED is like installing a fire alarm that never goes off &#8212; you won&#8217;t know something&#8217;s broken until it&#8217;s too late.</p><p>Watching the test fail is the <strong>cheapest, fastest bug prevention you can get</strong>.</p><h2>RED Before GREEN</h2><p>TDD isn&#8217;t just about writing tests.</p><p>It&#8217;s about <strong>proof</strong>.</p><p>Proof that your test matters.<br>Proof that your code works.<br>Proof that the system behaves as expected.</p><p>You can take a step further with Acceptance Test&#8211;Driven Development (ATDD).</p><p>Instead of just asking:</p><blockquote><p>&#8220;Does my code work?&#8221;</p></blockquote><p>ATDD asks:</p><blockquote><p>&#8220;Does the system behave as the user expects?&#8221;</p></blockquote><p>It&#8217;s less about writing more tests and more about making sure everyone &#8212; Devs, QA, and Product Owners &#8212; agrees on what &#8220;done&#8221; really means.</p><p>The cool part? Fewer arguments about whether something is broken, fewer regression bugs, and a lot less late-night firefighting &#8212; because expectations are clear <strong>before you write a single line of code</strong>.</p><div><hr></div><p>&#128640; <strong>Join me for: <a href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop">ATDD &#8211; Acceptance Testing Workshop</a></strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop&quot;,&quot;text&quot;:&quot;Join the workshop &#8594;&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop"><span>Join the workshop &#8594;</span></a></p><p>Limited spots. Get <strong>100 EUR off with code EARLYBIRD100</strong></p><p></p>]]></content:encoded></item><item><title><![CDATA[Clean Architecture: DTOs Are NOT Just Wrappers]]></title><description><![CDATA[Code Example]]></description><link>https://journal.optivem.com/p/clean-architecture-dtos-are-not-just-wrappers</link><guid isPermaLink="false">https://journal.optivem.com/p/clean-architecture-dtos-are-not-just-wrappers</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 03 Apr 2026 06:01:42 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/983a1287-3fd1-45a2-8765-55c7ffe65c66_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>Developers like DTOs. And wrappers. And more wrappers.</p><p>Until your &#8220;simple&#8221; API layer turns into <strong>a tower of classes no one can read</strong>, tests become painful, and adding a new field feels like a chore.</p><p>DTOs and mappers are tools &#8212; not dogma. Clean Architecture doesn&#8217;t say &#8220;wrap everything 5 times.&#8221; It says: be intentional about what crosses boundaries.</p><h2>The Problem</h2><p>Many codebases follow a pattern like this:</p><pre><code><code>Domain objects
   &#8595;
Domain DTO
   &#8595;
Application DTO
   &#8595;
API DTO
   &#8595;
JSON</code></code></pre><p>That&#8217;s 3&#8211;4 layers of wrapping <strong>for the same data</strong>, plus mappers everywhere.</p><p>To be clear &#8212; sometimes multiple layers <em>are</em> justified. If you're serving both GraphQL and REST from the same domain, or maintaining a published API contract that must change independently from your domain, extra DTOs earn their keep. The problem isn't layers &#8212; it's layers that exist "just in case."</p><p><strong>The result:</strong></p><ul><li><p>Lots of boilerplate</p></li><li><p>Hard to understand what actually changes the domain</p></li><li><p>Fragile tests because every layer must be mocked</p></li><li><p>Accidental complexity: devs can&#8217;t tell which layer matters for business rules</p></li></ul><div><hr></div><p>&#128640; <strong>Register now</strong>: <a href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop">Acceptance Testing Workshop</a><br>Get 100 EUR off with code <strong>EARLYBIRD100</strong></p><div><hr></div><h1>&#128161;Code Example</h1><h2>Over-Wrapping Everything &#10060;</h2>
      <p>
          <a href="https://journal.optivem.com/p/clean-architecture-dtos-are-not-just-wrappers">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Clean Code Is NOT Optional]]></title><description><![CDATA[It&#8217;s tempting to write code fast...]]></description><link>https://journal.optivem.com/p/clean-code-is-not-optional</link><guid isPermaLink="false">https://journal.optivem.com/p/clean-code-is-not-optional</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Wed, 01 Apr 2026 06:02:52 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/e5f34d3e-bd39-4e7d-ad18-2cd0c5357885_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>It&#8217;s tempting to write code fast and plan to clean it up later.</p><p>Skip the naming.<br>Duplicate a bit of logic.<br>Add &#8220;just one more if&#8221;.</p><h2>Messy Code Is a Tax</h2><p>Every change on top of messy code costs more:</p><ul><li><p><strong>Features take longer</strong> because you can&#8217;t trust what&#8217;s already there. You read more than you write, trying to figure out what the code actually does.</p></li><li><p><strong>Bugs hide</strong> in unclear abstractions and duplicated logic. You fix something in one place and miss the copy-pasted version three files over.</p></li><li><p><strong>Refactoring is painful</strong> because dependencies are tangled. You pull one thread and everything unravels.</p></li></ul><p>So you slow down. Every change gets harder. Every decision gets riskier. Every task takes longer than it should.</p><p>And the worst part? You <em>know</em> you should fix it, but now you can&#8217;t justify the time &#8212; because there&#8217;s a backlog of features waiting.</p><p>This is how codebases rot. Not from one bad decision, but from hundreds of small ones that compound.</p><blockquote><p>No matter how slow you are writing clean code, you will always be slower if you make a mess. &#8212; Uncle Bob</p></blockquote><p>Clean code may feel slower at first. But every line you write on top of it is faster, safer, and less stressful.</p><p>The hard truth: Messy code always costs more time than taking the time to do it right.</p><h2>But How Do You Keep Code Clean &#8212; and Keep It Working?</h2><p>Say you&#8217;ve written clean, well-structured code. You refactor a service, rename some methods, extract a class. The code reads well. You&#8217;re confident it&#8217;s correct.</p><p>But is it?</p><p>How do you know that the order flow still works end to end? That the payment integration didn&#8217;t break? That a customer can still check out?</p><p>You don&#8217;t &#8212; unless you have tests that verify <em>behavior</em>, not implementation.</p><h2>Clean Code Needs a Safety Net</h2><p>Unit tests are fast. They give you quick feedback and make refactoring feel safe.</p><p>But even the best unit tests have a blind spot.</p><p>They verify pieces in isolation. They mock the database, stub the API, skip the serialization. So when everything passes, you know your <em>unit</em> <em>logic</em> works &#8212; but you don&#8217;t know if the <em>system logic</em> works.</p><p>The gaps live at the boundaries: the database query that returns data in a different shape than you assumed. The HTTP layer that silently drops a field. The integration that worked in isolation but breaks when it meets real I/O.</p><p>Unit tests can&#8217;t catch these problems &#8212; not because they&#8217;re poorly written, but because that&#8217;s not what they&#8217;re designed to do.</p><p>Acceptance tests fill that gap. They exercise the system end-to-end, through the same boundaries your users hit:</p><p>A <strong>unit test</strong> says: &#8220;This function calculates the discount correctly.&#8221;</p><p>An <strong>acceptance test</strong> says: &#8220;When a customer applies a coupon at checkout, the total price is reduced and the order goes through.&#8221;</p><p>They&#8217;re slower than unit tests. You won&#8217;t run them on every keystroke. But they answer the question that unit tests can&#8217;t:</p><p><strong>Does this actually work for the user?</strong></p><h2>Clean Code Without Acceptance Tests Is a Risk</h2><p>Clean code keeps you moving fast. Unit tests ensure your refactoring doesn&#8217;t break the unit's logic. So why do you need anything else?</p><p>Because unit tests verify your code works <em>in isolation</em>. Acceptance tests verify your system works <em>in reality</em>.</p><p>You can have clean code, a full suite of passing unit tests, and still deploy a broken checkout flow &#8212; because the bug lives at a boundary that no unit test touches.</p><ul><li><p><strong>Clean code</strong> lets you move fast.</p></li><li><p><strong>Unit tests</strong> make sure the pieces work.</p></li><li><p><strong>Acceptance tests</strong> make sure the system works.</p></li></ul><p>One without the other will eventually slow you down.</p><p>You need all three.</p><h2>Where do you start?</h2><p>If you&#8217;re working in a greenfield project, you can build all three from day one.</p><p>But what about legacy code &#8212; where there are no tests, and the code is too messy to unit test?</p><p>You don&#8217;t start with unit tests. You can&#8217;t &#8212; the code isn&#8217;t structured for them yet.</p><p>You start with acceptance tests.</p><p>They don&#8217;t need clean code. They don&#8217;t need dependency injection or isolated modules. They test the system from the outside, through the same entry points your users hit. That means you can add them to any codebase, no matter how messy.</p><p>Once acceptance tests are in place, you have a safety net. Now you can refactor toward clean code and add unit tests &#8212; without breaking what already works.</p><p>Ready to put this into practice?</p><p>I&#8217;m running a hands-on <strong><a href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop">Acceptance Testing Workshop</a></strong> on May 27&#8211;28 (4 hours).</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop&quot;,&quot;text&quot;:&quot;Join the workshop &#8594;&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop"><span>Join the workshop &#8594;</span></a></p><p>Limited spots. Register now with the early bird discount - <strong>100 EUR off with code EARLYBIRD100</strong></p>]]></content:encoded></item><item><title><![CDATA[TDD & ATDD]]></title><description><![CDATA[In Microservice Architecture]]></description><link>https://journal.optivem.com/p/tdd-and-atdd-in-microservice-architecture</link><guid isPermaLink="false">https://journal.optivem.com/p/tdd-and-atdd-in-microservice-architecture</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 27 Mar 2026 07:02:39 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/5beed640-9a0c-4081-b7db-37b6bcb7f5d7_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>When I first got into TDD, I thought I had it figured out.</p><p>I was reading Kent Beck, Martin Fowler&#8230; all of it just made sense.</p><p>I was really into clean code and unit tests.</p><p>I knew about e2e testing, but I saw it as QA&#8217;s job, not mine. Those tests were really badly written and constantly breaking. And to be honest, I saw it as boring and something that developers shouldn&#8217;t be doing.</p><p>I saw the whole CI/CD pipeline as a separate thing done by a DevOps engineer &#8212; not me.</p><div><hr></div><p>Still, no matter how many unit tests were written, management kept on complaining:</p><blockquote><p>&#8220;Why are there still so many bugs?&#8221;</p><p>&#8220;Why is everything taking so long?&#8221;</p><p>&#8220;I thought the last release fixed this!&#8221;</p><p>&#8220;Customers are frustrated &#8212; something has to change!&#8221;</p></blockquote><div><hr></div><p><strong>&#9889;Want to skip the pain and go straight to the solution?</strong></p><p><strong>Every Release Is a Nightmare</strong> doesn&#8217;t have to be your reality.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop&quot;,&quot;text&quot;:&quot;Join the workshop &#8594;&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop"><span>Join the workshop &#8594;</span></a></p><p>Limited spots. Register now with the early bird discount - <strong>100 EUR off with code EARLYBIRD100</strong></p><div><hr></div><h2>A Story That Stuck With Me</h2><p>Later, I stumbled across something Dave Farley shared. The teams were practicing TDD&#8230; they were lots of unit tests, all green &#8212; but the system was failing in QA environment, for possibly weeks. QA engineers constantly had to play catch-up with their e2e tests.</p><p>And that&#8217;s what got Dave Farley to develop acceptance tests.</p><div><hr></div><p>I realized I had to see the bigger picture.</p><p>That meant looking at the whole continuous delivery pipeline and understanding how all the pieces fit together.</p><p>In the commit stage, we&#8217;re doing TDD and writing unit tests (and component tests)&#8212; making sure each component behaves the way we expect.</p><p>But further down the pipeline, there&#8217;s the acceptance stage, where ATDD comes in &#8212; checking that all those pieces actually work together as a system.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!u0K5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3036e5-1075-443d-a679-551a9bf49e30_900x1110.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!u0K5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3036e5-1075-443d-a679-551a9bf49e30_900x1110.png 424w, https://substackcdn.com/image/fetch/$s_!u0K5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3036e5-1075-443d-a679-551a9bf49e30_900x1110.png 848w, https://substackcdn.com/image/fetch/$s_!u0K5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3036e5-1075-443d-a679-551a9bf49e30_900x1110.png 1272w, https://substackcdn.com/image/fetch/$s_!u0K5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3036e5-1075-443d-a679-551a9bf49e30_900x1110.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!u0K5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3036e5-1075-443d-a679-551a9bf49e30_900x1110.png" width="900" height="1110" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ea3036e5-1075-443d-a679-551a9bf49e30_900x1110.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1110,&quot;width&quot;:900,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:120470,&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/191875686?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3036e5-1075-443d-a679-551a9bf49e30_900x1110.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_!u0K5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3036e5-1075-443d-a679-551a9bf49e30_900x1110.png 424w, https://substackcdn.com/image/fetch/$s_!u0K5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3036e5-1075-443d-a679-551a9bf49e30_900x1110.png 848w, https://substackcdn.com/image/fetch/$s_!u0K5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3036e5-1075-443d-a679-551a9bf49e30_900x1110.png 1272w, https://substackcdn.com/image/fetch/$s_!u0K5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3036e5-1075-443d-a679-551a9bf49e30_900x1110.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h1>&#9889;TDD &amp; ATDD in Microservice Architecture</h1><h2>ATDD: Seeing the System as a Whole</h2><p>If you zoom out and look at the system&#8230; it starts with a user story.</p><p>And that story comes with <strong>acceptance criteria</strong> &#8212; the conditions that tell you: <em>did we actually deliver what the user needs?</em></p><p>Instead of just reading those criteria&#8230; we can turn each one into an <strong>acceptance test</strong>. It&#8217;s basically like 1:1 mapping.</p><p>And these tests are very different from unit tests.</p>
      <p>
          <a href="https://journal.optivem.com/p/tdd-and-atdd-in-microservice-architecture">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[ATDD – How Do You Actually Start?]]></title><description><![CDATA[Write acceptance tests that catch real bugs]]></description><link>https://journal.optivem.com/p/atdd-how-do-you-actually-start</link><guid isPermaLink="false">https://journal.optivem.com/p/atdd-how-do-you-actually-start</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Tue, 24 Mar 2026 07:02:56 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/e3f1f2e8-7c96-4355-9d4f-fc834f4a447e_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>Every time I write about acceptance testing, someone asks:</p><blockquote><p>This makes sense. But how do I actually start?</p></blockquote><p>I&#8217;ve gotten this in DMs. In comments. In conversations with Tech Leads.</p><p>They understand <em>why</em> acceptance tests matter.<br>They&#8217;ve felt the pain of shipping bugs.<br>They&#8217;ve read about ATDD.</p><p>But when they sit down to actually write one?</p><p>They stare at a blank file and think:</p><ul><li><p>Where do you put the acceptance tests?</p></li><li><p>How do you structure them so they don&#8217;t become as fragile as the E2E tests you&#8217;re trying to replace?</p></li><li><p>How do you design them so your whole team can write and maintain them &#8212; not just the one person who &#8220;gets testing&#8221;?</p></li></ul><div><hr></div><p>You want tests that are:</p><p><strong>Clean. Readable. Runs in seconds. Survives refactors.</strong></p><p>But&#8230;</p><ul><li><p>How do you build the DSL that makes it work?</p></li><li><p>How do you wire up the Drivers that connect these tests to your actual system?</p></li><li><p>How do you structure all of this so it scales across your whole team?</p></li></ul><div><hr></div><h2>Hands-On Acceptance Testing</h2><p>That&#8217;s why I built a live, hands-on workshop.</p><p>This isn&#8217;t a course you watch alone at 2x speed and forget by Friday.</p><p><strong>What you&#8217;ll learn:</strong></p><p><strong>1. The architecture.</strong> How acceptance tests are structured &#8212; DSL, Drivers, the layers that make them maintainable. You&#8217;ll understand <em>why</em> some acceptance tests become unmaintainable spaghetti and yours won&#8217;t.</p><p><strong>2. ATDD Cycle with AI.</strong> We&#8217;ll design an acceptance test together &#8212; from a business scenario all the way through to verification. You&#8217;ll get to see the layers (DSL &amp; Drivers) in practice.</p><p><strong>3. Apply it with your team.</strong> How to introduce acceptance testing into an existing project without stopping everything. How to get buy-in. How to start.<br></p><p><strong>When:</strong> May 25&#8211;26, 2026 | 2-4 PM CET <br><em>(FYI: May 27&#8211;28 </em>| <em>5-7 PM CET &#8212; Fully Booked)</em><br><strong>Where:</strong> Live on Zoom<br><strong>Duration:</strong> 4 hours (2 sessions x 2 hours)</p><p>&#128187; <strong>Who it&#8217;s for:</strong> Senior Engineers and Tech Leads who are tired of shipping bugs and ready to do something about it</p><p>&#128640; <strong>Register:</strong> <strong><a href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop">ATDD &#8211; Acceptance Testing Workshop</a><br>Get &#8364;100 off with code</strong> <strong>EARLYBIRD100</strong></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-05-27-acceptance-testing-workshop" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mYju!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb92ebee2-ad87-403e-93e4-709ea70fedaf_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!mYju!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb92ebee2-ad87-403e-93e4-709ea70fedaf_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!mYju!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb92ebee2-ad87-403e-93e4-709ea70fedaf_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!mYju!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb92ebee2-ad87-403e-93e4-709ea70fedaf_1280x720.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mYju!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb92ebee2-ad87-403e-93e4-709ea70fedaf_1280x720.png" width="1280" height="720" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b92ebee2-ad87-403e-93e4-709ea70fedaf_1280x720.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:720,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:185160,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://journal.optivem.com/i/190817885?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb92ebee2-ad87-403e-93e4-709ea70fedaf_1280x720.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_!mYju!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb92ebee2-ad87-403e-93e4-709ea70fedaf_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!mYju!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb92ebee2-ad87-403e-93e4-709ea70fedaf_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!mYju!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb92ebee2-ad87-403e-93e4-709ea70fedaf_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!mYju!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb92ebee2-ad87-403e-93e4-709ea70fedaf_1280x720.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Don&#8217;t spend next month debugging something that should have been caught before it shipped.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop&quot;,&quot;text&quot;:&quot;Join the workshop &#8594;&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop"><span>Join the workshop &#8594;</span></a></p><p>Limited spots. Register now with the early bird discount - <strong>&#8364;100 off with code EARLYBIRD100</strong></p>]]></content:encoded></item><item><title><![CDATA[The Architecture Behind Acceptance Tests That Don’t Break]]></title><description><![CDATA[How four layers turn fragile tests into executable specifications]]></description><link>https://journal.optivem.com/p/the-architecture-behind-acceptance-tests</link><guid isPermaLink="false">https://journal.optivem.com/p/the-architecture-behind-acceptance-tests</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 19 Mar 2026 07:02:47 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b6fda881-9471-4375-84a3-c79b75928fc9_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>This test doesn&#8217;t lie:</p><pre><code><code>scenario
    .given().product().withUnitPrice(20.00)
    .and().country().withCode("US").withTaxRate(0.08)
    .when().placeOrder().withQuantity(5).withCountry("US")
    .then().shouldSucceed()
    .and().order()
        .hasBasePrice(100.00)
        .hasTaxAmount(8.00)
        .hasTotalPrice(108.00);</code></code></pre><p>Clean. Readable. Runs in seconds. Survives refactors.</p><p>But how does it actually <em>work</em>?</p><p>What&#8217;s behind <code>.given().product().withUnitPrice(20.00)</code>? How does the same test run against both the API and the UI? Where does the &#8220;magic&#8221; come from?</p><p>I&#8217;m going to take this test apart, layer by layer, and show you exactly how the DSL is built.</p><div><hr></div><p><strong>&#9889;Ready to see this in action?</strong></p><p>I&#8217;m running a live, hands-on workshop where we build acceptance tests using this exact architecture. <strong>Every Release Is a Nightmare</strong> doesn&#8217;t have to be your reality.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop&quot;,&quot;text&quot;:&quot;Join the workshop &#8594;&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop"><span>Join the workshop &#8594;</span></a></p><p>Limited spots. Register now with the early bird discount - <strong>100 EUR off with code EARLYBIRD100</strong></p><div><hr></div><h2>The four layers</h2><p>An acceptance test DSL has four layers. Each layer has one job, and they only talk to the layer directly below them:</p><pre><code><code>Test           &#8594;  what we're testing (the scenario)
DSL            &#8594;  how we express it (the fluent API)
Driver Port    &#8594;  what we need from the system (interfaces)
Driver Adapter &#8594;  how we interact with the system (API calls, browser clicks)</code></code></pre><p>This separation is the entire reason acceptance tests are maintainable. Remove any layer and the whole thing falls apart.</p><div><hr></div><h2>Layer 1: The Test</h2><p>This is what you&#8217;ve already seen. A test method that reads like a business scenario:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;678b799c-be8a-40bc-9c92-5d0c0a1152ed&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">@TestTemplate
@Channel({ChannelType.UI, ChannelType.API})
void shouldCalculateCorrectTotalWithTaxRate() {
    scenario
        .given().product()
            .withUnitPrice(20.00)
        .and().country()
            .withCode("US")
            .withTaxRate(0.08)
        .when().placeOrder()
            .withQuantity(5)
            .withCountry("US")
        .then().shouldSucceed()
        .and().order()
            .hasBasePrice(100.00)
            .hasTaxAmount(8.00)
            .hasTotalPrice(108.00);
}</code></pre></div><p>Notice what&#8217;s <em>not</em> here:</p><ul><li><p>No URLs</p></li><li><p>No HTTP calls</p></li><li><p>No CSS selectors</p></li><li><p>No database queries</p></li><li><p>No setup or teardown</p></li></ul><p>The test only knows <em>what</em> it&#8217;s testing. It has no idea <em>how</em> the system works under the hood. That&#8217;s the point.</p><p>The <code>@Channel</code> annotation is interesting &#8212; it tells the test runner to execute this same test once through the API and once through the UI. Same scenario, same assertions, two completely different ways of talking to the system. Here&#8217;s how it actually works.</p>
      <p>
          <a href="https://journal.optivem.com/p/the-architecture-behind-acceptance-tests">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Unit Tests passed. The bug shipped anyway.]]></title><description><![CDATA[The Pipeline was green. All the unit tests passed. All the E2E Tests passed. Yet the feature was broken.]]></description><link>https://journal.optivem.com/p/unit-tests-passed-the-bug-shipped</link><guid isPermaLink="false">https://journal.optivem.com/p/unit-tests-passed-the-bug-shipped</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Tue, 17 Mar 2026 07:02:09 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/8f43f40f-784c-4318-a654-30f46e384716_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>Marco stared at the Slack message from support.</p><p>&#8220;Customer says they were charged $110 on a $100 order. Tax should have been $8, not $10.&#8221;</p><p>He pulled up the dashboard. All tests green. CI/CD pipeline &#8212; clean. Not a single failure in weeks.</p><p>And yet, the tax calculation was wrong.</p><div><hr></div><p>Marco spent the next hour tracing the bug. The tax calculation itself was correct. The unit test proved it: pass in a rate, get back the right amount. No issues there.</p><p>The problem was simpler than that. And worse.</p><p>The developer who built the order service had hardcoded the tax rate as a constant &#8212; 10% &#8212; while working on the feature. The plan was to pull it from the country configuration later.</p><p>But &#8220;later&#8221; never came.</p><p>The feature shipped, the tests passed, and nobody noticed.</p><p>The unit test for the tax calculation? It tested the math. And the math was right. 10% of $100 is $10. Test passes.</p><p>But the <em>feature</em> was wrong. The customer was in a state with an 8% tax rate, and the system charged 10% because it never actually looked up the rate.</p><p>E2E tests? They had those too. But the E2E test just checked that the confirmation page appeared after placing an order. It never verified the actual total.</p><p><strong>The feature was broken. And no test caught it.</strong></p><div><hr></div><p>Here&#8217;s the uncomfortable truth most teams don&#8217;t talk about:</p><p><strong>Unit tests</strong> verify that individual components work. They can&#8217;t tell you whether the <em>feature</em> works. Marco&#8217;s tax calculation was correct &#8212; it just used the wrong input.</p><p><strong>E2E tests</strong> verify the happy path through the UI. But they&#8217;re slow, fragile, and they often check that <em>something</em> shows up on screen, not that the <em>right thing</em> shows up.</p><p>There&#8217;s a massive gap between &#8220;all my unit tests pass&#8221; and &#8220;this feature actually works as the customer expects.&#8221;</p><p>That gap is where your production bugs live.</p><div><hr></div><h2>There&#8217;s a test type that fills this gap</h2><p>It&#8217;s called an <strong>acceptance test</strong>.</p><p>Not another E2E test. Something fundamentally different.</p><p>An acceptance test is an executable specification of system behavior. It answers one question: <em>does this feature work the way the business expects?</em></p><p>Here&#8217;s what Marco&#8217;s E2E test looked like:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;f33e61f9-46d3-4427-a4d4-80449890cf4c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">@Test
void testPlaceOrder() {
    driver.get(baseUrl + "/products");
    driver.findElement(By.id("product-1")).click();
    driver.findElement(By.id("add-to-cart")).click();
    driver.findElement(By.id("quantity")).sendKeys("5");
    driver.findElement(By.id("checkout")).click();
    driver.findElement(By.id("place-order")).click();

    WebElement confirmation = driver.findElement(By.id("confirmation"));
    assertTrue(confirmation.isDisplayed());
}</code></pre></div><p>Brittle. Tied to the UI. Breaks if someone renames a button. And it only checks if the confirmation page shows up &#8212; it never verified the actual total.</p><p>Here&#8217;s the same scenario as an acceptance test:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;b587750e-fb39-4b9f-b945-4590bcf507a9&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">@Test
void shouldCalculateCorrectTotalWithTaxRate() {
    scenario
        .given().product()
            .withUnitPrice(20.00)
        .and().country()
            .withCode("US")
            .withTaxRate(0.08)
        .when().placeOrder()
            .withQuantity(5)
            .withCountry("US")
        .then().shouldSucceed()
        .and().order()
            .hasBasePrice(100.00)
            .hasTaxAmount(8.00)
            .hasTotalPrice(108.00);
}</code></pre></div><p>No browser. No CSS selectors. No flaky waits.</p><p>Just a clear business scenario: <em>given a product at $20, a quantity of 5, and a country with 8% tax &#8212; the base price should be $100, the tax should be $8, and the total should be $108.</em></p><p>This test would have caught Marco&#8217;s bug immediately. The system would return a total of $110 instead of $108 &#8212; test fails. The hardcoded constant would have been found before it ever reached a customer.</p><p>Marco&#8217;s team? After they introduced acceptance tests, customer-reported bugs dropped dramatically in the first few months. Not because they wrote more tests &#8212; but because they wrote the <em>right</em> tests.</p><div><hr></div><h2>Want to learn how to write tests like this?</h2><p>I&#8217;m running a live workshop: <strong><a href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop">Stop Shipping Bugs: Acceptance Tests Workshop</a></strong>.</p><p>4 hours. Two evenings. Live on Zoom, with me.</p><p>&#9889;I&#8217;ll walk you through the full architecture &#8212; DSL, Drivers, how it all fits together &#8212; and by the end, you&#8217;ll have written a real acceptance test from scenario to assertion.</p><p>&#128197; May 27-28, 5:00-7:00 PM CET</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop&quot;,&quot;text&quot;:&quot;Join the workshop &#8594;&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop"><span>Join the workshop &#8594;</span></a></p><p>Limited spots. Register now with the early bird discount - <strong>100 EUR off with code EARLYBIRD100</strong></p><p>&#8212; Valentina</p>]]></content:encoded></item><item><title><![CDATA[SOLID: Stop Overloading Your Service Classes]]></title><description><![CDATA[Single Responsibility Principle (SRP)]]></description><link>https://journal.optivem.com/p/solid-stop-overloading-your-service-classes</link><guid isPermaLink="false">https://journal.optivem.com/p/solid-stop-overloading-your-service-classes</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 13 Mar 2026 07:02:11 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/fa4244c3-4f7c-45d2-a26c-923a9ca65cca_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>Ever opened a class and thought, &#8216;What does this thing even do?&#8217;</p><p>It starts small, but over time&#8230; </p><h2>&#10060;One Class Tries to Do It All</h2><p><code>OrderService</code> becomes <strong>the place where everything goes</strong>.</p><p>Need to send an email? Add it to <code>OrderService</code>.<br>Need to generate an invoice? Add it to <code>OrderService</code>.<br>Need to call the shipping API? Add it to <code>OrderService</code>.</p><p>Suddenly, the class looks like this:</p><pre><code><code>OrderService

// Own reposponsibilities
- createOrder()
- cancelOrder()
- getOrderById()

// Extra responsibilities
- generateInvoice()
- shipOrder()
- sendEmail()
- trackOrder()

.... a laundry list of responsibilities...</code></code></pre><p>At first, it feels convenient.</p><p>Each method is small.<br>Each feature made sense when it was added.</p><p>But over time, this class becomes large, fragile, and difficult to change&#8230;</p><p>Because it&#8217;s responsible for too many different things:</p><ul><li><p>order logic</p></li><li><p>invoice generation</p></li><li><p>shipping logic</p></li><li><p>email notifications</p></li></ul><p>One class does <strong>all of it</strong>.</p><div><hr></div><p><strong>&#128161;Want to make it safe to redesign your architecture without breaking everything?</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop&quot;,&quot;text&quot;:&quot;Join the workshop &#8594;&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop"><span>Join the workshop &#8594;</span></a></p><p>Limited spots. Early bird discount - <strong>100 EUR off with code EARLYBIRD100</strong></p><div><hr></div><h2>&#9989;One Class, One Job</h2><p>The Single Responsibility Principle (SRP) prevents this from happening.</p><p>A class should have <strong>one reason to change</strong>.</p><p>Not five. Not ten. One.</p><p>Instead of putting everything into <code>OrderService</code>, split responsibilities:</p><pre><code>OrderService
- createOrder()
- cancelOrder()
- getOrderById()

InvoiceService
- generateInvoice()

EmailService
- sendEmail()

ShippingService
- shipOrder()
- trackOrder()</code></pre><p>Now each class changes for <strong>one reason</strong>.</p><ul><li><p>Order logic changes &#8594; <code>OrderService</code></p></li><li><p>Invoice logic changes &#8594; <code>InvoiceService</code></p></li><li><p>Email logic changes &#8594; <code>EmailService</code></p></li><li><p>Shipping logic changes &#8594; <code>ShippingService</code></p></li></ul><p></p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jQpH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c8abd5-2c5b-4191-b7e0-6a8ace4866d0_1025x1233.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jQpH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c8abd5-2c5b-4191-b7e0-6a8ace4866d0_1025x1233.png 424w, https://substackcdn.com/image/fetch/$s_!jQpH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c8abd5-2c5b-4191-b7e0-6a8ace4866d0_1025x1233.png 848w, https://substackcdn.com/image/fetch/$s_!jQpH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c8abd5-2c5b-4191-b7e0-6a8ace4866d0_1025x1233.png 1272w, https://substackcdn.com/image/fetch/$s_!jQpH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c8abd5-2c5b-4191-b7e0-6a8ace4866d0_1025x1233.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jQpH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c8abd5-2c5b-4191-b7e0-6a8ace4866d0_1025x1233.png" width="604" height="726.5678048780488" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b9c8abd5-2c5b-4191-b7e0-6a8ace4866d0_1025x1233.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1233,&quot;width&quot;:1025,&quot;resizeWidth&quot;:604,&quot;bytes&quot;:132942,&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/185057081?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c8abd5-2c5b-4191-b7e0-6a8ace4866d0_1025x1233.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_!jQpH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c8abd5-2c5b-4191-b7e0-6a8ace4866d0_1025x1233.png 424w, https://substackcdn.com/image/fetch/$s_!jQpH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c8abd5-2c5b-4191-b7e0-6a8ace4866d0_1025x1233.png 848w, https://substackcdn.com/image/fetch/$s_!jQpH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c8abd5-2c5b-4191-b7e0-6a8ace4866d0_1025x1233.png 1272w, https://substackcdn.com/image/fetch/$s_!jQpH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c8abd5-2c5b-4191-b7e0-6a8ace4866d0_1025x1233.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>&#128161;SRP Isn&#8217;t About One Method or Tiny Classes</h2>
      <p>
          <a href="https://journal.optivem.com/p/solid-stop-overloading-your-service-classes">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[“If you think good architecture is expensive, try bad architecture.”]]></title><description><![CDATA[(&#8212;Brian Foote and Joseph Yoder)]]></description><link>https://journal.optivem.com/p/if-you-think-good-architecture-is-expensive</link><guid isPermaLink="false">https://journal.optivem.com/p/if-you-think-good-architecture-is-expensive</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Tue, 10 Mar 2026 07:02:39 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/50e5a484-3d18-4de0-b1ac-32a10f4142f3_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><h2>Bad Architecture Is Expensive</h2><p>The <strong>&#8220;shortcut&#8221;</strong> decisions another developer made six months ago are now your daily headache.</p><p>Meetings start to revolve around workarounds. Every new feature is debated: &#8220;Is it safe? Will it break something else?&#8221;</p><p>And the pressure builds quietly.</p><p>Bad architecture doesn&#8217;t scream. It doesn&#8217;t send warning emails. It grows in the background.</p><p>It spreads through the system like rot.</p><p>Every service is <strong>tightly coupled</strong>. Every module is <strong>dependent on another</strong>. Every <strong>boundary unclear</strong>.</p><blockquote><p>&#8220;If you think good architecture is expensive, try bad architecture.&#8221;<br>&#8212; Brian Foote &amp; Joseph Yoder</p></blockquote><h2>So You Decide to Fix It</h2><p>You&#8217;ve had enough. You start redesigning. You decouple the modules. You clean up the boundaries. You refactor the mess left behind by someone else.</p><p>And then everything breaks.</p><p>Regression bugs everywhere. Features that worked yesterday are failing today. QA is overwhelmed. Stakeholders are asking what happened. Your manager wants answers.</p><p>And suddenly, you&#8217;re the problem. The person who tried to improve the system is now the one everyone blames for breaking it.</p><h2>What Went Wrong?</h2><p>It wasn&#8217;t the refactoring. It was what you didn&#8217;t have before you started.</p><p>Half your unit tests broke &#8212; not because the behavior changed, but because they were coupled to the old structure. The other half still passed, but it didn&#8217;t matter. Bugs were slipping through anyway, in the gaps between units that no test ever covered.</p><p>Your unit tests couldn&#8217;t tell you whether the system still worked. They were never designed to. Because <strong>unit tests</strong> only tell you if <strong>individual units of behavior work</strong>. They <strong>do</strong> <strong>NOT</strong> tell you if the <strong>system as a whole works</strong>.</p><p>Now QA has to catch everything. You push a change, they find a bug, you fix it, they test again&#8212;a loop that never ends. It&#8217;s slow, it&#8217;s expensive, it&#8217;s frustrating&#8230; and code still ships with bugs.</p><h2>Your System&#8217;s Real Safety Net</h2><p>This is exactly where acceptance tests change the game.</p><p>Acceptance tests don&#8217;t care about internal structure. They test what the system does from the outside &#8212; the behavior your users and stakeholders actually depend on.</p><ul><li><p>Redesign your modules? Acceptance tests should still pass.</p></li><li><p>Split a monolith into microservices? Acceptance tests should still pass.</p></li><li><p>Switch from SQL to NoSQL? Acceptance tests should still pass.</p></li></ul><p>In any System Redesign, Acceptance Tests should still pass, because we expect the system's black-box behavior NOT to change.</p><p>They&#8217;re the one safety net that survives architectural change.</p><h2>The Right Sequence</h2><p>Bad architecture and missing acceptance tests are an invisible tax. And the people paying for it are the ones writing, testing, and maintaining the code every day.</p><p>The fix isn&#8217;t to stop improving your architecture. It&#8217;s to change the order:</p><ol><li><p>First, write acceptance tests around what the system does today. Lock in the behavior. </p></li><li><p>Then redesign the architecture, knowing that if something breaks, you&#8217;ll see it immediately &#8212; not three sprints later in production.</p></li></ol><p>Acceptance tests first. Architecture second. That&#8217;s the sequence that actually works.</p><h2>See It in Practice</h2><p>Want to see how acceptance tests can save you time, frustration, and endless bug loops? I&#8217;m running a hands-on <strong><a href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop">Acceptance Tests Workshop</a></strong> on May 27&#8211;28 (4 hours). </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop&quot;,&quot;text&quot;:&quot;Join the workshop &#8594;&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop"><span>Join the workshop &#8594;</span></a></p><p>Limited spots. Register now with the early bird discount - <strong>&#8364;100 off with code EARLYBIRD100</strong></p>]]></content:encoded></item><item><title><![CDATA[Stop Writing Fragile Gherkin]]></title><description><![CDATA[The Gherkin maintenance nightmare and how to solve it]]></description><link>https://journal.optivem.com/p/stop-writing-fragile-gherkin</link><guid isPermaLink="false">https://journal.optivem.com/p/stop-writing-fragile-gherkin</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 06 Mar 2026 07:02:50 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/04d4e881-6a81-4643-b30e-770a52a748bf_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>If you&#8217;ve ever written acceptance tests in plain-text Gherkin, you probably had the same thought I did:</p><p>&#8220;This looks clean&#8230; so why is it so painful to maintain?&#8221;</p><p>Gherkin was supposed to let product owners write specifications.</p><p>But in reality?</p><p>Almost never happens.</p><p>Developers write the <code>.feature</code> files.<br>Developers maintain them.<br>Developers debug them at runtime.</p><p>Most teams follow the usual setup: they write Gherkin scenarios in <code>.feature</code> files using Cucumber (Java), SpecFlow (.NET) or Cucumber.js (TypeScript).</p><p>At first glance, it looks great:</p><p>English sentences.<br>Given / When / Then.</p><p>But&#8230; </p><h2>&#9888;&#65039;The Hidden Cost of Textual Gherkin</h2><p>Scenario files look readable, but become a maintenance burden:</p><pre><code><code>Scenario: Calculate base price as product of unit price and quantity
  Given a product with unit price $20.00
  When an order is placed for that product with quantity 5
  Then the order placement should succeed
  And the order base price should be $100.00</code></code></pre><p><strong>Duplication everywhere</strong></p><p>That sentence in the <code>.feature</code> file?</p><p>You repeat it again in step definitions (&#8220;glue code&#8220;).</p><p>Change one word?<br>Now you change it in multiple places:</p><ul><li><p>Change it in the step definition</p></li><li><p>Change it in all affected test scenarios</p></li></ul><p><strong>Typos = runtime failure</strong></p><p>Misspell a word?</p><p>You won&#8217;t know until you run the test.</p><p>The compiler doesn&#8217;t help you.</p><p>Your IDE doesn&#8217;t help you.</p><p>You only find out after everything compiles&#8230; and then fails.</p><p><strong>You&#8217;re on your own.</strong></p><p>No auto-complete.<br>No &#8220;what comes next?&#8221;<br>No guardrails.</p><p>You have to remember exact sentences.</p><p>And nothing prevents you from:</p><ul><li><p>Writing two <code>When</code> steps</p></li><li><p>Writing steps that don&#8217;t exist</p></li><li><p>Mixing domain concepts accidentally</p></li></ul><p>It&#8217;s fragile by design.</p><div><hr></div><p><strong>Want to skip the pain and go straight to the solution?</strong></p><p>I&#8217;m running a live workshop where we build acceptance tests that are compile-time safe, IDE-guided, and refactor-proof &#8212; no .feature files, no glue code.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop&quot;,&quot;text&quot;:&quot;Join the workshop &#8594;&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://optivem.thinkific.com/products/courses/2026-05-27-acceptance-testing-workshop"><span>Join the workshop &#8594;</span></a></p><p>Limited spots. Register now with the early bird discount - <strong>100 EUR off with code EARLYBIRD100 </strong></p><div><hr></div><h2>&#128161;What Changed Everything for Me</h2><p>Instead of writing English sentences and matching them with strings&#8230;</p>
      <p>
          <a href="https://journal.optivem.com/p/stop-writing-fragile-gherkin">
              Read more
          </a>
      </p>
   ]]></content:encoded></item></channel></rss>