<?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>Sat, 25 Apr 2026 16:38:13 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: 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 broken tax calculation despite having green tests across the board.</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><item><title><![CDATA[DDD + Clean Architecture: Stop Putting Business Logic in the Application Layer]]></title><description><![CDATA[Your Clean Architecture isn&#8217;t clean if the domain is empty]]></description><link>https://journal.optivem.com/p/ddd-clean-architecture-dont-put-business-logic-in-application-layer</link><guid isPermaLink="false">https://journal.optivem.com/p/ddd-clean-architecture-dont-put-business-logic-in-application-layer</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 27 Feb 2026 07:01:53 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/e4338280-54a0-4076-9ab2-e0613f86fdee_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>When people try Clean Architecture&#8230; they often end up with bloated application layers and empty domain layers.</p><pre><code><code>Application Layer:
   - Validate input
   - Load entities
   - Perform business logic
   - Call repositories
   - Call gateways
   - Publish events

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

    public int getId() {
        return id;
    }

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

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

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

    public String getSku() {
        return sku;
    }

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

    public int getQuantity() {
        return quantity;
    }

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

    public boolean isExpired() {
        return expired;
    }

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

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

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

    items.add(item);
    order.setItems(items);
    orderRepository.save(order);
}</code></pre></div><p>All the rules (expiration check, limit check) live in the <strong>application layer</strong>.</p><p>The entity is just a data container.</p><p>Basically, you&#8217;ve moved all the complexity into a layer that&#8217;s supposed to orchestrate, not decide.</p><h2>&#10060;Bloated Application Layer</h2>
      <p>
          <a href="https://journal.optivem.com/p/ddd-clean-architecture-dont-put-business-logic-in-application-layer">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[TDD: No Test? No Implementation.]]></title><description><![CDATA[Test the Requirement First]]></description><link>https://journal.optivem.com/p/tdd-no-test-no-implementation</link><guid isPermaLink="false">https://journal.optivem.com/p/tdd-no-test-no-implementation</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Mon, 23 Feb 2026 11:18:58 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/fafbaf5b-cfb1-4fa2-a27a-cef3ba6a3000_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128075; <em>Hello, this is Valentina with the free edition of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>Most devs start coding with only a hazy idea of what &#8220;done&#8221; really means.</p><ul><li><p>You assume requirements are clear.</p></li><li><p>You assume you understand the edge cases.</p></li><li><p>You assume QA will catch any gaps.</p></li></ul><p>TDD forces you to confront assumptions before you touch the code.</p><p><strong>It&#8217;s not about writing more tests.</strong><br><strong>It&#8217;s about clarity, assurance, and focus.</strong></p><p>When you know you can test a requirement, you know exactly what success looks like.</p><p>And that changes everything &#8212; from how you design your interface to how you structure your implementation to how fast you can deliver working software.</p><h2>&#9889;Can You Even Test the Requirement?</h2><p>Before you write code, TDD forces you to ask:</p><blockquote><p>Can I write a test for this requirement?</p></blockquote><p>If you can&#8217;t, something&#8217;s wrong.</p><p>Maybe:</p><ul><li><p>The requirement is vague.</p></li><li><p>There&#8217;s no concrete example.</p></li><li><p>Nobody knows what &#8220;done&#8221; really means.</p></li></ul><p>And here&#8217;s the uncomfortable truth:</p><p>If you can&#8217;t construct an example to verify the requirement&#8230;<br>&#8230; you&#8217;re about to implement something blind.</p><p>You might push code that looks correct.<br>It might even pass code reviews.<br>And yet&#8230; it may fail silently in production.</p><p>TDD flips the script.</p><p>The first checkpoint isn&#8217;t your implementation.<br>It&#8217;s the requirement itself.</p><p><strong>No test? No implementation.</strong></p><p>That simple rule prevents hours (or days) of wasted effort, wasted code, and wasted frustration.</p><h2>&#9889;TDD &amp; ATDD</h2><p>TDD is amazing at keeping <strong>your code correct</strong>, but it mostly protects <strong>your implementation</strong>.</p><p>ATDD (Acceptance Test&#8211;Driven Development) takes it one step further.</p><p>Instead of just asking:</p><blockquote><p>&#8220;Can I write a test that proves my code works?&#8221;</p></blockquote><p>ATDD asks:</p><blockquote><p>&#8220;Can I write a test that proves the system behaves as the user expects?&#8221;</p></blockquote><p>It shifts the focus from &#8220;my code works&#8221; to &#8220;the system works for everyone.&#8221;</p><ul><li><p>Requirements become executable specifications.</p></li><li><p>Developers, QA, and Product Owners are all aligned on what &#8220;done&#8221; really means.</p></li><li><p>Regression bugs drop, because everyone agrees on the expected behavior before a single line of code is written.</p></li></ul><p>Think of it this way:</p><ul><li><p>TDD protects the code.</p></li><li><p>ATDD protects the behavior.</p></li></ul><p>If you&#8217;re working on <strong>legacy systems</strong> or teams where QA keeps reporting regression bugs, ATDD can transform the process &#8212; turning vague requirements and tribal knowledge into tests that everyone trusts.</p><h2>&#9889;Release Without the Stress</h2><p>You know something is wrong with how testing is done.</p><p>This workshop is for engineers who refuse to accept that &#8220;this is just how it is.&#8221;</p><p><strong>Date &amp; Time:</strong> Wed Feb 25, 5-7PM (CET)<br><strong>Where:</strong> Live online<br><strong>Cost:</strong> &#8364;97 &#8212; (100% off for Optivem Journal paid members)</p><p><strong>&#128204;Spots are limited &#8211; Sign up here:</strong> <a href="https://optivem.thinkific.com/order?ct=4816abff-c281-4105-a6ab-0bceca765f6b">Acceptance Testing (Live Training)</a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/order?ct=4816abff-c281-4105-a6ab-0bceca765f6b&quot;,&quot;text&quot;:&quot;Reserve Your Spot&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/order?ct=4816abff-c281-4105-a6ab-0bceca765f6b"><span>Reserve Your Spot</span></a></p><p>&#127873; <strong>Free for Paid Members </strong>- <a href="https://journal.optivem.com/p/discount">Claim your 100% discount</a><br>Not a member yet? <a href="https://journal.optivem.com/subscribe">Upgrade now</a> for immediate access and replays</p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Hexagonal Architecture: Do NOT mock everything]]></title><description><![CDATA[Stop Mocking. Start Using Fakes, Stubs, and Spies.]]></description><link>https://journal.optivem.com/p/hexagonal-architecture-do-not-mock-everything</link><guid isPermaLink="false">https://journal.optivem.com/p/hexagonal-architecture-do-not-mock-everything</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 19 Feb 2026 07:01:12 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/5ba7bc75-490b-45ff-b8e9-0b7cd01a3dcd_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128197; Join me for <strong><a href="https://optivem.thinkific.com/products/live_events/2026-02-25-acceptance-testing-live-training">Acceptance Testing (Live Training)</a></strong> on Wed 25th Feb (17:00 - 19:00 CET) <em>(100% discount for Optivem Journal members)</em></p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>The most common mistake in Hexagonal Architecture is mocking everything outside the domain.</p><p>Tests break after the tiniest change.</p><p>Refactors become nightmares.</p><p>And your &#8220;clean architecture&#8221; is now just a pile of mocks.</p><h2>&#10060; Over-Mocking Everything</h2><p>Okay, Order Service Test&#8230; let&#8217;s mock all the driven ports:  </p><pre><code><code>- Repository? mock
