<?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[ATDD in Legacy Code with AI]]></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, 14 Nov 2026 22:16:41 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[Minimal Pipeline & Use Case Diagram in Legacy Code with AI]]></title><description><![CDATA[What to set up before your first acceptance test, with the AI prompt for each step (Acceptance Testing in Legacy Code with AI - Phase 0)]]></description><link>https://journal.optivem.com/p/acceptance-testing-legacy-code-ai-pipeline-use-case-diagram</link><guid isPermaLink="false">https://journal.optivem.com/p/acceptance-testing-legacy-code-ai-pipeline-use-case-diagram</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 13 Nov 2026 13:21:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!iMOA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34b6ee30-dcd4-45e6-a4f8-5fec2850ecbe_1860x476.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128274; <em>Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers cover legacy code with acceptance tests, using AI. This article is part of the <a href="https://journal.optivem.com/p/new-series-acceptance-testing-in-legacy-code-with-ai">Acceptance Testing in Legacy Code with AI</a> series. To get these articles in your inbox every week, subscribe. Until Tuesday, November 17, an annual subscription is <a href="https://journal.optivem.com/subscribe?coupon=26d7c410">20% off</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><p>Before we automate any use case with acceptance tests, we need two things:</p><p><strong>1. Minimal Pipeline:</strong> gets the system onto the acceptance environment the same way every time. Acceptance tests run against the deployed system, so when one fails, we need to know the feature broke, not the deploy.</p><ul><li><p>1.1 Dockerfiles</p></li><li><p>1.2 Docker Compose</p></li><li><p>1.3 Smoke Tests</p></li><li><p>1.4 Commit Stage</p></li><li><p>1.5 Acceptance Stage</p></li><li><p>1.6 Release Stages</p></li></ul><p><strong>2. Use Case Diagram:</strong> the actors, the use cases and the external systems. Every acceptance test covers one use case, so the diagram shows us what there is to test and which use case to start with.</p><ul><li><p>2.1 Ask the Product Owner</p></li><li><p>2.2 Generate the Use Case Diagram</p></li><li><p>2.3 Review with the Product Owner</p></li></ul><p>Where relevant, we give you the AI prompt for each step.</p><p>This is Phase 0 of the series <a href="https://journal.optivem.com/p/new-series-acceptance-testing-in-legacy-code-with-ai">Acceptance Testing in Legacy Code with AI</a>.</p><p>In the next article, we pick the core use case and write its use case narrative and business rules.</p><h2>1. Minimal Pipeline</h2><p>The pipeline has four stages. Every stage after the commit stage has its own environment.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iMOA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34b6ee30-dcd4-45e6-a4f8-5fec2850ecbe_1860x476.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iMOA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34b6ee30-dcd4-45e6-a4f8-5fec2850ecbe_1860x476.png 424w, https://substackcdn.com/image/fetch/$s_!iMOA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34b6ee30-dcd4-45e6-a4f8-5fec2850ecbe_1860x476.png 848w, https://substackcdn.com/image/fetch/$s_!iMOA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34b6ee30-dcd4-45e6-a4f8-5fec2850ecbe_1860x476.png 1272w, https://substackcdn.com/image/fetch/$s_!iMOA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34b6ee30-dcd4-45e6-a4f8-5fec2850ecbe_1860x476.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!iMOA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34b6ee30-dcd4-45e6-a4f8-5fec2850ecbe_1860x476.png" width="1456" height="373" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/34b6ee30-dcd4-45e6-a4f8-5fec2850ecbe_1860x476.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:373,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:46470,&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/224745127?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34b6ee30-dcd4-45e6-a4f8-5fec2850ecbe_1860x476.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_!iMOA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34b6ee30-dcd4-45e6-a4f8-5fec2850ecbe_1860x476.png 424w, https://substackcdn.com/image/fetch/$s_!iMOA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34b6ee30-dcd4-45e6-a4f8-5fec2850ecbe_1860x476.png 848w, https://substackcdn.com/image/fetch/$s_!iMOA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34b6ee30-dcd4-45e6-a4f8-5fec2850ecbe_1860x476.png 1272w, https://substackcdn.com/image/fetch/$s_!iMOA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34b6ee30-dcd4-45e6-a4f8-5fec2850ecbe_1860x476.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><ul><li><p><strong>Commit stage:</strong> one for each deployable component, such as the backend and the frontend. Compile the code, build the Docker images, publish them to the Docker registry.</p></li><li><p><strong>Acceptance stage:</strong> deploy to the acceptance environment, run the smoke tests.</p></li><li><p><strong>QA stage:</strong> deploy to the QA environment, run the smoke tests.</p></li><li><p><strong>Production stage:</strong> deploy to the production environment, run the smoke tests.</p></li></ul><p>Unit tests, component tests and static code analysis go in the commit stage too. Acceptance testing doesn&#8217;t need them, so we leave them out of this series.</p><p><em>Notice what&#8217;s missing from the acceptance stage: acceptance tests. They don&#8217;t exist yet. We build them later in the series.</em></p><p>The acceptance, QA and production stages all do the same two things: deploy the system, then run the smoke tests. So we write those two steps once and reuse them in all three.</p><p>Why go all the way to production? If you deploy to acceptance one way and to production another, a release can work on acceptance and still break in production. If all three stages deploy the same way, every deploy to acceptance is practice for production.</p><h3><strong>1.1 Dockerfiles</strong></h3><p>No Dockerfiles yet? Write them first. They also tell you whether anyone can still build the system from a clean checkout.</p><blockquote><p><strong>AI prompt:</strong> Write a Dockerfile for each deployable component:</p><ul><li><p>one for the backend,</p></li><li><p>one for the frontend.</p></li></ul><p>Find out how each is built and started today from the build scripts, the deployment scripts and the README. Build each from a clean checkout, using nothing from a developer&#8217;s machine. Don&#8217;t change the system&#8217;s code.</p></blockquote><p>Docker is the most common choice, and we recommend it wherever the system can run in a container. But it&#8217;s optional. If the system can&#8217;t run in a container, deploy it to a real server with the scripts you already have. You lose one thing: running the same setup on your machine and in the pipeline. You keep the most important one: every environment is deployed the same way. Without Docker, swap the Docker images for your build artifacts (such as JAR files or zip packages) and the Docker registry for your artifact repository. Run the system on your machine the way you do today; the steps stay the same.</p><h3><strong>1.2 Docker Compose</strong></h3><p>Write a Compose file to run the system on your machine. You use it to run the smoke tests locally before they go into the pipeline:</p><blockquote><p><strong>AI prompt:</strong> Write docker-compose.yml in the repository root:</p><ul><li><p>Build the backend and the frontend from their Dockerfiles and start them.</p></li><li><p>Start everything they need to start, such as the database and the login server.</p></li><li><p>Point each external system to its test instance.</p></li><li><p>Find every setting the system reads at startup in the deployment scripts and the README, and set each one in the compose file.</p></li></ul><p>Don&#8217;t change the system&#8217;s code, with one exception: if an external system&#8217;s URL is hard-coded, move it into the system&#8217;s configuration.</p></blockquote><p>One rule: external system URLs must be settings, not hard-coded. It&#8217;s the only change we make to the system&#8217;s code.</p><p>Example of the generated file, trimmed to the backend and the frontend:</p><pre><code><code>services:
  backend:
    build: ./backend
    ports:
      - "8080:8080"
    environment:
      - ERP_API_URL=https://test.erp.com
      - TAX_API_URL=https://test.tax.com
      - CLOCK_API_URL=https://test.clock.com
  frontend:
    build: ./frontend
    ports:
      - "3000:3000"
</code></code></pre><p><em>Why build the images locally instead of pulling them from the Docker registry? The registry is still empty: the commit stages that publish the images come later. And on your machine, you want to run the code you just changed.</em></p><p><em>Why make external system URLs configurable? Later in the series we replace the external systems with stubs, and that only works if the URL is a setting.</em></p>
      <p>
          <a href="https://journal.optivem.com/p/acceptance-testing-legacy-code-ai-pipeline-use-case-diagram">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[New series: Acceptance Testing in Legacy Code with AI]]></title><description><![CDATA[It doesn't have to take years. It can now take months, or even weeks.]]></description><link>https://journal.optivem.com/p/new-series-acceptance-testing-in-legacy-code-with-ai</link><guid isPermaLink="false">https://journal.optivem.com/p/new-series-acceptance-testing-in-legacy-code-with-ai</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Tue, 10 Nov 2026 14:48:08 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Ap0E!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9020530e-1be0-486c-904e-0dbab76325de_1200x630.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>AI made coding faster, but your releases didn&#8217;t speed up, because every feature still goes through manual testing. I help teams replace that with acceptance tests that run automatically in your pipeline. I have a few spots left for 2027. <a href="https://calendly.com/valentinajemuovic/call">Book a call</a>.</em></p><p>One of the biggest challenges with practicing ATDD is the existing legacy code base, which basically has no tests. You write acceptance tests for the new user story, you see them red, then green. But you could have very easily broken something that already worked.</p><p>Automating the whole system used to take years and years. So it wasn&#8217;t feasible to automate the whole system. Rather, we did it incrementally: for each user story, we first did legacy coverage.</p><p>But here&#8217;s the thing. When AI came in, I changed my approach. Hey, it doesn&#8217;t have to take years. It can now take months, or even weeks. I think it&#8217;s the first time in history ever that we can do possibly full system coverage of a legacy system.</p><p>So why does it matter that we cover the whole system?</p><ul><li><p><strong>No more manual regression testing.</strong> The manual test procedures that run before every release are covered by acceptance tests in the pipeline.</p></li><li><p><strong>You can modernize safely.</strong> Move to a modern tech stack, or restructure the architecture, for example from a monolith to microservices. The acceptance tests talk to the system only through its API and UI, so they keep checking the behaviour while you replace what&#8217;s behind them.</p></li><li><p><strong>You can trust what AI changes.</strong> AI writes code faster than anyone can review it by hand, and the acceptance tests tell you what a change broke.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ap0E!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9020530e-1be0-486c-904e-0dbab76325de_1200x630.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ap0E!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9020530e-1be0-486c-904e-0dbab76325de_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!Ap0E!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9020530e-1be0-486c-904e-0dbab76325de_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!Ap0E!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9020530e-1be0-486c-904e-0dbab76325de_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!Ap0E!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9020530e-1be0-486c-904e-0dbab76325de_1200x630.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ap0E!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9020530e-1be0-486c-904e-0dbab76325de_1200x630.png" width="1200" height="630" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9020530e-1be0-486c-904e-0dbab76325de_1200x630.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:630,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:49699,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://journal.optivem.com/i/224312696?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9020530e-1be0-486c-904e-0dbab76325de_1200x630.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_!Ap0E!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9020530e-1be0-486c-904e-0dbab76325de_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!Ap0E!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9020530e-1be0-486c-904e-0dbab76325de_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!Ap0E!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9020530e-1be0-486c-904e-0dbab76325de_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!Ap0E!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9020530e-1be0-486c-904e-0dbab76325de_1200x630.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>The new approach</h2><p><strong>Phase 0: the pipeline and the use case diagram</strong></p><p>We need a pipeline with the commit stage, the acceptance stage and the release stage, so from day one we see the tests passing in the acceptance stage.</p><p>Then the use case diagram, including which use cases depend on each other. The AI writes it from the source code, the docs and the running system. We pick the most important use case.</p><p><strong>Phase 1: the pilot</strong></p><p>We start with the most important use case and the ones it depends on, in that order. For example, Place Order with a coupon needs a published coupon, so Publish Coupon goes first.</p><ol><li><p><strong>Use case narrative.</strong> The main scenario and the alternative scenarios. We review it with the domain experts.</p></li><li><p><strong>Acceptance criteria.</strong> The use case scenarios, written as concrete examples with real values.</p></li><li><p><strong>Acceptance tests, passing.</strong> Against the existing system, without changing it. Once the tests for the most important use case pass in the acceptance stage of the pipeline, we demo it to your team.</p></li></ol><p><strong>Phase 2: scaling</strong></p><p>The pilot is where you learn the lessons if something goes wrong with AI. After the go-ahead, we repeat the same three steps for all the other use cases, one at a time, in the order they depend on each other.</p><p>To get that order, we use AI to build a dependency tree of the use cases. The tree sets the sequence: a use case only comes after the use cases it depends on.</p><p><em>This series is a work in progress. This list itself may be updated as I go through the writing of the series. The focus is primarily technical, but where I see it&#8217;s relevant, I might also include how to get buy-in from your team.</em></p><h2>Where you end up</h2><p>In a few weeks or a few months, instead of three to five years, you actually have protection. Then your team can keep working test-last, or move on to ATDD.</p><p>Ask me in the comments as you try to apply this in your real life project.</p><h2>The discount</h2><p>There&#8217;s a discount for the next 7 days: 20% off an annual subscription, until Tuesday, November 17.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?coupon=26d7c410&quot;,&quot;text&quot;:&quot;Get 20% off&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?coupon=26d7c410"><span>Get 20% off</span></a></p><p>This series is practical so you can apply it at work - you get AI prompts and code examples. You can ask me questions as you try to apply it at your job. This series is designed for busy software engineers who want to get results fast.</p><h2>Using your training budget</h2><p>You can expense it to your team&#8217;s training budget.</p><p>Here&#8217;s a sample email you can send your manager. Feel free to adapt it:</p><blockquote><p>Hi [manager name],</p><p>We&#8217;re using AI to code faster, but every release still waits for manual regression testing. We need to solve that problem.</p><p>Optivem Journal has a new series on using AI to cover a legacy system with automated acceptance tests in weeks or months, not years. The tests run in the pipeline, so we can stop testing every release by hand. It includes the AI prompts and code examples, so I can try it on our real system at work. Paid subscribers can also ask questions and get support when they get stuck.</p><p>I&#8217;d like to expense an annual subscription from our training budget. It&#8217;s 20% off until Tuesday, November 17: <a href="https://journal.optivem.com/26d7c410">https://journal.optivem.com/26d7c410</a></p><p>Thanks,<br>[your name]</p><p>P.S. If it&#8217;s useful for the whole team, there&#8217;s also a group subscription, so we could all go through the series together.</p></blockquote><p><em>You can get an invoice for your company from Substack (<a href="https://support.substack.com/hc/en-us/articles/360037489832-How-do-I-get-an-invoice-for-my-subscription">see how</a>). To subscribe your whole team, click &#8220;Other payment options&#8221; when subscribing and choose &#8220;Group Subscription&#8221; (at least four people).</em></p>]]></content:encoded></item><item><title><![CDATA[Acceptance test coverage with AI - fast]]></title><description><![CDATA[The behaviour is already there. AI can write it down, and the running system tells you where it got it wrong.]]></description><link>https://journal.optivem.com/p/acceptance-test-coverage-with-ai</link><guid isPermaLink="false">https://journal.optivem.com/p/acceptance-test-coverage-with-ai</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 06 Nov 2026 16:14:18 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/0fc14ac4-a59d-4a03-8bb5-b39c3f59ba3a_1200x630.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>AI made coding faster, but your releases didn&#8217;t speed up, because every feature still gets tested by hand. I help teams replace that with acceptance tests that run automatically. I have a few spots left for 2027. <a href="https://calendly.com/valentinajemuovic/call">Book a call</a>.</em></p><p>Most systems are legacy: years in production, no acceptance tests, and nobody can say exactly how they behave. Before every release, someone works through the manual test procedures by hand, and the list grows with every release.</p><p>Writing acceptance tests for the whole system by hand would take months, so it never starts. But the behaviour already exists: in the running system, the source code, the manual test procedures and the domain experts&#8217; heads. AI can write it down, and the running system checks what the AI wrote.</p><p>The steps:</p><p><strong>Phase 1. Prove it on the core use case</strong></p><ul><li><p><strong>Step 1.1.</strong> Generate the use case diagram.</p></li><li><p><strong>Step 1.2.</strong> Generate the use case narrative for the core use case.</p></li><li><p><strong>Step 1.3.</strong> Generate the acceptance criteria for the core use case.</p></li><li><p><strong>Step 1.4.</strong> Set up the acceptance test architecture, with the acceptance tests for the core use case.</p></li><li><p><strong>Step 1.5.</strong> Ensure that the acceptance tests pass against the existing system.</p></li></ul><p><strong>Phase 2. Scale to the other use cases</strong></p><ul><li><p><strong>Step 2.1.</strong> Generate the use case narratives for the other use cases.</p></li><li><p><strong>Step 2.2.</strong> Generate the acceptance criteria for the other use cases.</p></li><li><p><strong>Step 2.3.</strong> Generate the acceptance tests for the other use cases.</p></li><li><p><strong>Step 2.4.</strong> Ensure that the acceptance tests pass against the existing system.</p></li><li><p><strong>Step 2.5.</strong> Delete the acceptance criteria.</p></li></ul><p>Take one use case all the way through before doing the rest. Each step comes with its prompt. The examples come from an online shop.</p>
      <p>
          <a href="https://journal.optivem.com/p/acceptance-test-coverage-with-ai">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Prompt Engineering: Stop Asking the Wrong Questions]]></title><description><![CDATA[Specify the problem, not just the solution]]></description><link>https://journal.optivem.com/p/prompt-engineering-stop-asking-the-wrong-questions</link><guid isPermaLink="false">https://journal.optivem.com/p/prompt-engineering-stop-asking-the-wrong-questions</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 29 Oct 2026 07:01:50 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/7d883e55-d94f-4a3b-b537-5a24da26449d_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>&#9889;Spec-Driven Development in Legacy Code</strong><br>Hands-on work with your teams. Limited spaces for 2027.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://calendly.com/valentinajemuovic/call&quot;,&quot;text&quot;:&quot;Book a call&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://calendly.com/valentinajemuovic/call"><span>Book a call</span></a></p><div><hr></div><p>A prompt might contain precise instructions but make a false assumption.</p><p>It might ask for a solution before the problem is understood.</p><p>Or it might push the AI to agree with what you already believe.</p><p>Models tend to do this anyway. Researchers call it sycophancy: the model leans towards the answer it thinks you want.</p><p>The result can be an answer that sounds convincing, looks professional, and solves the wrong problem.</p><h2>1. Don&#8217;t use vague quality words when you can name the criteria</h2><p><strong>&#10060; Asking for &#8220;rigor&#8221; without saying what rigor means</strong></p><p>&#8220;Act as a senior software architect and give me a rigorous review of this design.&#8221;</p><p><strong>What&#8217;s wrong?</strong></p><p>Words like <em>senior</em>, <em>rigorous</em>, and <em>high-quality</em> sound useful, but they don&#8217;t say what the AI should examine or how it should back up its conclusions.</p><p>A response can sound authoritative without naming a single problem you can act on.</p><p>A role can set a perspective, but it doesn&#8217;t replace a clear task. (Asking the model to reason step by step before answering is different &#8212; that does change how it works on the problem. &#8220;Be rigorous&#8221; doesn&#8217;t.)</p>
      <p>
          <a href="https://journal.optivem.com/p/prompt-engineering-stop-asking-the-wrong-questions">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Spec-Driven: Bad AI Prompts vs. Good AI Prompts]]></title><description><![CDATA[A detailed prompt can still send AI in the wrong direction.]]></description><link>https://journal.optivem.com/p/spec-driven-bad-ai-prompts-vs-good-ai-prompts</link><guid isPermaLink="false">https://journal.optivem.com/p/spec-driven-bad-ai-prompts-vs-good-ai-prompts</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 23 Oct 2026 06:01:35 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/8eec6bff-01e6-483a-ae7d-3557a9fd9168_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>&#9889;Spec-Driven Development in Legacy Code</strong><br>Hands-on work with your teams. Limited spaces for 2027.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://calendly.com/valentinajemuovic/call&quot;,&quot;text&quot;:&quot;Book a call&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://calendly.com/valentinajemuovic/call"><span>Book a call</span></a></p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal.</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>We often hear that getting better results from AI is a matter of writing better prompts.</p><p>Be more specific. Give the AI a role. Add context. Explain what you want.</p><p>All of that can help. But: <strong>a prompt can be detailed, well-written, and still lead you in the wrong direction.</strong></p><h2>1. Don&#8217;t ask AI to confirm your decision. Ask it to evaluate it.</h2><p><strong>&#10060; Treating the decision as already made</strong></p><p>&#8220;We&#8217;ve decided to move to microservices. Outline how to split our application into services.&#8221;</p><p><strong>What&#8217;s wrong?</strong></p><p>The decision is presented as settled, so the AI works on the next step. It will produce a sensible-looking list of services. It won&#8217;t usually stop to ask whether you need microservices at all.</p><p>Ask a model directly &#8220;Are microservices better than a monolith?&#8221; and it will usually say &#8220;it depends&#8221;. The risk is the prompt where the decision is built in and never questioned.</p>
      <p>
          <a href="https://journal.optivem.com/p/spec-driven-bad-ai-prompts-vs-good-ai-prompts">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Spec-Driven: When AI writes the code and the tests, green means nothing]]></title><description><![CDATA[Don't let AI invent the expected behavior. Use it to refine the requirement, then let the tests verify the code against it.]]></description><link>https://journal.optivem.com/p/spec-driven-write-test-before-code</link><guid isPermaLink="false">https://journal.optivem.com/p/spec-driven-write-test-before-code</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 16 Oct 2026 06:01:21 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/82d94237-8bbf-44ec-938e-b4b3fe25ab57_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>&#9889;Spec-Driven Development in Legacy Code</strong><br>Hands-on work with your teams. Limited spaces for 2027.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://calendly.com/valentinajemuovic/call&quot;,&quot;text&quot;:&quot;Book a call&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://calendly.com/valentinajemuovic/call"><span>Book a call</span></a></p><div><hr></div><p>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal.</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>AI writes code fast. Many teams then ask AI to write the tests for that code too.</p><p>That&#8217;s the problem. The tests check the code against itself. They don&#8217;t check it against what the business asked for.</p><p>It&#8217;s like a student marking their own exam. The answers always match, so they always pass. A test that can&#8217;t fail tells you nothing.</p><h2>&#10060; Write test after code</h2><p>The test comes from the code, not from the spec.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!A3ii!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a518c7f-efea-40a0-bdd7-a47b74d61afa_510x935.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!A3ii!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a518c7f-efea-40a0-bdd7-a47b74d61afa_510x935.png 424w, https://substackcdn.com/image/fetch/$s_!A3ii!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a518c7f-efea-40a0-bdd7-a47b74d61afa_510x935.png 848w, https://substackcdn.com/image/fetch/$s_!A3ii!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a518c7f-efea-40a0-bdd7-a47b74d61afa_510x935.png 1272w, https://substackcdn.com/image/fetch/$s_!A3ii!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a518c7f-efea-40a0-bdd7-a47b74d61afa_510x935.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!A3ii!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a518c7f-efea-40a0-bdd7-a47b74d61afa_510x935.png" width="510" height="935" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1a518c7f-efea-40a0-bdd7-a47b74d61afa_510x935.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:935,&quot;width&quot;:510,&quot;resizeWidth&quot;:510,&quot;bytes&quot;:51323,&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/220574853?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a518c7f-efea-40a0-bdd7-a47b74d61afa_510x935.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_!A3ii!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a518c7f-efea-40a0-bdd7-a47b74d61afa_510x935.png 424w, https://substackcdn.com/image/fetch/$s_!A3ii!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a518c7f-efea-40a0-bdd7-a47b74d61afa_510x935.png 848w, https://substackcdn.com/image/fetch/$s_!A3ii!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a518c7f-efea-40a0-bdd7-a47b74d61afa_510x935.png 1272w, https://substackcdn.com/image/fetch/$s_!A3ii!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a518c7f-efea-40a0-bdd7-a47b74d61afa_510x935.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M" tabindex="0" type="button"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M" tabindex="0" type="button"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h3>1. AI Reads Spec</h3><p>AI reads the spec:</p><blockquote><p>A customer gets the 10% promotion and their 20% coupon.</p></blockquote><p>There are (at least) two ways to interpret the spec:</p><ul><li><p><strong>Interpretation A:</strong> 10% off first, then 20% off what is left.</p></li><li><p><strong>Interpretation B:</strong> 10% + 20% = 30% off.</p></li></ul><h3>2. AI Writes Code</h3><p>AI picks interpretation A: 10% off first, then 20% off the reduced price.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;495e222c-030c-4a60-9578-7db4f7da4b6c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">var promotedPrice = basePrice.multiply(promotionFactor);
var discountAmount = promotedPrice.multiply(discountRate);
var totalPrice = promotedPrice.subtract(discountAmount);</code></pre></div><p><em>Note: AI is non-deterministic, so we can&#8217;t know which interpretation it will choose. In one run it may only think of interpretation A. In another run it may only think of B. In another it may see both and ask us to choose. And in another it may come up with an interpretation C that we hadn&#8217;t even thought of.</em></p><h3>3. AI Writes Acceptance Test</h3><p>AI reads its own code, then writes a test that mirrors the behavior of that code. It makes up inputs and answers that fit the code:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;f9969ad4-f0ee-43f9-bde6-87235c032894&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">@TestTemplate
@Channel({ChannelType.UI, ChannelType.API})
void shouldApplyCouponOnPromotedPrice() {
    scenario
            .given().product().withUnitPrice(&#8221;100.00&#8221;)
                .and().promotion().isActive().withDiscount(&#8221;10%&#8221;)
                .and().coupon().withDiscountRate(&#8221;20%&#8221;)
            .when().placeOrder()
            .then().shouldSucceed()
                .and().order().hasTotalPrice(72.00);
}</code></pre></div><h3>4. Verify Acceptance Test Passes</h3><p>The acceptance test is executed. It passes, because it was made to match the code, so the build is green.</p><h3>5. Human discovers misinterpretation</h3><p>The build is green, so the work goes to QA. QA reads the spec their own way. They expect $70.00 and see $72.00, so they report a bug. The bug goes to the product owner.</p><p>Only now does anyone ask the question that should have been asked first. The product owner says 30% off.</p><h3>6. AI reworks code &amp; test</h3><p>The code is wrong, and the test is wrong too, because it was made to match the code. Both the code and the test must be reworked.</p><p>It was circular: the code decided the test, and then the test &#8220;proved&#8221; the code. That makes it tautological: true by construction, so it can never fail.</p><h2>&#9989; Write test before code</h2>
      <p>
          <a href="https://journal.optivem.com/p/spec-driven-write-test-before-code">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Spec-driven development has a Markdown problem]]></title><description><![CDATA[Stop manually checking whether AI implemented your spec correctly]]></description><link>https://journal.optivem.com/p/spec-driven-development-has-a-markdown-problem</link><guid isPermaLink="false">https://journal.optivem.com/p/spec-driven-development-has-a-markdown-problem</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Mon, 12 Oct 2026 06:02:03 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/92353ed2-fb8d-46b8-8150-e6a34eb417d0_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128197; Register (free): <strong><a href="https://www.meetup.com/techexcellence/events/316851439">Spec-Driven Development with ATDD &amp; AI</a></strong> (Oct 14)</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.meetup.com/techexcellence/events/316851439&quot;,&quot;text&quot;:&quot;Register now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.meetup.com/techexcellence/events/316851439"><span>Register now</span></a></p><div><hr></div><p>&#128075; <em>Hello, this is Valentina with the free edition of the Optivem Journal.</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>Markdown spec = enforced spec? WRONG.<br><br>Markdown = Spec. Acceptance Test = Executable Spec.<br><br>&#10060; Spec -&gt; AI writes code -&gt; Human manually tests<br><br>&#9989; Executable Spec -&gt; AI writes code -&gt; Automatic spec verification</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VIHJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f13b398-aa79-469a-896d-0cc68d35074b_1025x995.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VIHJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f13b398-aa79-469a-896d-0cc68d35074b_1025x995.png 424w, https://substackcdn.com/image/fetch/$s_!VIHJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f13b398-aa79-469a-896d-0cc68d35074b_1025x995.png 848w, https://substackcdn.com/image/fetch/$s_!VIHJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f13b398-aa79-469a-896d-0cc68d35074b_1025x995.png 1272w, https://substackcdn.com/image/fetch/$s_!VIHJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f13b398-aa79-469a-896d-0cc68d35074b_1025x995.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VIHJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f13b398-aa79-469a-896d-0cc68d35074b_1025x995.png" width="1025" height="995" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5f13b398-aa79-469a-896d-0cc68d35074b_1025x995.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:995,&quot;width&quot;:1025,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:85988,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://journal.optivem.com/i/219877641?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f13b398-aa79-469a-896d-0cc68d35074b_1025x995.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_!VIHJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f13b398-aa79-469a-896d-0cc68d35074b_1025x995.png 424w, https://substackcdn.com/image/fetch/$s_!VIHJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f13b398-aa79-469a-896d-0cc68d35074b_1025x995.png 848w, https://substackcdn.com/image/fetch/$s_!VIHJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f13b398-aa79-469a-896d-0cc68d35074b_1025x995.png 1272w, https://substackcdn.com/image/fetch/$s_!VIHJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f13b398-aa79-469a-896d-0cc68d35074b_1025x995.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>99% of developers follow Spec-Driven Development (SDD) the WRONG way.<br><br>1. They write the spec in Markdown.<br>2. They tell AI to implement code based on spec.<br><br>But what's the problem here? Sure, AI reads the markdown file, but doesn't follow it fully! It may decide to skip instructions, or misinterpret instructions, etc.<br><br>So we can NOT trust AI!<br><br>We have to manually read the spec, manually run the system, and manually decide whether the functionality that the system implemented matches what was in the spec.<br><br>Interestingly, this is not a new problem. We've had this problem for ages, with human developers. Remember, we'd write a User Story, the developer thinks they followed it, but then QA says it doesn't work correctly - that there was something that the developer missed, or they had misinterpretations.<br><br>Well, that's exactly the same problem in Spec-Driven Development, how most people practice it.<br><br>The solution is: EXECUTABLE specs. With executable specs, we can be sure to enforce that AI actually implements the behavior that we specified. We have an automated way to check - a pass or fail. This means if AI skips instructions, or misinterprets something, we'll have a test telling us it failed. AI tries again, until the test passes. There's no way for AI to "cheat" the test.<br><br>That's the right way to do Spec-Driven Development. Convert textual specs to executable specs.<br><br>&#9889;Full walkthrough, with the code examples </p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;6e1b5809-0eab-4a5b-bdd2-f79cc12eaf69&quot;,&quot;caption&quot;:&quot;I help teams adopt Spec-Driven Development, so that they can write code faster with AI agents. I have a few spots left for 2027.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Spec-Driven Development: Make Specs Enforceable with ATDD&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:27708677,&quot;name&quot;:&quot;Valentina Jemuovi&#263;&quot;,&quot;bio&quot;:&quot;Technical Coach @ Optivem | TDD | Hexagonal Architecture | Clean Architecture | I help Technical Founders to deliver software products faster by minimizing regression bugs and reducing maintenance cost&quot;,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/04f571bf-04c6-43af-b888-9bc1eb7d490a_200x200.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-09-25T15:21:52.676Z&quot;,&quot;cover_image&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/55e22a9e-b029-48c9-9d23-99ba2c8693b7_1000x666.png&quot;,&quot;cover_image_alt&quot;:null,&quot;cover_image_crop&quot;:null,&quot;canonical_url&quot;:&quot;https://journal.optivem.com/p/spec-driven-development-make-specs&quot;,&quot;section_name&quot;:&quot;ATDD&quot;,&quot;video_upload_id&quot;:null,&quot;id&quot;:217383701,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:9,&quot;comment_count&quot;:2,&quot;publication_id&quot;:824051,&quot;publication_name&quot;:&quot;Optivem Journal&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!0CjJ!,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&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div><hr></div><p><br>&#128197; Register (free): <strong><a href="https://www.meetup.com/techexcellence/events/316851439">Spec-Driven Development with ATDD &amp; AI</a></strong> (Oct 14)</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.meetup.com/techexcellence/events/316851439&quot;,&quot;text&quot;:&quot;Register now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.meetup.com/techexcellence/events/316851439"><span>Register now</span></a></p>]]></content:encoded></item><item><title><![CDATA[Clean Architecture: How to Implement the Strategy Pattern]]></title><description><![CDATA[Where the interface lives, who owns the rules, and how it's wired]]></description><link>https://journal.optivem.com/p/clean-architecture-how-to-implement-strategy-pattern</link><guid isPermaLink="false">https://journal.optivem.com/p/clean-architecture-how-to-implement-strategy-pattern</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 09 Oct 2026 06:02:54 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/e8c5f4e5-5735-4960-8e07-8c7416a6b862_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128197; Register (free): <strong><a href="https://www.meetup.com/techexcellence/events/316851439">Spec-Driven Development with ATDD &amp; AI</a></strong> (Oct 14)</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.meetup.com/techexcellence/events/316851439&quot;,&quot;text&quot;:&quot;Register now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.meetup.com/techexcellence/events/316851439"><span>Register now</span></a></p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal.</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 Strategy pattern is usually introduced with a diagram:</p><pre><code><code>Context
  &#9492;&#9472;&#9472; Strategy
        &#9500;&#9472;&#9472; Strategy A
        &#9500;&#9472;&#9472; Strategy B
        &#9492;&#9472;&#9472; Strategy C</code></code></pre><p>Useful. But in a real application, the more important question is:</p><p><strong>Where should those strategies live in Clean Architecture?</strong></p><h2>The problem</h2><p>You&#8217;re placing an order, and the shipping cost depends on where it&#8217;s going.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;5868778b-7a6d-41cd-b34f-138de7788d1f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public Money calculateShipping(Order order) {
    if (order.country().zone() == ShippingZone.DOMESTIC) {
        return Money.of(5);
    }

    return Money.of(15);
}</code></pre></div><p>That works. Then the rules grow:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;99294b5e-bbd5-44e1-9ffc-3cc6051ef38c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public Money calculateShipping(Order order) {
    var zone = order.country().zone();

    if (zone == ShippingZone.DOMESTIC) {
        return order.subtotal().isAtLeast(Money.of(50))
            ? Money.ZERO
            : Money.of(5);
    } else if (zone == ShippingZone.NORTH_AMERICA) {
        return Money.of(10).add(Money.of(1).multiply(order.itemCount()));
    } else if (zone == ShippingZone.INTERNATIONAL) {
        return Money.of(20)
            .add(Money.of(2).multiply(order.itemCount()))
            .add(Money.of(15));
    }

    throw new IllegalStateException(&#8221;Unknown zone: &#8220; + zone);
}</code></pre></div><p>Now the code that places the order knows every shipping rule in the system. Every new market means editing it. Every rate change means editing it.</p><h2>Step 1: The domain defines the interface</h2><p>Define what the business needs, not how it&#8217;s done:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;bc246708-8910-4517-97c0-fc55a9913e9b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public interface ShippingPolicy {
    Money calculate(Money subtotal, Quantity quantity);
}</code></pre></div><p>This is something the business needs: &#8220;give me the shipping cost for an order of this value and size&#8221;. So the domain owns it.</p><p>Notice the name: <code>ShippingPolicy</code>, not <code>ShippingStrategy</code>. &#8220;Strategy&#8221; is the pattern&#8217;s name; &#8220;policy&#8221; is the business&#8217;s word for it. The interface is a Strategy, but you name it in the language of the domain.</p><h2>Step 2: Each rule becomes a class</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;e9831487-2dbf-461e-8217-39f2c5fd034d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public final class DomesticShipping implements ShippingPolicy {

    private static final Money FREE_SHIPPING_THRESHOLD = Money.of(50);
    private static final Money FLAT_RATE = Money.of(5);

    @Override
    public Money calculate(Money subtotal, Quantity quantity) {
        if (subtotal.isAtLeast(FREE_SHIPPING_THRESHOLD)) {
            return Money.ZERO;
        }

        return FLAT_RATE;
    }
}</code></pre></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;1d22b3ae-7f67-4986-951a-fb19ecbd8a41&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public final class NorthAmericaShipping implements ShippingPolicy {

    private static final Money BASE = Money.of(10);
    private static final Money PER_ITEM = Money.of(1);

    @Override
    public Money calculate(Money subtotal, Quantity quantity) {
        return BASE.add(PER_ITEM.multiply(quantity.value()));
    }
}</code></pre></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;3d11fdd7-1b50-485f-b105-485530e24ccc&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public final class InternationalShipping implements ShippingPolicy {

    private static final Money BASE = Money.of(20);
    private static final Money PER_ITEM = Money.of(2);
    private static final Money CUSTOMS_HANDLING = Money.of(15);

    @Override
    public Money calculate(Money subtotal, Quantity quantity) {
        return BASE
            .add(PER_ITEM.multiply(quantity.value()))
            .add(CUSTOMS_HANDLING);
    }
}</code></pre></div><p>Each rule can change without touching the others or the use case.</p><h2>Step 3: The domain selects the policy by shipping zone</h2>
      <p>
          <a href="https://journal.optivem.com/p/clean-architecture-how-to-implement-strategy-pattern">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Developers Are Becoming Code Reviewers]]></title><description><![CDATA[You wanted AI to write code &#8212; not spend all day reviewing it]]></description><link>https://journal.optivem.com/p/developers-are-becoming-code-reviewers</link><guid isPermaLink="false">https://journal.optivem.com/p/developers-are-becoming-code-reviewers</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Tue, 06 Oct 2026 06:02:14 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/467acd4c-f847-438e-9411-129d1300c0f5_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128197; Register (free): <strong><a href="https://www.meetup.com/techexcellence/events/316851439">Spec-Driven Development with ATDD &amp; AI</a></strong> (Oct 14)</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.meetup.com/techexcellence/events/316851439&quot;,&quot;text&quot;:&quot;Register now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.meetup.com/techexcellence/events/316851439"><span>Register now</span></a></p><div><hr></div><p>&#128075; <em>Hello, this is Valentina with the free edition of the Optivem Journal.</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>Mike is a software developer.</p><p>He had been asking for better requirements for years.</p><p>Not because he loved writing documentation &#8212; because he was tired of building the wrong thing.</p><p>The product owner would create a one-line ticket. He was always busy and also hated writing)</p><p>Then, in backlog refinement:</p><p>&#8220;Everyone understands?&#8221;</p><p>Nod. Nod.</p><p>Next ticket.</p><p>And then off the developers go to implement it.</p><h2>Mike gets the blame</h2><p>QA started reporting so many bugs, so many scenarios that developers didn&#8217;t think of.</p><p>The product owner saw the feature and shook his head:</p><blockquote><p>&#8220;This isn&#8217;t what we wanted. You&#8217;ve basically implemented the wrong thing&#8221;</p></blockquote><p>Mike was frustrated. This kept happening.</p><p>He brought it up to management.</p><p>The answer?</p><blockquote><p>&#8220;We can't spend half the sprint on requirements.&#8221;</p><p>&#8220;Just start coding.&#8221;</p><p>&#8220;You&#8217;re the developer. You should figure it out.&#8221;</p></blockquote><h2>Then AI arrived</h2><p>Suddenly, specifications were a priority.</p><p>Now management was pushing Spec-Driven Development down on the whole team.</p><p>Mike&#8217;s reaction?</p><p><strong>&#8220;Wait. NOW specifications are important?&#8221;</strong></p><p>But there was another problem.</p><p>AI would skip parts of the requirements or misinterpret them.</p><p>Unlike a developer would at least try to follow the specification, not skip stuff.</p><h2>AI gives Mike more work</h2><p>Mike has to spend a lot of time like reviewing AI&#8217;s work, and management is now angry at him. They&#8217;re paying for all these AI tools, but delivery is still not fast enough.</p><p>He&#8217;s really frustrated, because now he has to do a lot of checking: did AI actually satisfy the specifications or not?</p><p>And he has to do it all manually, like manually testing things before handing it over to QA &#8212; and it&#8217;s just boring, tedious.</p><h2>Mike takes back control</h2><p>Then Mike discovered that if you do Spec-Driven Development with actually executable specifications, that problem pretty much disappears.</p><p>So instead of just giving a user story to AI, he&#8217;d have AI turn it into acceptance tests first. He&#8217;d confirm the acceptance tests before AI started coding.</p><p>And all of a sudden, he was back in control.</p><p>AI couldn&#8217;t just do whatever it wanted.</p><div><hr></div><p>Want to stop manually checking AI&#8217;s work?</p><p>&#128197; Register (free): <strong><a href="https://www.meetup.com/techexcellence/events/316851439">Spec-Driven Development with ATDD &amp; AI</a></strong> (Oct 14)</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.meetup.com/techexcellence/events/316851439&quot;,&quot;text&quot;:&quot;Register now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.meetup.com/techexcellence/events/316851439"><span>Register now</span></a></p>]]></content:encoded></item><item><title><![CDATA[Clean Architecture: Use Cases with Command Bus]]></title><description><![CDATA[Code Example]]></description><link>https://journal.optivem.com/p/clean-architecture-when-do-you-need-command-bus</link><guid isPermaLink="false">https://journal.optivem.com/p/clean-architecture-when-do-you-need-command-bus</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 02 Oct 2026 06:01:08 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/2e0be3c8-3120-4995-8e0b-f2cc5fd5c342_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128640; Register: <strong><a href="https://optivem.thinkific.com/products/courses/2026-oct-webinar-atdd">Why Your E2E Tests Didn&#8217;t Replace Manual QA</a></strong> (Oct 7)</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-oct-webinar-atdd&quot;,&quot;text&quot;:&quot;Register for free&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/courses/2026-oct-webinar-atdd"><span>Register for free</span></a></p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>You&#8217;ve split your application into one class per use case.</p><p>Then you gave every use case the same interface &#8212; the Command Handler pattern:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;79d2ddce-1619-417b-8af5-7efff8eb75e0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public interface UseCase&lt;TRequest, TResponse&gt; {
    Result&lt;TResponse, UseCaseError&gt; execute(TRequest request);
}</code></pre></div><p>One request in. One result out.</p><p>But you run into issues:</p><h3>&#10071;Problem #1: Cross-cutting concerns duplicated across use cases</h3><p>Logging, transactions, authorization &#8212; many use cases need them, so you end up copying them into each one:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;ade5d40f-738a-4676-b8b0-ccd52e45db37&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public final class PlaceOrder
        implements UseCase&lt;PlaceOrderRequest, PlaceOrderResponse&gt; {

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

    public Result&lt;CancelOrderResponse, UseCaseError&gt; execute(
        CancelOrderRequest request
    ) {
        log.info("CancelOrder started");
        authorize(Permission.CANCEL_ORDER);
        return transactional(() -&gt; {
            // ... the actual use case logic
        });
    }
}</code></pre></div><p>The same lines repeat in many use cases, and every new use case has to remember them.</p><h3>&#10071;Problem #2 (secondary): Constructors with lots of injection</h3><p>Every use case is its own class, so every controller needs each one it calls injected:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;a72705fd-6f91-4130-9468-33cf78cca590&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public OrderController(
    PlaceOrder placeOrder,
    ViewOrderDetails viewOrderDetails,
    CancelOrder cancelOrder,
    PublishCoupon publishCoupon,
    BrowseCoupons browseCoupons,
    // ... and more with every new use case
) { ... }</code></pre></div><p>And each endpoint calls its own use case directly:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;0e473989-1c16-411a-a091-467916ac0f17&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">@PostMapping("/orders")
public ResponseEntity&lt;?&gt; placeOrder(@RequestBody PlaceOrderRequest request) {
    return mapResult(placeOrder.execute(request));
}</code></pre></div><p>The constructor grows with every use case you add. The wiring becomes noise, and every controller test has to supply them all.</p><h2>&#10060; Poor Solution: Base Classes</h2><p>The tempting fix for <strong>Problem #1</strong> is a BaseUseCase that every use case extends. But as it grows with logging, authorization, transactions, and error handling, every use case inherits all of it &#8212; whether it needs it or not.</p><p>And it does nothing for <strong>Problem #2</strong>: the controller still needs every use case injected.</p><h2>&#9989; Effective Solution: Command Bus &#8212; in Practice</h2>
      <p>
          <a href="https://journal.optivem.com/p/clean-architecture-when-do-you-need-command-bus">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Why Your E2E Tests Didn't Replace Manual QA]]></title><description><![CDATA[Your team writes code faster with AI. But releases are still slow.]]></description><link>https://journal.optivem.com/p/why-your-e2e-tests-didnt-replace-manual-qa</link><guid isPermaLink="false">https://journal.optivem.com/p/why-your-e2e-tests-didnt-replace-manual-qa</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Tue, 29 Sep 2026 06:03:48 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/0c8b7ae9-0951-47be-8eac-c907c8dd6336_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128075; <em>Hello, this is Valentina with the free edition of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>Your team automated testing.</p><p>So why does every release still wait for QA?</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-oct-webinar-atdd&quot;,&quot;text&quot;:&quot;Register for free&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/courses/2026-oct-webinar-atdd"><span>Register for free</span></a></p><p>You invested in E2E test automation to get faster releases and fewer bugs.</p><p>Instead, what you got is a test suite that breaks with every UI change, a team that doesn&#8217;t trust it, and a manual regression pass that still sits between &#8220;done&#8221; and &#8220;released.&#8221;</p><p>Now AI is helping your developers write code faster, and the queue in front of QA is only getting longer.</p><p>This isn&#8217;t a people problem, and more automation of the same kind won&#8217;t fix it. It comes down to how the tests are designed.</p><h2>What You&#8217;ll Learn</h2><ul><li><p>Why E2E automation rarely replaces manual QA &#8212; and the three design decisions behind it</p></li><li><p>What changes when tests are written in business language &#8212; your product owner can read them, QA can trust them, and UI changes stop breaking them</p></li><li><p>How to start small &#8212; with one critical use case, without stopping delivery or rewriting everything</p></li><li><p>How to get it funded &#8212; by presenting it to your CEO in terms of release speed, cost of bugs and time spent on repeat work</p></li></ul><h2>Who Should Join</h2><p>This is for you if:</p><ul><li><p>You lead an engineering team (as engineering manager, head of engineering or CTO)</p></li><li><p>Releases are slowed down by manual testing</p></li><li><p>Your team tried test automation and it didn&#8217;t deliver what was promised</p></li></ul><h2>Event Details</h2><p>&#128197; <strong>Date &amp; Time:</strong> Wed, Oct 7, 2026 5:30PM<br>&#128205; <strong>Where:</strong> Live online<br>&#128176; <strong>Cost:</strong> Free</p><p>&#128279; <strong>Register here:</strong> <a href="https://optivem.thinkific.com/products/courses/2026-oct-webinar-atdd">Why Your E2E Tests Didn&#8217;t Replace Manual QA</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://optivem.thinkific.com/products/courses/2026-oct-webinar-atdd" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iWry!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png 424w, https://substackcdn.com/image/fetch/$s_!iWry!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png 848w, https://substackcdn.com/image/fetch/$s_!iWry!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png 1272w, https://substackcdn.com/image/fetch/$s_!iWry!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!iWry!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:675,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:180163,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-oct-webinar-atdd&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://journal.optivem.com/i/217951574?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!iWry!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png 424w, https://substackcdn.com/image/fetch/$s_!iWry!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png 848w, https://substackcdn.com/image/fetch/$s_!iWry!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png 1272w, https://substackcdn.com/image/fetch/$s_!iWry!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd83e5bf1-aa39-40bb-9e6c-bb7d2c975455_1200x675.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Meet Your Coach</h2><p>Valentina Jemuovi&#263; is a technical coach and the founder of Optivem. She coaches engineering teams in ATDD and TDD, helping them deliver software faster with fewer bugs. She works in Java, .NET and TypeScript, and shares her work with ~30K followers on LinkedIn.</p><h2>Coding Got Faster &#8212; Releases Are Still Slow</h2><p>If you want to eliminate manual regression testing and get faster delivery with fewer bugs&#8230;</p><p>&#128204; <strong>Register here:</strong> <a href="https://optivem.thinkific.com/products/courses/2026-oct-webinar-atdd">Why Your E2E Tests Didn&#8217;t Replace Manual QA</a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/courses/2026-oct-webinar-atdd&quot;,&quot;text&quot;:&quot;Register for free&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/courses/2026-oct-webinar-atdd"><span>Register for free</span></a></p>]]></content:encoded></item><item><title><![CDATA[Spec-Driven Development: Make Specs Enforceable with ATDD]]></title><description><![CDATA[AI can ignore your spec &#8212; it can&#8217;t ignore a failing test.]]></description><link>https://journal.optivem.com/p/spec-driven-development-make-specs</link><guid isPermaLink="false">https://journal.optivem.com/p/spec-driven-development-make-specs</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 25 Sep 2026 15:21:52 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/55e22a9e-b029-48c9-9d23-99ba2c8693b7_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>I help teams adopt Spec-Driven Development, so that they can write code faster with AI agents. I have a few spots left for 2027.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://calendly.com/valentinajemuovic/call&quot;,&quot;text&quot;:&quot;Book a call&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://calendly.com/valentinajemuovic/call"><span>Book a call</span></a></p><p>&#8220;Spec-driven development&#8221; is everywhere in AI tooling right now: Kiro, GitHub&#8217;s spec-kit, Tessl. Write the spec first, then let the agent build against it. Birgitta B&#246;ckeler reviewed all three in <a href="https://martinfowler.com/articles/exploring-gen-ai/sdd-3-tools.html">Understanding Spec-Driven-Development: Kiro, spec-kit, and Tessl</a> on martinfowler.com and found the catch.</p><p>In every one of them, &#8220;spec&#8221; means a Markdown document the agent is supposed to read. <strong>Nothing checks that the code matches it, and the agents don&#8217;t always follow it.</strong> As she put it: &#8220;I frequently saw the agent ultimately not follow all the instructions.&#8221;</p><p>The problem isn&#8217;t the tools. It&#8217;s that the spec is a Markdown file, and <strong>you can&#8217;t run a Markdown file against the code.</strong> To find out whether the agent followed it, a person has to read the code line by line. That&#8217;s the work you hoped the spec would save.</p><p>We&#8217;ve had a spec without this problem since 1999. Kent Beck&#8217;s first Extreme Programming book, <a href="https://dl.acm.org/doi/abs/10.5555/318762">Extreme Programming Explained</a> (1999), already had acceptance tests, and the <a href="http://www.extremeprogramming.org/rules/functionaltests.html">XP rules</a> on extremeprogramming.org spell out how they work: acceptance tests &#8220;are created from user stories&#8221; and &#8220;should be automated so they can be run often.&#8221;</p><p>In 2010, Jez Humble and Dave Farley&#8217;s <a href="https://www.amazon.com/dp/B003YMNVC0">Continuous Delivery</a> gave automated acceptance tests their own chapter: &#8220;Acceptance tests are a crucial stage in the deployment pipeline: They take delivery teams beyond basic continuous integration. Once you have automated acceptance tests in place, you are testing the business acceptance criteria of your application, that is, validating that it provides users with valuable functionality.&#8221;</p><p>An acceptance test is the spec and the check in one: it describes what the system should do, and it fails until the system does it.</p><p><strong>An agent can ignore a Markdown file.</strong></p><p><strong>It can&#8217;t ignore a test that won&#8217;t pass.</strong></p><p>An acceptance test doesn&#8217;t call the system directly. It calls a DSL, and the DSL calls two kinds of drivers. System drivers talk to the system through its UI or API. External-system drivers talk to the external systems it depends on, such as an ERP.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ol6t!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ol6t!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png 424w, https://substackcdn.com/image/fetch/$s_!ol6t!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png 848w, https://substackcdn.com/image/fetch/$s_!ol6t!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png 1272w, https://substackcdn.com/image/fetch/$s_!ol6t!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!ol6t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png" width="602" height="608.2019230769231" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1471,&quot;width&quot;:1456,&quot;resizeWidth&quot;:602,&quot;bytes&quot;:145963,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://journal.optivem.com/i/217383701?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ol6t!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png 424w, https://substackcdn.com/image/fetch/$s_!ol6t!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png 848w, https://substackcdn.com/image/fetch/$s_!ol6t!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png 1272w, https://substackcdn.com/image/fetch/$s_!ol6t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3640883-04bc-47c3-9204-41e9f9eeccd4_1760x1778.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>In acceptance test-driven development (ATDD), the Red step is where you write that test, before any system code exists. When an AI agent does the work, the Red step is split into four small steps, and a human reviews each one.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-OUs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-OUs!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png 424w, https://substackcdn.com/image/fetch/$s_!-OUs!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png 848w, https://substackcdn.com/image/fetch/$s_!-OUs!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png 1272w, https://substackcdn.com/image/fetch/$s_!-OUs!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!-OUs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png" width="602" height="1147.3557692307693" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2775,&quot;width&quot;:1456,&quot;resizeWidth&quot;:602,&quot;bytes&quot;:275044,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://journal.optivem.com/i/217383701?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-OUs!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png 424w, https://substackcdn.com/image/fetch/$s_!-OUs!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png 848w, https://substackcdn.com/image/fetch/$s_!-OUs!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png 1272w, https://substackcdn.com/image/fetch/$s_!-OUs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc95d581b-fab0-4902-a97a-edddd337345f_1760x3354.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In every box, the AI writes the code, a human reviews it, and then it&#8217;s committed.</p><h2>Before Red: the acceptance criteria</h2><p>Example: placing an order in an online shop.</p><p>It starts with a ticket. Its acceptance criteria are agreed before any code is written:</p><pre><code>Scenario: Calculate the base price from unit price and quantity
  Given a product with unit price 20.00
  When I place an order for 5 of that product
  Then the order is placed successfully
  And the order&#8217;s base price is 100.00</code></pre><h2>Inside the Red step</h2><p><strong>1. Write Acceptance Tests</strong></p><p>The agent turns the acceptance criteria into a test. The test is written in a DSL, the test code&#8217;s own vocabulary (<code>placeOrder().withQuantity(...)</code>). Here&#8217;s the acceptance test, from <code>PlaceOrderPositiveTest.java</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;e8dd1e8a-3469-4dc3-9b7d-ce0e682d802c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">@TestTemplate