- Payment Gateway? mock
- Email Service? mock</code></code></pre><p>By the time your test runs, it&#8217;s fragile spaghetti:</p><ul><li><p>Change a method signature in the repository? Test fails.</p></li><li><p>Change a method signature in the payment gateway? Test fails.</p></li></ul><p>In the repository, I had a method <code>add(int id, OrderData data)</code>. I refactored it to be <code>add(Order order)</code>&#8230; tests fail because the mock is now out-of-date.</p><p>In the payment gateway, I used the method <code>getAllInvoices()</code> and then did in-memory filtering to find an invoice. In my source code I decided to use the method <code>getInvoice(String invoiceId)</code>&#8230; tests fail because the mock now is out-of-date.</p><p>You now have <strong>hexagonal architecture&#8230; and 47 failing tests</strong>.</p><p>Why it&#8217;s bad:</p><ul><li><p>Tests describe implementation, not behavior</p></li><li><p>Ports are treated as a checklist of mocks, not boundaries</p></li><li><p>Refactoring domain logic becomes a nightmare</p></li><li><p>Cognitive load goes through the roof</p></li></ul><h2>&#9989; Testing What Matters</h2><p>Focus on <strong>behavior, not mocks</strong>:</p><ul><li><p>&#9989; Fakes</p></li><li><p>&#9989; Stubs</p></li><li><p>&#9989; Spies</p></li><li><p>&#10060; Mocks</p></li></ul><p>For the same Order Service:</p><pre><code><code>- Repository &#8594; in-memory fake
- Payment Gateway &#8594; simple stub that can be configured to maybe payment succeed or fail
- Email Service &#8594; spy, whereby we collect emails in a queue, and inspect emails sent</code></code></pre><p>Notice the difference?</p><ul><li><p>Tests now describe what the domain does, not how infrastructure works.</p></li><li><p>Refactoring domain code rarely breaks tests.</p></li><li><p>Cognitive load drops &#8212; you&#8217;re testing one thing at a time.</p></li></ul><blockquote><p>Fake data, not behavior. Test behavior, not calls.</p></blockquote><h1>&#128161;Order Test Without Mock Overload</h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nfP-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab030e5-4a45-4ebe-824f-53ff8bbbf4af_1654x1991.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nfP-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab030e5-4a45-4ebe-824f-53ff8bbbf4af_1654x1991.png 424w, https://substackcdn.com/image/fetch/$s_!nfP-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab030e5-4a45-4ebe-824f-53ff8bbbf4af_1654x1991.png 848w, https://substackcdn.com/image/fetch/$s_!nfP-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab030e5-4a45-4ebe-824f-53ff8bbbf4af_1654x1991.png 1272w, https://substackcdn.com/image/fetch/$s_!nfP-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab030e5-4a45-4ebe-824f-53ff8bbbf4af_1654x1991.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nfP-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab030e5-4a45-4ebe-824f-53ff8bbbf4af_1654x1991.png" width="1456" height="1753" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fab030e5-4a45-4ebe-824f-53ff8bbbf4af_1654x1991.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1753,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:331079,&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/187517057?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab030e5-4a45-4ebe-824f-53ff8bbbf4af_1654x1991.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_!nfP-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab030e5-4a45-4ebe-824f-53ff8bbbf4af_1654x1991.png 424w, https://substackcdn.com/image/fetch/$s_!nfP-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab030e5-4a45-4ebe-824f-53ff8bbbf4af_1654x1991.png 848w, https://substackcdn.com/image/fetch/$s_!nfP-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab030e5-4a45-4ebe-824f-53ff8bbbf4af_1654x1991.png 1272w, https://substackcdn.com/image/fetch/$s_!nfP-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffab030e5-4a45-4ebe-824f-53ff8bbbf4af_1654x1991.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>
          <a href="https://journal.optivem.com/p/hexagonal-architecture-do-not-mock-everything">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[TDD: Test the API, NOT the world]]></title><description><![CDATA[Write fast and deterministic tests]]></description><link>https://journal.optivem.com/p/tdd-test-the-api-not-the-world</link><guid isPermaLink="false">https://journal.optivem.com/p/tdd-test-the-api-not-the-world</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 13 Feb 2026 09:23:41 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/c00c418e-f1bf-43e9-b73d-a4cfc213166f_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128197; Join me for <strong><a href="https://optivem.thinkific.com/products/live_events/2026-02-25-acceptance-testing-live-training">Acceptance Testing (Live Training)</a></strong> on Wed 25th Feb (17:00 - 19:00 CET) <em>(100% discount for Optivem Journal members)</em></p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>Many years ago, I used to think every test had to touch everything &#8212; the database, other services, payment providers&#8230; you name it. </p><p>Then I realized: <strong>that&#8217;s inefficient, feedback loop is too slow. </strong>We need tests at different levels.</p><p>A user adds a product to their cart, maybe ten times while browsing, and then finally hits checkout, then later cancels their order.</p><p>That&#8217;s two very different use cases: <strong>AddItemToCart </strong>and <strong>PlaceOrder</strong>. And each requires different tests.</p><h2>The problem: Use Cases and the External World</h2><p>Most real-world use cases depend on something external:</p><ul><li><p>Databases</p></li><li><p>Message brokers</p></li><li><p>External API services</p></li></ul><p>For example, the PlaceOrder use case involves the DB (we need to save orders), it involves message brokers (we publish an event, so that the reporting service can register the newly placed order), and it involves external API services (we need to use PayPal for processing payment for the placed order).</p><p>The problem with testing use cases in this way, involving the external world are the following:</p><ul><li><p>The test is too slow due to the I/O</p></li><li><p>We might not be able to test various logic scenarios</p></li><li><p>The test might not be able to run if the external system is not available</p></li></ul><h2>The solution: Isolation from the External World</h2><p>In order to be able to test our use case in isolation from the external world:</p><ul><li><p><strong>User-side api:</strong> We test the use case via its driving (incoming) port, so that we bypass UI / REST API</p></li><li><p><strong>Server-side api:</strong> We &#8220;mock out&#8221; the driven (outgoing) ports, so that we&#8217;re excluding DB, external APIs, etc. In this way, the use case depends on interfaces of the external world, and doesn&#8217;t know (or care) about concrete implementations</p></li></ul><p>This keeps your core logic <strong>isolated and maintainable</strong>.</p><h2>Server-side API: Enter Test Doubles</h2><p>When we write <strong>unit tests</strong>, those server-side ports are replaced with <strong>in-memory test doubles</strong>:</p><ul><li><p>Repositories Test Doubles (e.g. Order Repository Test Double)</p></li><li><p>Gateway Test Doubles (e.g. Notification Test Double, Payment Test Double)</p></li></ul><p>There are different types of Test Doubles: Dummies, Fakes, Stubs, Spies, Mocks (see Fowler&#8217;s <a href="https://martinfowler.com/bliki/TestDouble.html">Test Double</a> article)</p><p>The test doubles:</p><ul><li><p>Live entirely in memory</p></li><li><p>Run ultra fast</p></li><li><p>Behave predictably</p></li></ul><p>Basically, they let your tests focus on <strong>business logic</strong>, not &#8220;what the database or external API does today.&#8221;</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!OHx9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821c1-9a0c-4705-99b3-bff58d075339_1025x1264.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!OHx9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821c1-9a0c-4705-99b3-bff58d075339_1025x1264.png 424w, https://substackcdn.com/image/fetch/$s_!OHx9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821c1-9a0c-4705-99b3-bff58d075339_1025x1264.png 848w, https://substackcdn.com/image/fetch/$s_!OHx9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821c1-9a0c-4705-99b3-bff58d075339_1025x1264.png 1272w, https://substackcdn.com/image/fetch/$s_!OHx9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821c1-9a0c-4705-99b3-bff58d075339_1025x1264.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!OHx9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821c1-9a0c-4705-99b3-bff58d075339_1025x1264.png" width="1025" height="1264" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/660821c1-9a0c-4705-99b3-bff58d075339_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;:154142,&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/181080596?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821c1-9a0c-4705-99b3-bff58d075339_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_!OHx9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821c1-9a0c-4705-99b3-bff58d075339_1025x1264.png 424w, https://substackcdn.com/image/fetch/$s_!OHx9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821c1-9a0c-4705-99b3-bff58d075339_1025x1264.png 848w, https://substackcdn.com/image/fetch/$s_!OHx9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821c1-9a0c-4705-99b3-bff58d075339_1025x1264.png 1272w, https://substackcdn.com/image/fetch/$s_!OHx9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821c1-9a0c-4705-99b3-bff58d075339_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><em>Note: The above image is for a microservice, but a similar image can be applied to the frontend. Also, in case of monolithic architecture, similar image can be applied to the monolith.</em></p><h2>AddItemToCart vs PlaceOrder (Real-life Example)</h2>
      <p>
          <a href="https://journal.optivem.com/p/tdd-test-the-api-not-the-world">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Developer vs QA: Regression Bug Ping-Pong]]></title><description><![CDATA[From &#8220;It worked&#8221; to &#8220;Nothing works as expected&#8221;]]></description><link>https://journal.optivem.com/p/developer-vs-qa-regression-bug-ping-pong</link><guid isPermaLink="false">https://journal.optivem.com/p/developer-vs-qa-regression-bug-ping-pong</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Tue, 10 Feb 2026 08:11:55 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/3840ded2-df53-4a46-b689-6c07572225fb_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>You just finished a User Story.<br>Tested it locally. It worked.<br>You pushed it. Felt good.</p><p>And then&#8230;<br>QA comes back with <strong>a long list of regression bugs</strong>.</p><p>Again.</p><p>Your first reaction?</p><blockquote><p>&#8220;How is this even related to what I changed?&#8221;</p></blockquote><p>Your second reaction?</p><blockquote><p>&#8220;Did QA even test this properly?&#8221;</p></blockquote><p>And your third (the quiet one)?</p><blockquote><p>&#8220;Am I actually bad at my job?&#8221;</p></blockquote><p>You implemented <em>exactly</em> what the story asked for.<br>You didn&#8217;t refactor half the system.<br>You didn&#8217;t touch those areas.</p><h2>&#8220;But it worked on my machine.&#8221;</h2><p>You change one thing&#8230;<br>and accidentally affect five others.</p><p>Not because you&#8217;re reckless &#8212;<br>but because the system doesn&#8217;t <em>tell you</em> what behavior you&#8217;re about to break.</p><p>Manual QA discovers these issues <em>after the fact</em>.</p><p>The problem is that <strong>expected behavior lives only in people&#8217;s heads</strong>.<br>Or worse:</p><ul><li><p>In outdated tickets</p></li><li><p>In vague acceptance criteria</p></li><li><p>In tribal knowledge</p></li><li><p>In &#8220;that one person who knows the system&#8221;</p></li></ul><p>Nothing is executable.<br>Nothing runs automatically.<br>Nothing protects you <em>before</em> QA gets involved.</p><p>So regression bugs slip in &#8212; silently.</p><h2>What Acceptance Tests change</h2><p>Acceptance Tests move expectations <strong>out of conversations</strong> and <strong>into code</strong>.</p><p>They answer questions like:</p><ul><li><p>&#8220;What must <em>never</em> break?&#8221;</p></li><li><p>&#8220;What behaviors define &#8216;working&#8217;?&#8221;</p></li><li><p>&#8220;What does the business actually rely on?&#8221;</p></li></ul><p>And they answer them <strong>before</strong> QA ever sees the build.</p><p>When you have Acceptance Tests in your pipeline:</p><ul><li><p>You catch regression bugs immediately</p></li><li><p>QA spends less time reporting obvious breakages</p></li><li><p>Developers stop being surprised</p></li><li><p>Confidence comes back</p></li></ul><h2>Legacy Code is where bugs thrive</h2><p>You <em>expect</em> things to break.<br>You&#8217;re afraid to touch anything.<br>Every change feels risky.</p><p>If you&#8217;ve ever thought:</p><ul><li><p>&#8220;QA is blocking us&#8221;</p></li><li><p>&#8220;Regression bugs keep killing our velocity&#8221;</p></li><li><p>&#8220;Every release feels risky&#8221;</p></li><li><p>&#8220;We test, but it&#8217;s never enough&#8221;</p></li></ul><p>That&#8217;s exactly where Acceptance Tests help most.</p><h2>Want to see how this works in practice?</h2><p>&#128073; <strong>Join me for a 2-hour live training on Acceptance Testing</strong></p><p>I&#8217;ll walk you through:</p><ul><li><p>How to stop regression bugs <em>before</em> QA finds them</p></li><li><p>How to write maintainable, behavior-driven acceptance tests</p></li><li><p>How to do this safely &#8212; even in legacy code</p></li><li><p>How to reduce friction between devs and QA</p></li></ul><p>No theory-heavy fluff.<br>No &#8220;rewrite everything&#8221;.<br>Just a practical roadmap you can apply immediately.</p><p><strong>&#128204;Spots are limited &#8211; Sign up here:</strong> <a href="https://optivem.thinkific.com/order?ct=4816abff-c281-4105-a6ab-0bceca765f6b">Acceptance Testing (Live Training)</a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/order?ct=4816abff-c281-4105-a6ab-0bceca765f6b&quot;,&quot;text&quot;:&quot;Reserve Your Spot&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/order?ct=4816abff-c281-4105-a6ab-0bceca765f6b"><span>Reserve Your Spot</span></a></p><p>&#127873; <strong>Free for Paid Members </strong>- <a href="https://journal.optivem.com/p/discount">Claim your 100% discount</a><br>Not a member yet? &#128073; <a href="https://journal.optivem.com/subscribe">Upgrade now</a> for immediate access and replays</p>]]></content:encoded></item><item><title><![CDATA[8-Step Human–AI Code Review]]></title><description><![CDATA[AI-Powered Code Review]]></description><link>https://journal.optivem.com/p/8-step-human-ai-code-review</link><guid isPermaLink="false">https://journal.optivem.com/p/8-step-human-ai-code-review</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 06 Feb 2026 07:01:04 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/efcd24e5-39ff-4078-bb58-9fc52557150d_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128197; Join me for <strong><a href="https://optivem.thinkific.com/products/live_events/2026-02-25-acceptance-testing-live-training">Acceptance Testing (Live Training)</a></strong> on Wed 25th Feb (17:00 - 19:00 CET) <em>(100% discount for Optivem Journal members)</em></p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>I used to love code reviews. Going through a merge request line by line, questioning naming, debating implementation choices, which design patterns to use, etc....</p><p>Fast forward to today. AI is everywhere. Developers use AI to write code, refactor code, write tests. Before, it used to take hours/days, now it&#8217;s reduced to minutes.</p><p>The problem? Just because AI is fast doesn&#8217;t mean the code is good. Or even maintainable. How much should humans actually review?</p><h2>Code Review in the age of AI</h2><p>A developer recently told me he&#8217;s stopped reviewing code the way he used to. </p><p>He doesn&#8217;t read every line anymore. Instead, he checks:</p><ul><li><p><strong>Is the code readable?</strong></p></li><li><p><strong>Can a human actually understand what&#8217;s going on?</strong></p></li></ul><p>If the answer is no, the developer should explain or improve it. That&#8217;s it.</p><h2>Human Review was already shrinking before AI</h2><p>Even <em>before</em> AI, human review was already shrinking.</p><p>Linters in the pipeline would do formatting checks. SonarQube would do clean code checks &#8212; cyclomatic complexity, duplication, smells. A lot of issues that reviewers used to flag was already handled automatically.</p><p>So the shift didn&#8217;t start with AI.<br>AI just took it one level further.</p><p>Before AI, SonarLint would flag code with:</p><ul><li><p>Code smells</p></li><li><p>Complexity</p></li><li><p>Duplication</p></li></ul><p>Developers fixed those issues locally.</p><p>Once the code reached the Pipeline, Linters &amp; SonarQube ran the checks again, and developers fixed any remaining problems before merging.</p><h1>8 Steps to Smarter Human&#8211;AI Code Reviews</h1>
      <p>
          <a href="https://journal.optivem.com/p/8-step-human-ai-code-review">
              Read more
          </a>
      </p>
   ]]></content:encoded></item></channel></rss>