@Channel({ChannelType.UI, ChannelType.API})
void shouldCalculateBasePriceAsProductOfUnitPriceAndQuantity() {
    scenario
            .given().product()
                .withUnitPrice(20.00)
            .when().placeOrder()
                .withQuantity(5)
            .then().shouldSucceed()
            .and().order()
                .hasBasePrice(100.00);
}</code></pre></div>
      <p>
          <a href="https://journal.optivem.com/p/spec-driven-development-make-specs">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[DDD: Stop Making Your Domain a Data Bag]]></title><description><![CDATA[Your domain needs more than getters]]></description><link>https://journal.optivem.com/p/ddd-stop-making-your-domain-a-data-bag</link><guid isPermaLink="false">https://journal.optivem.com/p/ddd-stop-making-your-domain-a-data-bag</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Fri, 18 Sep 2026 06:00:53 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d0cdf30b-791f-44da-8efc-12dbced5ef9e_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>ATDD in Legacy Code: catch regression bugs before production.</strong><br>Hands-on work with your teams. Limited spaces for 2027.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://calendly.com/valentinajemuovic/call&quot;,&quot;text&quot;:&quot;Let's talk&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://calendly.com/valentinajemuovic/call"><span>Let's talk</span></a></p><div><hr></div><p>&#128197; Join our next live course: <strong><a href="https://optivem.thinkific.com/products/courses/2026-oct-atdd">ATDD</a>, <a href="https://optivem.thinkific.com/products/courses/2026-oct-ca">Clean Architecture</a>, <a href="https://optivem.thinkific.com/products/courses/2026-oct-pipelines">Pipelines</a><br></strong>Early bird: <strong>&#8364;100 off with code EARLYBIRD100</strong></p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>Your SubscriptionService has 800 lines.</p><p>It activates subscriptions, handles cancellations, validates plan changes, checks expiration...</p><p>Meanwhile <code>Subscription</code> is just getters and setters.</p><p>That&#8217;s the problem.</p><h2><strong>&#10060;</strong>Your Domain Is Just Data (Anemic Domain)</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;0c00cd6a-ac8a-4139-ac7e-836fc8e49777&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public class Subscription {
    private String customerId;
    private SubscriptionPlan plan;
    private SubscriptionStatus status;
    private LocalDate startedAt;
    private LocalDate expiresAt;
    private BigDecimal price;

    public String getCustomerId() {
        return customerId;
    }

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

    public SubscriptionPlan getPlan() { ... }

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

    public SubscriptionStatus getStatus() { ... }

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

    public LocalDate getStartedAt() { ... }

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

    public LocalDate getExpiresAt() { ... }

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

    public BigDecimal getPrice() { ... }

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

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

        subscription.setStatus(SubscriptionStatus.CANCELLED);
    }

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

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

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

        subscription.setExpiresAt(
            subscription.getExpiresAt().plusMonths(1)
        );
    }
}</code></pre></div><p>At first, this doesn&#8217;t look terrible.</p><p>But keep adding business rules.</p><p>Soon you have:</p><pre><code><code>SubscriptionService
&#9500;&#9472;&#9472; startTrial()
&#9500;&#9472;&#9472; cancel()
&#9500;&#9472;&#9472; changePlan()
&#9500;&#9472;&#9472; renew()
&#9500;&#9472;&#9472; pause()
&#9500;&#9472;&#9472; resume()
&#9500;&#9472;&#9472; extendTrial()
&#9500;&#9472;&#9472; changeBillingCycle()
&#9500;&#9472;&#9472; updatePaymentMethod()
&#9492;&#9472;&#9472; ...</code></code></pre><p>And your <code>Subscription</code> is still just data.</p><p>That&#8217;s where the real problems appear:</p><ul><li><p><strong>Rules get scattered</strong> across services</p></li><li><p><strong>The same rules get duplicated</strong> in different places</p></li><li><p><strong>Rules can be bypassed</strong> by calling setters directly</p></li><li><p><strong>Changing a business rule means hunting through services</strong> to find everywhere it is implemented</p></li><li><p><strong>The Subscription object can&#8217;t protect its own state</strong></p></li></ul><h2>&#9989;Your Domain Owns the Rules (Rich Domain)</h2>
      <p>
          <a href="https://journal.optivem.com/p/ddd-stop-making-your-domain-a-data-bag">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Slow releases. Production bugs. And you already automated.]]></title><description><![CDATA[You automated, and manual regression testing is still there. Those two facts have the same cause.]]></description><link>https://journal.optivem.com/p/slow-releases-production-bugs</link><guid isPermaLink="false">https://journal.optivem.com/p/slow-releases-production-bugs</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Wed, 16 Sep 2026 07:33:58 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/12031a27-f1a8-4135-82ae-da60813a8961_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>ATDD in Legacy Code: catch regression bugs before production.</strong><br>Hands-on work with your teams. Limited spaces for 2027.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://calendly.com/valentinajemuovic/call&quot;,&quot;text&quot;:&quot;Let's talk&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://calendly.com/valentinajemuovic/call"><span>Let's talk</span></a></p><div><hr></div><p><span>&#128075; </span><em>Hello, this is Valentina with the free edition of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>When there are hundreds of things to check, QA can't check everything.</p><blockquote><p>&#8220;That feature hasn&#8217;t changed.&#8221;</p><p>&#8220;Let&#8217;s just test the critical flows.&#8221;</p></blockquote><p>Some tests get skipped.</p><p>That&#8217;s when production bugs slip through.</p><h2>&#10060; &#8220;Just hire more QA&#8221;</h2><p>But every release adds more features.</p><p>More features = more things to regression test.</p><p>QA can&#8217;t keep up.</p><h2>&#10060; &#8220;But we already automated!&#8221;</h2><p><strong>You tried e2e tests.</strong></p><p>Run it &#8594; red.</p><p>Run it again &#8594; green.</p><p>Eventually, nobody trusts the tests.</p><p>Developers start ignoring them.</p><p><strong>You tried unit tests.</strong></p><p>They're green.</p><p>But something still breaks in production.</p><p>Why?</p><h2>Does Checkout Work?</h2><p>Checkout has these requirements:</p><ul><li><p>Customer can add a product to the cart.</p></li><li><p>Customer can pay by card.</p></li><li><p>Order is created after payment.</p></li><li><p>Customer can&#8217;t order an out-of-stock product.</p></li></ul><h3>&#9888;&#65039;Unit tests</h3><p>Test individual pieces, but not the whole flow.</p><blockquote><p><code>CartService</code> adds a product.</p><p><code>PaymentService</code> charges the card.</p><p><code>OrderService</code> creates the order.</p></blockquote><p>They can all pass while the <strong>checkout flow still breaks.</strong></p><h3>&#9888;&#65039;E2E test</h3><p>It tests the whole flow.</p><p>But the behaviour is buried inside UI steps.</p><blockquote><p>Open checkout &#8594; add product &#8594; enter card &#8594; click Pay &#8594; see &#8220;Order confirmed&#8221;</p></blockquote><p>So <strong>what is this test actually checking?</strong></p><blockquote><p>Can the customer pay by card?</p><p>Is an order created?</p><p>Is the cart emptied?</p><p>Does the confirmation page appear?</p></blockquote><p>You have to read the steps and figure it out.</p><p>Now imagine 200 tests like that.</p><p><strong>You can&#8217;t easily tell:</strong></p><blockquote><p>&#8220;Which test proves that an out-of-stock product can&#8217;t be ordered?&#8221;</p><p>&#8220;Do we have a test for refunds?&#8221;</p><p>&#8220;Do we test that an order is only created after successful payment?&#8221;</p></blockquote><h3>&#9989; Acceptance test</h3><p>The behavior being checked is obvious.</p><blockquote><p>Given a product is out of stock<br>When a customer tries to order it<br>Then the order is rejected</p></blockquote><p>The requirement and the test are the same.</p><p><strong>You can see exactly what behavior you&#8217;re checking.</strong></p><p>And because the test isn't tied to the UI, changing the checkout screen doesn't mean rewriting the test.</p><div><hr></div><p><strong>ATDD in Legacy Code: catch regression bugs before production.</strong><br><span>Hands-on work with your teams. Limited spaces for 2027.</span></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://calendly.com/valentinajemuovic/call&quot;,&quot;text&quot;:&quot;Let's talk&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://calendly.com/valentinajemuovic/call"><span>Let's talk</span></a></p>]]></content:encoded></item><item><title><![CDATA[Clean Architecture: One Class Per Use Case Is NOT Enough]]></title><description><![CDATA[Code Example]]></description><link>https://journal.optivem.com/p/clean-architecture-one-class-per-use-case-is-not-enough</link><guid isPermaLink="false">https://journal.optivem.com/p/clean-architecture-one-class-per-use-case-is-not-enough</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 10 Sep 2026 06:01:29 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d70a3b4c-f61a-453c-9f44-26a6dfc6378b_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>ATDD in Legacy Code: catch regression bugs before production.</strong><br>Hands-on work with your teams. Limited spaces for 2027.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://calendly.com/valentinajemuovic/call&quot;,&quot;text&quot;:&quot;Let's talk&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://calendly.com/valentinajemuovic/call"><span>Let's talk</span></a></p><div><hr></div><p>&#128197; Join our next live course: <strong><a href="https://optivem.thinkific.com/products/courses/2026-oct-atdd">ATDD</a>, <a href="https://optivem.thinkific.com/products/courses/2026-oct-ca">Clean Architecture</a>, <a href="https://optivem.thinkific.com/products/courses/2026-oct-pipelines">Pipelines</a><br></strong>Early bird: <strong>&#8364;100 off with code EARLYBIRD100</strong></p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>You already know a fat service class is a bad place for all your business logic.</p><p>So you split it up.</p><p>One class per use case, like in<span> </span><a href="https://github.com/sandromancuso/cleancoders_openchat/tree/openchat-unclebob/src/main/java/org/openchat/usecases">Uncle Bob's example</a><span>:</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;20f8f782-68e0-4ba8-899f-d11ba615db39&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public class PlaceOrder {
    public PlaceOrderResponse placeOrder(PlaceOrderRequest request) { ... }
}

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

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

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

public class BrowseCoupons {
    public List&lt;Coupon&gt; getAllCoupons() { ... }
}</code></pre></div><p>Looks better.</p><p>But splitting the service into separate classes didn&#8217;t fix everything.</p><p>Here&#8217;s what&#8217;s still wrong:</p><h3>&#10060; Entities leak across the boundary &#8212; but only sometimes</h3><p><code>PublishCoupon</code> returns a <code>Coupon</code>.</p><p><code>BrowseCoupons</code> returns <code>List&lt;Coupon&gt;</code>.</p><p>But <code>PlaceOrder</code> and <code>ViewOrderDetails</code> return response DTOs.</p><p>So... can entities cross the use case boundary or not?</p><p>You can&#8217;t tell. It depends on which class you&#8217;re looking at.</p><h3>&#10060; A bug is hiding in PublishCoupon</h3><p>If you accidently swap <code>validFrom</code> and <code>validTo</code> &#8212; it still compiles, because they&#8217;re the same type.</p><p>But now your coupon has the wrong validity period &#8212; it&#8217;s backwards.</p><p><code>CouponController</code> already has a <code>PublishCouponRequest</code> with all the values. Instead of passing that request to <code>PublishCoupon</code>, it pulls the values out and passes them one by one.</p><p>That's how you end up with a bug like this.</p><h3>&#10060; Expected outcomes are thrown, not declared</h3><p>Order not found, order can&#8217;t be delivered &#8212; are thrown as exceptions instead of declared.</p><p>These aren't bugs. They're things that can happen when the use case runs.</p><p>But they&#8217;re in a shared exception handler that has grown a case for every use case's failures.</p><p>The method says &#8220;I return an order.&#8221; It doesn't tell you that &#8220;order not found&#8221; or &#8220;order can't be delivered&#8221; can happen.</p><h3>&#10060; Calling each use case is different</h3><p><code>PlaceOrder</code> takes a request object, <code>ViewOrderDetails</code>/<code>CancelOrder</code> take a raw <code>String</code>, <code>PublishCoupon</code> takes five loose parameters, <code>BrowseCoupons</code> takes nothing.</p><p>There&#8217;s no obvious answer to: &#8220;How do I call a use case?&#8221;</p><h3>&#10060; The class says one thing, the method says another</h3><p>ViewOrderDetails.getOrder()</p><p>The class says &#8220;view the order details&#8221;, but the method says &#8220;get the order.&#8221;</p><p>Same problem here: <code>PublishCoupon.createCoupon()</code></p><p>The class says &#8220;publish a coupon&#8221;, but the method says &#8220;create a coupon&#8221;.</p>
      <p>
          <a href="https://journal.optivem.com/p/clean-architecture-one-class-per-use-case-is-not-enough">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Refactoring Should NOT Touch Your Acceptance Tests]]></title><description><![CDATA[A &#8220;refactor&#8221; PR that changes the acceptance test or the DSL isn't a refactor &#8212; it's a behavioral change]]></description><link>https://journal.optivem.com/p/refactoring-should-not-touch-your-acceptance-tests</link><guid isPermaLink="false">https://journal.optivem.com/p/refactoring-should-not-touch-your-acceptance-tests</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 03 Sep 2026 06:00:37 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/e53e08ea-33ea-4d0b-930c-cf47d5ae8edf_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><span>&#128197; Join our next live course: </span><strong><a href="https://optivem.thinkific.com/products/courses/2026-oct-atdd"><span>ATDD</span></a><span>, </span><a href="https://optivem.thinkific.com/products/courses/2026-oct-ca"><span>Clean Architecture</span></a><span>, </span><a href="https://optivem.thinkific.com/products/courses/2026-oct-pipelines"><span>Pipelines</span></a><span><br></span></strong><span>Early bird: </span><strong>&#8364;100 off with code EARLYBIRD100</strong></p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>You open a PR labelled:</p><blockquote><p>&#8220;API redesign &#8212; no behavior change&#8221;</p></blockquote><p>Looks fine.</p><p>Merged.</p><p>The one-line acceptance test change goes unnoticed.</p><p>Six months later, a regression bug slips through.</p><p>The acceptance test that was supposed to catch it didn&#8217;t.</p><p>Why?</p><p>Because the test was quietly changed at the same time as the code.</p><p><strong>That&#8217;s the problem.</strong></p><p>If the behavior didn't change, the acceptance test shouldn't need to change.</p><p>The driver should handle it.</p><h2>&#9989;The test stays the same &#8212; the driver changes</h2><p>Look at <code>PlaceOrderPositiveTest.java</code> in the shop&#8217;s <code>system-test</code> module:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;c4f330db-8b1e-4443-9670-00731a7d11f5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">@TestTemplate
@Channel({ChannelType.UI, ChannelType.API})
void shouldCalculateBasePriceAsProductOfUnitPriceAndQuantity() {
    scenario
            .given().product()
                .withUnitPrice(20.00)
            .when().placeOrder()
                .withQuantity(5)
            .then().shouldSucceed()
            .and().order()
                .hasBasePrice(100.00);
}</code></pre></div><p>This test has no idea whether the order is placed by <code>POST /api/orders</code> or <code>POST /api/purchases</code>.</p><p>It has no idea whether the UI&#8217;s place-order button is selected by <code>[aria-label="Place Order"]</code> or by some new CSS class.</p><p>It also has no idea whether the ERP returns the product&#8217;s price as <code>price</code> or <code>unitPrice</code>.</p>
      <p>
          <a href="https://journal.optivem.com/p/refactoring-should-not-touch-your-acceptance-tests">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Don't Let AI Multiply Bad Tests]]></title><description><![CDATA[AI learns from the tests you already have. If those are bad, you now get bad tests much faster.]]></description><link>https://journal.optivem.com/p/dont-let-ai-multiply-bad-tests</link><guid isPermaLink="false">https://journal.optivem.com/p/dont-let-ai-multiply-bad-tests</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Tue, 01 Sep 2026 08:22:21 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/a4619885-fb6d-424e-8fd7-2fc25f1c9bbd_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>ATDD in Legacy Code: catch regression bugs before production &#8212; in banking, insurance, and healthcare systems.</strong><br>Hands-on work with your teams. Limited spaces for 2027. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://calendly.com/valentinajemuovic/call&quot;,&quot;text&quot;:&quot;Let's talk&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://calendly.com/valentinajemuovic/call"><span>Let's talk</span></a></p><div><hr></div><p>&#128197; Join our next live courses: <strong><a href="https://optivem.thinkific.com/products/courses/2026-oct-atdd">ATDD</a>, <a href="https://optivem.thinkific.com/products/courses/2026-oct-ca">Clean Architecture</a>, <a href="https://optivem.thinkific.com/products/courses/2026-oct-pipelines">Pipelines</a><br></strong>Early bird: <strong>&#8364;100 off with code EARLYBIRD100</strong></p><div><hr></div><p><span>&#128075; </span><em>Hello, this is Valentina with the free edition of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><blockquote><p>&#8220;AI can write the tests.&#8221;</p></blockquote><p>Finally.</p><p>All those tests nobody has time to write.</p><p>No more days spent waiting for manual QA before every release.</p><p><strong>Six months later&#8230;</strong></p><p>More code.</p><p>More PRS.</p><p><strong>But releases aren&#8217;t faster.</strong></p><p>QA is still testing everything manually before anything ships &#8212; exactly as they were last year.</p><p>And the bugs? Still there.</p><p>Now management wants to know:</p><p><strong>What did we actually get for the money?</strong></p><p>You have plenty of numbers to show them.</p><p>More code.<br>More PRs.<br>More tests.</p><p>But none of those numbers answer the question.</p><p>Because more code isn't the same as safer releases.</p><h2>AI copies whatever it finds</h2><p>Ask AI to write a test.</p><p><strong>But what if your e2e suite is full of tests coupled to the UI?</strong></p><p>Click the button.<br>Fill the field.<br>Check the screen.</p><p><strong>AI just writes hundreds more tests like that.</strong></p><p>Faster.</p><p>It doesn't stop and ask:</p><blockquote><p>&#8220;Is this a good way to test this application?&#8221;</p></blockquote><p>It looks at the tests you already have and learns:</p><blockquote><p>&#8220;This is how tests are written here.&#8221;</p></blockquote><h2>You had a speed limit</h2><p>Before AI, writing tests was slow.</p><p>That was annoying.</p><p>But it also limited how many bad tests you could create.</p><p>Your team could only write so many brittle E2E tests before the sprint ended.</p><p><strong>AI removed that limit.</strong></p><p>Now you can write hundreds in the time it used to take to write a few.</p><p>But the tests aren&#8217;t any better.</p><p>They&#8217;re still coupled to the UI.</p><p>Still break when the UI changes.</p><p>Still create more work for the team.</p><h2>Before AI writes 100 more tests</h2><p>AI writing tests isn&#8217;t the problem.</p><p>AI writing bad tests faster is.</p><p>The problem is giving it a bad test suite to learn from.</p><p>&#10060; If your tests are all coupled to the UI, AI will keep generating tests like:</p><p><code>click("Place Order")</code></p><p><strong>&#9989; Instead, fix the test, so that it's coupled to the domain rather than the UI:</strong></p><p><code>shop.placeOrder()</code></p><p>Now AI can do what it&#8217;s good at:</p><p><strong>Write hundreds more. Fast.</strong></p><div><hr></div><h3>&#9889;Join our next live courses</h3><ul><li><p><strong><a href="https://optivem.thinkific.com/products/courses/2026-oct-atdd">ATDD: Stop Shipping Bugs</a></strong></p></li><li><p><strong><a href="https://optivem.thinkific.com/products/courses/2026-oct-ca">Clean Architecture: In Practice</a></strong></p></li><li><p><strong><a href="https://optivem.thinkific.com/products/courses/2026-oct-pipelines">Pipelines: Stop Release Nightmares</a></strong></p></li></ul><p>Early bird: <strong>&#8364;100 off with code EARLYBIRD100</strong></p><div><hr></div><p><strong>ATDD in Legacy Code: catch regression bugs before production &#8212; in banking, insurance, and healthcare systems.</strong><br>Hands-on work with your teams. Limited spaces for 2027.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://calendly.com/valentinajemuovic/call&quot;,&quot;text&quot;:&quot;Let's talk&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://calendly.com/valentinajemuovic/call"><span>Let's talk</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Clean Architecture: Use Cases Should NOT Catch Stripe Exceptions]]></title><description><![CDATA[Error Handling - Application layer]]></description><link>https://journal.optivem.com/p/clean-architecture-use-cases-should-not-catch-stripe-exceptions</link><guid isPermaLink="false">https://journal.optivem.com/p/clean-architecture-use-cases-should-not-catch-stripe-exceptions</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 27 Aug 2026 06:01:02 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/ffd1a862-8f06-4cdd-b6bf-98e116e8ed6d_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>ATDD in Legacy Code: catch regression bugs before production.</strong><br>Hands-on work with your teams. Limited spaces for 2027. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://calendly.com/valentinajemuovic/call&quot;,&quot;text&quot;:&quot;Let&#8217;s talk &#8594;&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://calendly.com/valentinajemuovic/call"><span>Let&#8217;s talk &#8594;</span></a></p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><blockquote><p>&#8220;What should I do when a use case fails?&#8221;</p></blockquote><p>But a use case can fail for completely different reasons.</p><p>Maybe:</p><ul><li><p>the customer doesn&#8217;t exist</p></li><li><p>the order hasn&#8217;t been paid for</p></li><li><p>the order cannot be checked out</p></li><li><p>the payment provider is unavailable</p></li></ul><p>Should the use case handle all of them?</p><h2>Error Handling at the Application layer</h2><p>This layer contains your use cases:</p><ul><li><p>checkout order</p></li><li><p>refund payment</p></li><li><p>create shipment</p></li><li><p>reserve seat</p></li></ul><p>This is where application rules are enforced.</p><p>Examples:</p><ul><li><p>cannot checkout if another checkout is already in progress</p></li><li><p>cannot place an order for a customer that doesn&#8217;t exist</p></li><li><p>cannot apply the same coupon twice</p></li></ul><p>These are rules that the use case needs to check because because the information isn't inside the <code>Order</code> itself.</p><p>E.g. an <code>Order</code> can&#8217;t know whether the customer exists or another checkout is already in progress, or whether a coupon had already been used. The use case can check that information.</p><p>These are not technical failures. A database connection dropping is a completely different kind of problem.</p><h2>&#10060; Use cases should NOT handle infrastructure exceptions</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;d56c13cd-735d-40ef-a4f4-ae4ac43d75c5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public void checkout(Order order) {

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

    } catch (StripeException e) {
        throw new RuntimeException("Payment failed");
    } catch (SQLException e) {
        throw new RuntimeException("Database error");
    } catch (HttpException e) {
        throw new RuntimeException("Warehouse call failed");
    }
}</code></pre></div><p>Now the use case is tied to three unrelated technologies:</p><ul><li><p><code>StripeException</code> &#8212; Stripe&#8217;s SDK</p></li><li><p><code>SQLException</code> &#8212; JDBC / the database</p></li><li><p><code>HttpException</code> &#8212; the HTTP client</p></li></ul><p>Swap Stripe for PayPal, JDBC for an ORM, or the HTTP client for a message queue, and you now have to change the use case too.</p><p><strong>But the checkout rules haven&#8217;t changed.</strong></p><p>That&#8217;s the problem.</p><div><hr></div><h2>&#9989; Use cases should ONLY handle application errors</h2>
      <p>
          <a href="https://journal.optivem.com/p/clean-architecture-use-cases-should-not-catch-stripe-exceptions">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Senior Engineer ≠ Years of Experience]]></title><description><![CDATA[&#8220;I've been doing this for 15 years, so I already know.&#8221;]]></description><link>https://journal.optivem.com/p/senior-engineer-years-of-experience</link><guid isPermaLink="false">https://journal.optivem.com/p/senior-engineer-years-of-experience</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Mon, 24 Aug 2026 06:01:00 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/9edc6505-bd88-4093-baf1-1a1d8348f025_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128197; Join me: <strong><a href="https://optivem.thinkific.com/products/live_events/clean-architecture-for-backend-developers">Clean Architecture for Backend Developers</a></strong> on Wed 26th Aug, 5:00 - 6:30 PM (CEST)</p><div><hr></div><p><span>&#128075; </span><em><span>Hello, this is Valentina with the free edition of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply </span><a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a><span>.</span></em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>Someone can spend 20 years getting better at software engineering.</p><p>Or they can spend 20 years repeating the same mistakes.</p><h2>The &#8220;I&#8217;ve Been Doing This for 20 Years&#8221; Developer</h2><p>There&#8217;s a particular kind of senior engineer who is difficult to work with.</p><p>They&#8217;ve been doing this for a long time.<br>They&#8217;ve seen everything.<br>They have an answer for everything.</p><p>And every new problem gets solved with something they&#8217;ve already done before.</p><p>You suggest a different approach.<br>They tell you why it won&#8217;t work.</p><p>You show them an example.<br>They explain why that example is different.</p><p>You point out a problem with their approach.<br>They tell you they&#8217;ve been doing this for 20 years.</p><p>And that&#8217;s supposed to end the discussion.</p><h2>Experience Doesn&#8217;t Fix Bad Habits</h2><p>It makes them harder to see.</p><p>A developer who has spent 10 years writing huge classes doesn&#8217;t necessarily become better at design.</p><p>They might simply become extremely fast at writing huge classes.</p><p>A developer who has spent 10 years avoiding tests doesn&#8217;t become good at testing.</p><p>They become very experienced at working without tests.</p><p>And someone who has spent years solving every problem with another layer, abstraction, framework, or design pattern can become incredibly experienced at overengineering.</p><p>Experience makes you better at whatever you repeatedly practice.</p><p><strong>Including the wrong things.</strong></p><h2>Stop Counting Years</h2><p>Years of experience are a terrible measurement of engineering ability.</p><p>A developer with three years of experience who constantly learns, experiments, gets feedback, and improves their approach can be far more effective than someone with fifteen years who stopped learning five years ago.</p><p>I&#8217;ve seen developers with relatively little experience who can walk into an unfamiliar codebase and start asking the right questions.</p><p>And I&#8217;ve seen extremely experienced developers who can&#8217;t change their approach even when it&#8217;s clearly not working.</p><h2>Good Engineers Say &#8220;I Was Wrong&#8221;</h2><p>The best engineers can look at their own code and say:</p><blockquote><p>&#8220;I wouldn&#8217;t write this that way today.&#8221;</p></blockquote><p>Years of experience can tell you how long someone has been in the game.</p><p>It doesn&#8217;t tell you how well they&#8217;ve played it.</p><p>Getting better means being willing to rethink how you&#8217;ve been doing things.</p><p>Join the live session:</p><p><strong><a href="https://optivem.thinkific.com/products/live_events/clean-architecture-for-backend-developers">Clean Architecture for Backend Developers</a></strong></p><p>&#128467; Aug 26<br>&#9200; 5:00&#8211;6:30 PM (CEST)</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://optivem.thinkific.com/products/live_events/clean-architecture-for-backend-developers&quot;,&quot;text&quot;:&quot;&#127942;Register now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://optivem.thinkific.com/products/live_events/clean-architecture-for-backend-developers"><span>&#127942;Register now</span></a></p>]]></content:encoded></item><item><title><![CDATA[Hexagonal Architecture: Don't Follow the Diagram]]></title><description><![CDATA[Don't create a class just because the Hexagonal Architecture diagram has one]]></description><link>https://journal.optivem.com/p/hexagonal-architecture-dont-follow-the-diagram</link><guid isPermaLink="false">https://journal.optivem.com/p/hexagonal-architecture-dont-follow-the-diagram</guid><dc:creator><![CDATA[Valentina Jemuović]]></dc:creator><pubDate>Thu, 20 Aug 2026 06:01:57 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/8955446c-045c-4fb0-a60d-3f61a6e7b4d2_1000x666.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128197; Join me: <strong><a href="https://optivem.thinkific.com/products/live_events/clean-architecture-for-backend-developers">Clean Architecture for Backend Developers</a></strong> on Wed 26th Aug, 5:00 - 6:30 PM (CEST)</p><div><hr></div><p><em>&#128274; Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders &amp; Senior Software Developers apply <a href="https://journal.optivem.com/p/tdd-in-legacy-code-transformation">TDD in Legacy Code</a>.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://journal.optivem.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://journal.optivem.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>The first time you see Hexagonal Architecture, it can feel like a lot.</p><p><strong>Schedule a shipment.</strong></p><p>You're used to something like: <code>ShipmentHttpController</code>, talking straight to the ORM and the FedEx API. One class, two integrations.</p><p>Then you look at the Hexagonal Architecture version of the same feature:</p><ul><li><p>driving adapter (<code>ShipmentHttpController</code>)</p></li><li><p>driving port (<code>ScheduleShipmentUseCase</code>)</p></li><li><p>the hexagon itself (<code>Shipment</code>, <code>Address</code>, <code>PackageDetails</code>, <code>ShipmentRepository</code>, <code>CourierGateway</code>)</p></li><li><p>driven adapters (<code>SqlShipmentRepository</code>, <code>FedExCourierGateway</code>).</p></li></ul><p>And here&#8217;s where things can get out of hand.</p><p><strong>You start creating all of these classes before you actually need them.</strong></p><h2>The Starting Point</h2><p><strong>&#128683; Problem: No pain yet</strong></p><p>One feature, one caller, one integration on each side.</p><p><strong>&#9989; Solution: Ship it as a single class</strong></p><p>There&#8217;s nothing to abstract until something actually hurts.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZnVO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZnVO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png 424w, https://substackcdn.com/image/fetch/$s_!ZnVO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png 848w, https://substackcdn.com/image/fetch/$s_!ZnVO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png 1272w, https://substackcdn.com/image/fetch/$s_!ZnVO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png 1456w" sizes="auto, 100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZnVO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png" width="1180" height="772" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:772,&quot;width&quot;:1180,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:74274,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://journal.optivem.com/i/211436844?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ZnVO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png 424w, https://substackcdn.com/image/fetch/$s_!ZnVO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png 848w, https://substackcdn.com/image/fetch/$s_!ZnVO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png 1272w, https://substackcdn.com/image/fetch/$s_!ZnVO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9483b22-4cb6-40f4-8709-50f69efa4963_1180x772.png 1456w" sizes="auto, 100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image buttonBase-GK1x3M"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon-noB79L"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image buttonBase-GK1x3M"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 icon-noB79L"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>One class handles the HTTP request, runs the validation, talks to Postgres through JPA, and calls the FedEx SDK directly. No ports, no adapters, no domain model.</p><p>This isn't the design we'd want to keep forever: it&#8217;s slow to test and it&#8217;s going to get worse as more logic lands in it &#8212; but it hasn&#8217;t gotten worse yet.</p><h2>Step 1 &#8212; Driven Ports Keep I/O Out of the Way</h2><p><strong>&#128683; Problem: Tests are slow</strong></p><p>Every test hits a real Postgres instance and the real FedEx test instance, so they&#8217;re slow, and the test instance goes down more often than the code changes.</p><p><strong>&#9989; Solution: Use driven ports for I/O</strong></p>
      <p>
          <a href="https://journal.optivem.com/p/hexagonal-architecture-dont-follow-the-diagram">
              Read more
          </a>
      </p>
   ]]></content:encoded></item></channel></rss>