<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Digital Craft Workshop</title>
    <description>The latest articles on DEV Community by Digital Craft Workshop (@danielrusnok).</description>
    <link>https://dev.to/danielrusnok</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F277448%2Fdf127f3b-f0f6-45fe-b25e-1608deb963fe.png</url>
      <title>DEV Community: Digital Craft Workshop</title>
      <link>https://dev.to/danielrusnok</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/danielrusnok"/>
    <language>en</language>
    <item>
      <title>Four Reason to Change your Software</title>
      <dc:creator>Digital Craft Workshop</dc:creator>
      <pubDate>Thu, 18 Jun 2026 15:07:15 +0000</pubDate>
      <link>https://dev.to/danielrusnok/four-reason-to-change-your-software-1pc</link>
      <guid>https://dev.to/danielrusnok/four-reason-to-change-your-software-1pc</guid>
      <description>&lt;h4&gt;
  
  
  CLEAN&amp;nbsp;CODE&amp;nbsp;&amp;amp;&amp;nbsp;REFACTORING
&lt;/h4&gt;

&lt;h3&gt;
  
  
  Four&amp;nbsp;Reasons&amp;nbsp;to&amp;nbsp;Change&amp;nbsp;your&amp;nbsp;Software
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftq7wyj2467juibb31vtw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftq7wyj2467juibb31vtw.jpeg" alt="Four Reason to Change your Software cover image" width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;
Created&amp;nbsp;by&amp;nbsp;[Stories](https://www.freepik.com/stories)



&lt;p&gt;&lt;a href="http://Michael%20Feathers" rel="noopener noreferrer"&gt;Michael&amp;nbsp;Feathers&lt;/a&gt;&amp;nbsp;defines&amp;nbsp;four&amp;nbsp;main&amp;nbsp;reasons&amp;nbsp;to&amp;nbsp;change&amp;nbsp;your&amp;nbsp;code&amp;nbsp;in&amp;nbsp;the&amp;nbsp;book&amp;nbsp;&lt;a href="https://www.goodreads.com/book/show/44919.Working_Effectively_with_Legacy_Code" rel="noopener noreferrer"&gt;Working&amp;nbsp;Effectively&amp;nbsp;with&amp;nbsp;Legacy&amp;nbsp;Code&lt;/a&gt;.&amp;nbsp;I&amp;nbsp;found&amp;nbsp;those&amp;nbsp;few&amp;nbsp;paragraphs&amp;nbsp;interesting,&amp;nbsp;and&amp;nbsp;I&amp;nbsp;would&amp;nbsp;like&amp;nbsp;to&amp;nbsp;share&amp;nbsp;them&amp;nbsp;with&amp;nbsp;you&amp;nbsp;from&amp;nbsp;my&amp;nbsp;perspective.&lt;/p&gt;

&lt;h4&gt;
  
  
  Four&amp;nbsp;main&amp;nbsp;reasons&amp;nbsp;to&amp;nbsp;change&amp;nbsp;are
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  Adding&amp;nbsp;a&amp;nbsp;feature&lt;/li&gt;
&lt;li&gt;  Fixing&amp;nbsp;a&amp;nbsp;bug&lt;/li&gt;
&lt;li&gt;  Improving&amp;nbsp;design&lt;/li&gt;
&lt;li&gt;  Optimising&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fosi8fqjje78c8kkmoo93.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fosi8fqjje78c8kkmoo93.png" alt="Illustration" width="800" height="46"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding&amp;nbsp;Feature&amp;nbsp;and&amp;nbsp;Bug&amp;nbsp;Fixing
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6n5puxw0f7mkqtuwyg22.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6n5puxw0f7mkqtuwyg22.png" alt="Illustration" width="800" height="302"&gt;&lt;/a&gt;&lt;/p&gt;
original&amp;nbsp;pictures&amp;nbsp;by&amp;nbsp;[Stories](https://www.freepik.com/stories)



&lt;p&gt;The&amp;nbsp;most&amp;nbsp;typical&amp;nbsp;reason&amp;nbsp;to&amp;nbsp;change&amp;nbsp;software&amp;nbsp;is&amp;nbsp;an&amp;nbsp;adding&amp;nbsp;of&amp;nbsp;the&amp;nbsp;feature.&amp;nbsp;Your&amp;nbsp;software&amp;nbsp;is&amp;nbsp;acting&amp;nbsp;one&amp;nbsp;way,&amp;nbsp;and&amp;nbsp;the&amp;nbsp;boss&amp;nbsp;or&amp;nbsp;client&amp;nbsp;needs&amp;nbsp;it&amp;nbsp;to&amp;nbsp;do&amp;nbsp;something&amp;nbsp;else.&lt;/p&gt;

&lt;p&gt;At&amp;nbsp;first,&amp;nbsp;adding&amp;nbsp;a&amp;nbsp;feature&amp;nbsp;may&amp;nbsp;look&amp;nbsp;like&amp;nbsp;an&amp;nbsp;easy&amp;nbsp;task&amp;nbsp;for&amp;nbsp;both&amp;nbsp;sides.&amp;nbsp;Friction&amp;nbsp;between&amp;nbsp;the&amp;nbsp;developer&amp;nbsp;and&amp;nbsp;the&amp;nbsp;task&amp;nbsp;assigner&amp;nbsp;began&amp;nbsp;when&amp;nbsp;the&amp;nbsp;assigner&amp;nbsp;thinks&amp;nbsp;it&amp;nbsp;is&amp;nbsp;a&amp;nbsp;bug,&amp;nbsp;but&amp;nbsp;the&amp;nbsp;developer&amp;nbsp;sees&amp;nbsp;it&amp;nbsp;as&amp;nbsp;a&amp;nbsp;completely&amp;nbsp;new&amp;nbsp;feature.&lt;/p&gt;

&lt;p&gt;At&amp;nbsp;the&amp;nbsp;moment&amp;nbsp;when&amp;nbsp;I&amp;nbsp;am&amp;nbsp;writing&amp;nbsp;this&amp;nbsp;article,&amp;nbsp;I&amp;nbsp;am&amp;nbsp;also&amp;nbsp;working&amp;nbsp;on&amp;nbsp;a&amp;nbsp;form-based&amp;nbsp;information&amp;nbsp;system,&amp;nbsp;and&amp;nbsp;new&amp;nbsp;task&amp;nbsp;recently&amp;nbsp;appeared&amp;nbsp;in&amp;nbsp;the&amp;nbsp;mailbox.&amp;nbsp;The&amp;nbsp;client&amp;nbsp;wants&amp;nbsp;me&amp;nbsp;to&amp;nbsp;fix&amp;nbsp;the&amp;nbsp;focus&amp;nbsp;behaviour&amp;nbsp;of&amp;nbsp;textboxes.&amp;nbsp;He&amp;nbsp;thinks&amp;nbsp;that&amp;nbsp;every&amp;nbsp;time&amp;nbsp;he&amp;nbsp;clicks&amp;nbsp;or&amp;nbsp;tabs&amp;nbsp;into&amp;nbsp;a&amp;nbsp;textbox&amp;nbsp;which&amp;nbsp;contains&amp;nbsp;a&amp;nbsp;text,&amp;nbsp;the&amp;nbsp;whole&amp;nbsp;text&amp;nbsp;should&amp;nbsp;be&amp;nbsp;already&amp;nbsp;selected&amp;nbsp;or&amp;nbsp;as&amp;nbsp;he&amp;nbsp;said;&amp;nbsp;“it&amp;nbsp;should&amp;nbsp;be&amp;nbsp;blue”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk2xovt5nnuj3cptnzoor.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk2xovt5nnuj3cptnzoor.gif" alt="Illustration" width="436" height="55"&gt;&lt;/a&gt;&lt;/p&gt;
selection&amp;nbsp;of&amp;nbsp;text



&lt;p&gt;As&amp;nbsp;the&amp;nbsp;developer,&amp;nbsp;you&amp;nbsp;know&amp;nbsp;that&amp;nbsp;this&amp;nbsp;is&amp;nbsp;not&amp;nbsp;typical&amp;nbsp;behaviour&amp;nbsp;of&amp;nbsp;textbox&amp;nbsp;and&amp;nbsp;you&amp;nbsp;will&amp;nbsp;have&amp;nbsp;to&amp;nbsp;make&amp;nbsp;changes.&amp;nbsp;Depends&amp;nbsp;on&amp;nbsp;the&amp;nbsp;framework&amp;nbsp;you&amp;nbsp;are&amp;nbsp;work&amp;nbsp;with,&amp;nbsp;the&amp;nbsp;task&amp;nbsp;can&amp;nbsp;take&amp;nbsp;a&amp;nbsp;few&amp;nbsp;minutes&amp;nbsp;or&amp;nbsp;a&amp;nbsp;few&amp;nbsp;hours,&amp;nbsp;and&amp;nbsp;you&amp;nbsp;may&amp;nbsp;come&amp;nbsp;out&amp;nbsp;without&amp;nbsp;a&amp;nbsp;solution.&lt;/p&gt;

&lt;p&gt;It&amp;nbsp;might&amp;nbsp;seem&amp;nbsp;that&amp;nbsp;difference&amp;nbsp;between&amp;nbsp;adding&amp;nbsp;a&amp;nbsp;feature&amp;nbsp;and&amp;nbsp;bug&amp;nbsp;fixing&amp;nbsp;is&amp;nbsp;just&amp;nbsp;the&amp;nbsp;dilemma&amp;nbsp;about&amp;nbsp;the&amp;nbsp;contrast&amp;nbsp;of&amp;nbsp;the&amp;nbsp;point&amp;nbsp;of&amp;nbsp;view.&amp;nbsp;A&amp;nbsp;task&amp;nbsp;stays&amp;nbsp;the&amp;nbsp;same,&amp;nbsp;and&amp;nbsp;you&amp;nbsp;still&amp;nbsp;have&amp;nbsp;to&amp;nbsp;complete&amp;nbsp;it&amp;nbsp;one&amp;nbsp;or&amp;nbsp;another.&lt;/p&gt;

&lt;p&gt;But&amp;nbsp;some&amp;nbsp;companies&amp;nbsp;are&amp;nbsp;tracking&amp;nbsp;developers&amp;nbsp;time,&amp;nbsp;for&amp;nbsp;example,&amp;nbsp;for&amp;nbsp;invoice&amp;nbsp;calculations&amp;nbsp;or&amp;nbsp;quality&amp;nbsp;initiatives.&amp;nbsp;Such&amp;nbsp;difference&amp;nbsp;decides&amp;nbsp;if&amp;nbsp;the&amp;nbsp;company&amp;nbsp;will&amp;nbsp;bill&amp;nbsp;the&amp;nbsp;customer&amp;nbsp;for&amp;nbsp;a&amp;nbsp;new&amp;nbsp;feature&amp;nbsp;or&amp;nbsp;not.&amp;nbsp;Bug&amp;nbsp;fixing&amp;nbsp;can&amp;nbsp;be&amp;nbsp;part&amp;nbsp;of&amp;nbsp;the&amp;nbsp;contract&amp;nbsp;with&amp;nbsp;the&amp;nbsp;customer.&amp;nbsp;In&amp;nbsp;this&amp;nbsp;situation,&amp;nbsp;the&amp;nbsp;determination&amp;nbsp;of&amp;nbsp;difference&amp;nbsp;crucial.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fosi8fqjje78c8kkmoo93.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fosi8fqjje78c8kkmoo93.png" alt="Illustration" width="800" height="46"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Design&amp;nbsp;Improvement&amp;nbsp;and&amp;nbsp;Optimisation
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiesjgd0j3ts3zi5czgd8.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiesjgd0j3ts3zi5czgd8.jpeg" alt="Illustration" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
Created&amp;nbsp;by&amp;nbsp;[Stories](https://www.freepik.com/stories)



&lt;p&gt;First&amp;nbsp;of&amp;nbsp;all,&amp;nbsp;don’t&amp;nbsp;get&amp;nbsp;me&amp;nbsp;wrong.&amp;nbsp;By&amp;nbsp;saying&amp;nbsp;word&amp;nbsp;design,&amp;nbsp;I&amp;nbsp;mean&amp;nbsp;a&amp;nbsp;code&amp;nbsp;design,&amp;nbsp;no&amp;nbsp;design&amp;nbsp;of&amp;nbsp;user&amp;nbsp;interface.&lt;/p&gt;

&lt;p&gt;Design&amp;nbsp;improvement&amp;nbsp;is&amp;nbsp;a&amp;nbsp;different&amp;nbsp;kind&amp;nbsp;of&amp;nbsp;software&amp;nbsp;change.&amp;nbsp;It&amp;nbsp;is&amp;nbsp;driven&amp;nbsp;by&amp;nbsp;the&amp;nbsp;goodwill&amp;nbsp;of&amp;nbsp;the&amp;nbsp;developer,&amp;nbsp;no&amp;nbsp;by&amp;nbsp;the&amp;nbsp;boss&amp;nbsp;task.&lt;/p&gt;

&lt;p&gt;When&amp;nbsp;we&amp;nbsp;want&amp;nbsp;to&amp;nbsp;alter&amp;nbsp;the&amp;nbsp;software’s&amp;nbsp;design&amp;nbsp;to&amp;nbsp;make&amp;nbsp;it&amp;nbsp;more&amp;nbsp;maintainable,&amp;nbsp;we&amp;nbsp;want&amp;nbsp;software&amp;nbsp;behaviour&amp;nbsp;to&amp;nbsp;keep&amp;nbsp;untouched.&amp;nbsp;By&amp;nbsp;changing&amp;nbsp;a&amp;nbsp;design,&amp;nbsp;we&amp;nbsp;may&amp;nbsp;introduce&amp;nbsp;a&amp;nbsp;bug.&amp;nbsp;Fear&amp;nbsp;of&amp;nbsp;introducing&amp;nbsp;a&amp;nbsp;bug&amp;nbsp;leads&amp;nbsp;to&amp;nbsp;intact&amp;nbsp;code&amp;nbsp;segments&amp;nbsp;which&amp;nbsp;stay&amp;nbsp;the&amp;nbsp;same&amp;nbsp;since&amp;nbsp;the&amp;nbsp;beginning&amp;nbsp;and&amp;nbsp;will&amp;nbsp;rot.&lt;/p&gt;

&lt;p&gt;As&amp;nbsp;developers,&amp;nbsp;we&amp;nbsp;grow&amp;nbsp;and&amp;nbsp;knowledge&amp;nbsp;of&amp;nbsp;our&amp;nbsp;craft&amp;nbsp;rise.&amp;nbsp;Time&amp;nbsp;goes,&amp;nbsp;design&amp;nbsp;of&amp;nbsp;new&amp;nbsp;code&amp;nbsp;is&amp;nbsp;better,&amp;nbsp;and&amp;nbsp;one&amp;nbsp;day&amp;nbsp;task&amp;nbsp;will&amp;nbsp;appear&amp;nbsp;for&amp;nbsp;adding&amp;nbsp;a&amp;nbsp;new&amp;nbsp;feature&amp;nbsp;into&amp;nbsp;old&amp;nbsp;code&amp;nbsp;parts.&amp;nbsp;We&amp;nbsp;can&amp;nbsp;clearly&amp;nbsp;say&amp;nbsp;that&amp;nbsp;such&amp;nbsp;situations&amp;nbsp;will&amp;nbsp;once&amp;nbsp;in&amp;nbsp;a&amp;nbsp;while&amp;nbsp;come&amp;nbsp;out.&amp;nbsp;Such&amp;nbsp;a&amp;nbsp;position&amp;nbsp;always&amp;nbsp;motivate&amp;nbsp;me&amp;nbsp;to&amp;nbsp;improve&amp;nbsp;the&amp;nbsp;design,&amp;nbsp;but&amp;nbsp;I&amp;nbsp;am&amp;nbsp;too&amp;nbsp;afraid&amp;nbsp;of&amp;nbsp;dropping&amp;nbsp;behaviours,&amp;nbsp;and&amp;nbsp;I&amp;nbsp;will&amp;nbsp;not&amp;nbsp;do&amp;nbsp;any.&amp;nbsp;Again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fby95e3omb2sdtfxnupzh.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fby95e3omb2sdtfxnupzh.jpeg" alt="Illustration" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;
Created&amp;nbsp;by&amp;nbsp;[Stories](https://www.freepik.com/stories)



&lt;p&gt;&lt;em&gt;Side note: if you like thinking about the craft itself — when to refactor, when to leave code alone — I share short, free lessons like that in &lt;a href="https://drippery.app/subscribe/6e302973-cf5f-47ff-8803-22122ac3bd9d" rel="noopener noreferrer"&gt;The Claude Code Memory Starter&lt;/a&gt;, a tiny email series you can join in one click.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Refactoring
&lt;/h4&gt;

&lt;p&gt;Developers&amp;nbsp;fear&amp;nbsp;of&amp;nbsp;changing&amp;nbsp;code&amp;nbsp;drives&amp;nbsp;the&amp;nbsp;creation&amp;nbsp;of&amp;nbsp;whole&amp;nbsp;the&amp;nbsp;discipline&amp;nbsp;named&amp;nbsp;refactoring.&amp;nbsp;Refactoring&amp;nbsp;is&amp;nbsp;also&amp;nbsp;the&amp;nbsp;main&amp;nbsp;topic&amp;nbsp;of&amp;nbsp;the&amp;nbsp;earlier&amp;nbsp;mentioned&amp;nbsp;&lt;a href="http://working%20effectively%20with%20legacy%20code" rel="noopener noreferrer"&gt;book&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The&amp;nbsp;idea&amp;nbsp;behind&amp;nbsp;refactoring&amp;nbsp;is&amp;nbsp;that&amp;nbsp;we&amp;nbsp;can&amp;nbsp;make&amp;nbsp;the&amp;nbsp;software&amp;nbsp;more&amp;nbsp;maintainable&amp;nbsp;without&amp;nbsp;changing&amp;nbsp;behaviour&amp;nbsp;if&amp;nbsp;we&amp;nbsp;write&amp;nbsp;tests.&amp;nbsp;Tests&amp;nbsp;will&amp;nbsp;serve&amp;nbsp;as&amp;nbsp;watchdogs.&amp;nbsp;If&amp;nbsp;any&amp;nbsp;test&amp;nbsp;fails&amp;nbsp;while&amp;nbsp;you&amp;nbsp;improve&amp;nbsp;a&amp;nbsp;design,&amp;nbsp;it&amp;nbsp;indicates&amp;nbsp;that&amp;nbsp;you&amp;nbsp;unwillingly&amp;nbsp;drop&amp;nbsp;a&amp;nbsp;behaviour.&lt;/p&gt;

&lt;p&gt;Code&amp;nbsp;should&amp;nbsp;be&amp;nbsp;cover&amp;nbsp;by&amp;nbsp;the&amp;nbsp;collection&amp;nbsp;of&amp;nbsp;tests&amp;nbsp;which&amp;nbsp;will&amp;nbsp;keep&amp;nbsp;watch&amp;nbsp;on&amp;nbsp;application&amp;nbsp;behaviours.&amp;nbsp;We&amp;nbsp;should&amp;nbsp;refactor&amp;nbsp;in&amp;nbsp;small&amp;nbsp;steps,&amp;nbsp;and&amp;nbsp;continuously&amp;nbsp;run&amp;nbsp;a&amp;nbsp;set&amp;nbsp;of&amp;nbsp;tests&amp;nbsp;that&amp;nbsp;will&amp;nbsp;keep&amp;nbsp;us&amp;nbsp;informed.&lt;/p&gt;

&lt;p&gt;The&amp;nbsp;critical&amp;nbsp;thing&amp;nbsp;about&amp;nbsp;refactoring&amp;nbsp;from&amp;nbsp;a&amp;nbsp;change&amp;nbsp;point&amp;nbsp;of&amp;nbsp;view&amp;nbsp;is&amp;nbsp;that&amp;nbsp;there&amp;nbsp;aren’t&amp;nbsp;supposed&amp;nbsp;to&amp;nbsp;be&amp;nbsp;any&amp;nbsp;functional&amp;nbsp;changes&amp;nbsp;when&amp;nbsp;you&amp;nbsp;refactor.&lt;/p&gt;

&lt;h4&gt;
  
  
  Optimisation
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvy9dr7iwe2ltdbfrxugz.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvy9dr7iwe2ltdbfrxugz.jpeg" alt="Illustration" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
Created&amp;nbsp;by&amp;nbsp;[Stories](https://www.freepik.com/stories)



&lt;p&gt;The&amp;nbsp;optimisation&amp;nbsp;is&amp;nbsp;like&amp;nbsp;refactoring,&amp;nbsp;but&amp;nbsp;with&amp;nbsp;a&amp;nbsp;different&amp;nbsp;goal.&amp;nbsp;We&amp;nbsp;want&amp;nbsp;the&amp;nbsp;functionality&amp;nbsp;to&amp;nbsp;stay&amp;nbsp;the&amp;nbsp;same,&amp;nbsp;but&amp;nbsp;the&amp;nbsp;goal&amp;nbsp;is&amp;nbsp;to&amp;nbsp;increase&amp;nbsp;performance.&amp;nbsp;Optimising&amp;nbsp;can&amp;nbsp;lead&amp;nbsp;to&amp;nbsp;dropping&amp;nbsp;a&amp;nbsp;behaviour&amp;nbsp;and&amp;nbsp;introduction&amp;nbsp;of&amp;nbsp;bugs&amp;nbsp;too.&amp;nbsp;We&amp;nbsp;must&amp;nbsp;apply&amp;nbsp;the&amp;nbsp;very&amp;nbsp;same&amp;nbsp;rules&amp;nbsp;as&amp;nbsp;in&amp;nbsp;the&amp;nbsp;refactoring&amp;nbsp;process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fosi8fqjje78c8kkmoo93.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fosi8fqjje78c8kkmoo93.png" alt="Illustration" width="800" height="46"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;Fight&amp;nbsp;between&amp;nbsp;“it’s&amp;nbsp;a&amp;nbsp;feature”&amp;nbsp;and&amp;nbsp;“it’s&amp;nbsp;a&amp;nbsp;bug”&amp;nbsp;is&amp;nbsp;endless.&amp;nbsp;Correct&amp;nbsp;identification&amp;nbsp;of&amp;nbsp;the&amp;nbsp;problem&amp;nbsp;could&amp;nbsp;lead&amp;nbsp;to&amp;nbsp;the&amp;nbsp;rise&amp;nbsp;of&amp;nbsp;the&amp;nbsp;company’s&amp;nbsp;income&amp;nbsp;or&amp;nbsp;vice&amp;nbsp;versa.&lt;/p&gt;

&lt;p&gt;Refactoring&amp;nbsp;and&amp;nbsp;Optimizing&amp;nbsp;are&amp;nbsp;essential&amp;nbsp;disciplines&amp;nbsp;of&amp;nbsp;code&amp;nbsp;improvement&amp;nbsp;and&amp;nbsp;reasons&amp;nbsp;for&amp;nbsp;changing&amp;nbsp;code.&amp;nbsp;It&amp;nbsp;has&amp;nbsp;to&amp;nbsp;be&amp;nbsp;done&amp;nbsp;slowly,&amp;nbsp;wisely,&amp;nbsp;and&amp;nbsp;with&amp;nbsp;the&amp;nbsp;collection&amp;nbsp;of&amp;nbsp;tests&amp;nbsp;which&amp;nbsp;are&amp;nbsp;going&amp;nbsp;to&amp;nbsp;watch&amp;nbsp;your&amp;nbsp;back.&lt;/p&gt;

&lt;p&gt;One&amp;nbsp;way&amp;nbsp;of&amp;nbsp;improving&amp;nbsp;code&amp;nbsp;design&amp;nbsp;is&amp;nbsp;to&amp;nbsp;take&amp;nbsp;advantage&amp;nbsp;of&amp;nbsp;design&amp;nbsp;patterns.&amp;nbsp;I&amp;nbsp;wrote&amp;nbsp;a&amp;nbsp;few&amp;nbsp;pieces,&amp;nbsp;and&amp;nbsp;you&amp;nbsp;might&amp;nbsp;find&amp;nbsp;an&amp;nbsp;interested&amp;nbsp;in&amp;nbsp;reading&amp;nbsp;them&amp;nbsp;as&amp;nbsp;well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://medium.com/net-core/decoupling-with-chain-of-responsibility-pattern-in-c-1273329ed923" rel="noopener noreferrer"&gt;Decoupling&amp;nbsp;with&amp;nbsp;Chain&amp;nbsp;of&amp;nbsp;Responsibility&amp;nbsp;Pattern&amp;nbsp;in&amp;nbsp;C#&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://medium.com/net-core/decoupling-with-chain-of-responsibility-pattern-in-c-1273329ed923" rel="noopener noreferrer"&gt;The&amp;nbsp;Chain&amp;nbsp;of&amp;nbsp;Responsibility&amp;nbsp;Pattern&amp;nbsp;allows&amp;nbsp;us&amp;nbsp;to&amp;nbsp;easily&amp;nbsp;separate&amp;nbsp;dependent&amp;nbsp;parts&amp;nbsp;to&amp;nbsp;make&amp;nbsp;code&amp;nbsp;more&amp;nbsp;extensible&amp;nbsp;and…&lt;/a&gt;&lt;/em&gt;&lt;a href="https://medium.com/net-core/decoupling-with-chain-of-responsibility-pattern-in-c-1273329ed923" rel="noopener noreferrer"&gt;medium.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://medium.com/net-core/how-to-manage-states-with-state-design-pattern-in-c-d4ca47ec6aa" rel="noopener noreferrer"&gt;How&amp;nbsp;to&amp;nbsp;manage&amp;nbsp;states&amp;nbsp;with&amp;nbsp;State&amp;nbsp;Design&amp;nbsp;Pattern&amp;nbsp;in&amp;nbsp;C#?&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://medium.com/net-core/how-to-manage-states-with-state-design-pattern-in-c-d4ca47ec6aa" rel="noopener noreferrer"&gt;Everything&amp;nbsp;you&amp;nbsp;need&amp;nbsp;to&amp;nbsp;know&amp;nbsp;about&amp;nbsp;State&amp;nbsp;Design&amp;nbsp;Pattern&amp;nbsp;in&amp;nbsp;C#&amp;nbsp;at&amp;nbsp;one&amp;nbsp;place.&lt;/a&gt;&lt;/em&gt;&lt;a href="https://medium.com/net-core/how-to-manage-states-with-state-design-pattern-in-c-d4ca47ec6aa" rel="noopener noreferrer"&gt;medium.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fosi8fqjje78c8kkmoo93.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fosi8fqjje78c8kkmoo93.png" alt="Illustration" width="800" height="46"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.danielrusnok.com/daniel-rusnoks-newsletter" rel="noopener noreferrer"&gt;Daniel&amp;nbsp;Rusnok's&amp;nbsp;Newsletter&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://www.danielrusnok.com/daniel-rusnoks-newsletter" rel="noopener noreferrer"&gt;Every&amp;nbsp;month&amp;nbsp;I&amp;nbsp;will&amp;nbsp;send&amp;nbsp;you&amp;nbsp;an&amp;nbsp;email&amp;nbsp;about&amp;nbsp;with&amp;nbsp;list&amp;nbsp;of&amp;nbsp;my&amp;nbsp;newest&amp;nbsp;articles.&amp;nbsp;It&amp;nbsp;will&amp;nbsp;be&amp;nbsp;ofcourse&amp;nbsp;the&amp;nbsp;friendly&amp;nbsp;links…&lt;/a&gt;&lt;/em&gt;&lt;a href="https://www.danielrusnok.com/daniel-rusnoks-newsletter" rel="noopener noreferrer"&gt;www.danielrusnok.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.buymeacoffee.com/danielrusnok" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5tm54rf5555ym89o2ne4.png" alt="Illustration" width="200" height="56"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If this resonated, you might also enjoy &lt;a href="https://drippery.app/subscribe/6e302973-cf5f-47ff-8803-22122ac3bd9d" rel="noopener noreferrer"&gt;The Claude Code Memory Starter&lt;/a&gt; — a short free email series, one bite-sized lesson at a time.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &amp;nbsp;Related&amp;nbsp;Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  [LINK&amp;nbsp;PLACEHOLDER&amp;nbsp;-&amp;nbsp;&lt;a href="https://medium.com/@danielrusnok/how-to-rewrite-your-code-to-achieve-more-flexible-design-3c86dad822e%5D%C2%A0%E2%80%94%C2%A0_How%C2%A0to%C2%A0Rewrite%C2%A0your%C2%A0Code%C2%A0to%C2%A0Achieve%C2%A0More%C2%A0Flexible%C2%A0Design_" rel="noopener noreferrer"&gt;https://medium.com/@danielrusnok/how-to-rewrite-your-code-to-achieve-more-flexible-design-3c86dad822e]&amp;nbsp;—&amp;nbsp;_How&amp;nbsp;to&amp;nbsp;Rewrite&amp;nbsp;your&amp;nbsp;Code&amp;nbsp;to&amp;nbsp;Achieve&amp;nbsp;More&amp;nbsp;Flexible&amp;nbsp;Design_&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  [LINK&amp;nbsp;PLACEHOLDER&amp;nbsp;-&amp;nbsp;&lt;a href="https://medium.com/@danielrusnok/how-to-safely-extend-your-legacy-code-part-i-af98b49e913b%5D%C2%A0%E2%80%94%C2%A0_How%C2%A0to%C2%A0safely%C2%A0extend%C2%A0your%C2%A0legacy%C2%A0code?%C2%A0Part%C2%A0I_" rel="noopener noreferrer"&gt;https://medium.com/@danielrusnok/how-to-safely-extend-your-legacy-code-part-i-af98b49e913b]&amp;nbsp;—&amp;nbsp;_How&amp;nbsp;to&amp;nbsp;safely&amp;nbsp;extend&amp;nbsp;your&amp;nbsp;legacy&amp;nbsp;code?&amp;nbsp;Part&amp;nbsp;I_&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  [LINK&amp;nbsp;PLACEHOLDER&amp;nbsp;-&amp;nbsp;&lt;a href="https://medium.com/@danielrusnok/how-did-i-reduce-the-load-time-of-my-application-37f418289d7b%5D%C2%A0%E2%80%94%C2%A0_How%C2%A0did%C2%A0I%C2%A0reduce%C2%A0the%C2%A0load%C2%A0time%C2%A0of%C2%A0my%C2%A0application?_" rel="noopener noreferrer"&gt;https://medium.com/@danielrusnok/how-did-i-reduce-the-load-time-of-my-application-37f418289d7b]&amp;nbsp;—&amp;nbsp;_How&amp;nbsp;did&amp;nbsp;I&amp;nbsp;reduce&amp;nbsp;the&amp;nbsp;load&amp;nbsp;time&amp;nbsp;of&amp;nbsp;my&amp;nbsp;application?_&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  [LINK&amp;nbsp;PLACEHOLDER&amp;nbsp;-&amp;nbsp;&lt;a href="https://medium.com/@danielrusnok/how-to-dynamically-add-behaviors-with-decorator-pattern-6a130c41b7d4%5D%C2%A0%E2%80%94%C2%A0_How%C2%A0to%C2%A0Dynamically%C2%A0Add%C2%A0Behaviors%C2%A0With%C2%A0Decorator%C2%A0Pattern_" rel="noopener noreferrer"&gt;https://medium.com/@danielrusnok/how-to-dynamically-add-behaviors-with-decorator-pattern-6a130c41b7d4]&amp;nbsp;—&amp;nbsp;_How&amp;nbsp;to&amp;nbsp;Dynamically&amp;nbsp;Add&amp;nbsp;Behaviors&amp;nbsp;With&amp;nbsp;Decorator&amp;nbsp;Pattern_&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  [LINK&amp;nbsp;PLACEHOLDER&amp;nbsp;-&amp;nbsp;&lt;a href="https://medium.com/@danielrusnok/how-to-switch-the-algorithms-at-runtime-with-strategy-pattern-in-c-43fec29a1702%5D%C2%A0%E2%80%94%C2%A0_How%C2%A0to%C2%A0Switch%C2%A0the%C2%A0Algorithms%C2%A0at%C2%A0Runtime%C2%A0with%C2%A0Strategy%C2%A0Pattern_" rel="noopener noreferrer"&gt;https://medium.com/@danielrusnok/how-to-switch-the-algorithms-at-runtime-with-strategy-pattern-in-c-43fec29a1702]&amp;nbsp;—&amp;nbsp;_How&amp;nbsp;to&amp;nbsp;Switch&amp;nbsp;the&amp;nbsp;Algorithms&amp;nbsp;at&amp;nbsp;Runtime&amp;nbsp;with&amp;nbsp;Strategy&amp;nbsp;Pattern_&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>refactoring</category>
      <category>csharp</category>
      <category>programming</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>Developers are introverts. Domain-driven design can help.</title>
      <dc:creator>Digital Craft Workshop</dc:creator>
      <pubDate>Tue, 16 Jun 2026 15:07:44 +0000</pubDate>
      <link>https://dev.to/danielrusnok/developers-are-introverts-domain-driven-design-can-help-2bn1</link>
      <guid>https://dev.to/danielrusnok/developers-are-introverts-domain-driven-design-can-help-2bn1</guid>
      <description>&lt;h3&gt;
  
  
  Developers&amp;nbsp;are&amp;nbsp;introverts.&amp;nbsp;Domain-driven&amp;nbsp;design&amp;nbsp;can&amp;nbsp;help&amp;nbsp;to&amp;nbsp;change&amp;nbsp;it.
&lt;/h3&gt;

&lt;h4&gt;
  
  
  As&amp;nbsp;developers,&amp;nbsp;we&amp;nbsp;are&amp;nbsp;writing&amp;nbsp;code&amp;nbsp;to&amp;nbsp;build&amp;nbsp;software,&amp;nbsp;but&amp;nbsp;we&amp;nbsp;can’t&amp;nbsp;make&amp;nbsp;good&amp;nbsp;software&amp;nbsp;without&amp;nbsp;talking&amp;nbsp;to&amp;nbsp;clients.
&lt;/h4&gt;

&lt;h4&gt;
  
  
  Developers&amp;nbsp;aren’t&amp;nbsp;chatty.
&lt;/h4&gt;

&lt;p&gt;Most&amp;nbsp;of&amp;nbsp;the&amp;nbsp;time&amp;nbsp;software&amp;nbsp;developers&amp;nbsp;are&amp;nbsp;struggling&amp;nbsp;in&amp;nbsp;communication&amp;nbsp;with&amp;nbsp;clients&amp;nbsp;and&amp;nbsp;also&amp;nbsp;with&amp;nbsp;the&amp;nbsp;outer&amp;nbsp;world.&amp;nbsp;Many&amp;nbsp;of&amp;nbsp;us&amp;nbsp;choose&amp;nbsp;this&amp;nbsp;career&amp;nbsp;to&amp;nbsp;avoid&amp;nbsp;meeting&amp;nbsp;with&amp;nbsp;people,&amp;nbsp;and&amp;nbsp;that&amp;nbsp;is&amp;nbsp;why&amp;nbsp;companies&amp;nbsp;hire&amp;nbsp;managers&amp;nbsp;who&amp;nbsp;are&amp;nbsp;doing&amp;nbsp;instead&amp;nbsp;of&amp;nbsp;us.&lt;/p&gt;

&lt;p&gt;If&amp;nbsp;you&amp;nbsp;are&amp;nbsp;one&amp;nbsp;of&amp;nbsp;these&amp;nbsp;introverts,&amp;nbsp;then&amp;nbsp;I&amp;nbsp;have&amp;nbsp;bad&amp;nbsp;news&amp;nbsp;for&amp;nbsp;you.&amp;nbsp;You&amp;nbsp;may&amp;nbsp;end&amp;nbsp;up&amp;nbsp;stuck&amp;nbsp;with&amp;nbsp;buggy&amp;nbsp;code&amp;nbsp;forever.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03jxxwn61ndvz8l0bngg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03jxxwn61ndvz8l0bngg.jpeg" alt="Developers are introverts. Domain-driven design can help. cover image" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;
Created&amp;nbsp;by&amp;nbsp;rawpixel.com&amp;nbsp;—&amp;nbsp;[www.freepik.com](http://www.freepik.com)



&lt;p&gt;If&amp;nbsp;you&amp;nbsp;are&amp;nbsp;going&amp;nbsp;to&amp;nbsp;build&amp;nbsp;software&amp;nbsp;for&amp;nbsp;business,&amp;nbsp;you&amp;nbsp;need&amp;nbsp;to&amp;nbsp;understand&amp;nbsp;every&amp;nbsp;detail&amp;nbsp;because&amp;nbsp;you&amp;nbsp;are&amp;nbsp;translating&amp;nbsp;business&amp;nbsp;processes&amp;nbsp;into&amp;nbsp;code.&amp;nbsp;And&amp;nbsp;you&amp;nbsp;don’t&amp;nbsp;want&amp;nbsp;to&amp;nbsp;lose&amp;nbsp;important&amp;nbsp;information&amp;nbsp;in&amp;nbsp;translation.&lt;/p&gt;

&lt;h4&gt;
  
  
  Domain-driven&amp;nbsp;design&amp;nbsp;intervention
&lt;/h4&gt;

&lt;p&gt;One&amp;nbsp;of&amp;nbsp;the&amp;nbsp;critical&amp;nbsp;pieces&amp;nbsp;of&amp;nbsp;Domain-driven&amp;nbsp;design(DDD)&amp;nbsp;is&amp;nbsp;to&amp;nbsp;encourage&amp;nbsp;developers&amp;nbsp;for&amp;nbsp;better&amp;nbsp;interaction&amp;nbsp;with&amp;nbsp;domain&amp;nbsp;experts.&amp;nbsp;The&amp;nbsp;&lt;strong&gt;domain&amp;nbsp;expert&lt;/strong&gt;&amp;nbsp;is&amp;nbsp;one&amp;nbsp;who&amp;nbsp;knows&amp;nbsp;the&amp;nbsp;client’s&amp;nbsp;business&amp;nbsp;needs&amp;nbsp;and&amp;nbsp;processes.&lt;/p&gt;

&lt;p&gt;Developers&amp;nbsp;need&amp;nbsp;to&amp;nbsp;understand&amp;nbsp;their&amp;nbsp;clients&amp;nbsp;more&amp;nbsp;than&amp;nbsp;their&amp;nbsp;partners.&amp;nbsp;If&amp;nbsp;you&amp;nbsp;are&amp;nbsp;a&amp;nbsp;man,&amp;nbsp;you&amp;nbsp;probably&amp;nbsp;are&amp;nbsp;more&amp;nbsp;successful&amp;nbsp;in&amp;nbsp;understanding&amp;nbsp;your&amp;nbsp;client&amp;nbsp;than&amp;nbsp;your&amp;nbsp;precious&amp;nbsp;one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fns4e2lpouj5on4zeb30m.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fns4e2lpouj5on4zeb30m.jpeg" alt="Illustration" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
Photo&amp;nbsp;by&amp;nbsp;yanalya&amp;nbsp;—&amp;nbsp;[www.freepik.com](http://www.freepik.com)



&lt;p&gt;You&amp;nbsp;may&amp;nbsp;say&amp;nbsp;you&amp;nbsp;already&amp;nbsp;talk&amp;nbsp;to&amp;nbsp;them,&amp;nbsp;but&amp;nbsp;maybe&amp;nbsp;in&amp;nbsp;a&amp;nbsp;different&amp;nbsp;language.&amp;nbsp;Maybe&amp;nbsp;you&amp;nbsp;are&amp;nbsp;using&amp;nbsp;your&amp;nbsp;terms&amp;nbsp;like&amp;nbsp;tables&amp;nbsp;in&amp;nbsp;the&amp;nbsp;database&amp;nbsp;rather&amp;nbsp;than&amp;nbsp;domain&amp;nbsp;concepts.&lt;/p&gt;

&lt;p&gt;DDD&amp;nbsp;guides&amp;nbsp;us&amp;nbsp;to&amp;nbsp;find&amp;nbsp;a&amp;nbsp;common&amp;nbsp;language&amp;nbsp;with&amp;nbsp;Domain&amp;nbsp;Experts&amp;nbsp;and&amp;nbsp;understand&amp;nbsp;each&amp;nbsp;other&amp;nbsp;at&amp;nbsp;a&amp;nbsp;much&amp;nbsp;higher&amp;nbsp;level.&lt;/p&gt;

&lt;h4&gt;
  
  
  Ubiquitous&amp;nbsp;language
&lt;/h4&gt;

&lt;p&gt;Ubiquitous.&amp;nbsp;Terrible&amp;nbsp;word.&amp;nbsp;If&amp;nbsp;you&amp;nbsp;don’t&amp;nbsp;know&amp;nbsp;how&amp;nbsp;to&amp;nbsp;pronounce&amp;nbsp;it,&amp;nbsp;this&amp;nbsp;might&amp;nbsp;help&amp;nbsp;[yo͞oˈbikwətəs].&amp;nbsp;For&amp;nbsp;me,&amp;nbsp;it&amp;nbsp;doesn’t.&amp;nbsp;I&amp;nbsp;had&amp;nbsp;to&amp;nbsp;use&amp;nbsp;Google&amp;nbsp;Translator&amp;nbsp;for&amp;nbsp;listening&amp;nbsp;to&amp;nbsp;the&amp;nbsp;pronunciation.&amp;nbsp;It&amp;nbsp;doesn’t&amp;nbsp;help&amp;nbsp;either.&lt;/p&gt;

&lt;p&gt;A&amp;nbsp;simple&amp;nbsp;definition&amp;nbsp;of&amp;nbsp;a&amp;nbsp;ubiquitous&amp;nbsp;language&amp;nbsp;is&amp;nbsp;to&amp;nbsp;come&amp;nbsp;up&amp;nbsp;with&amp;nbsp;terms&amp;nbsp;that&amp;nbsp;will&amp;nbsp;be&amp;nbsp;commonly&amp;nbsp;used&amp;nbsp;when&amp;nbsp;discussing&amp;nbsp;a&amp;nbsp;particular&amp;nbsp;subdomain.&amp;nbsp;What&amp;nbsp;subdomain&amp;nbsp;is,&amp;nbsp;will&amp;nbsp;be&amp;nbsp;introduced&amp;nbsp;in&amp;nbsp;the&amp;nbsp;next&amp;nbsp;paragraph.&amp;nbsp;Hold&amp;nbsp;tight.&lt;/p&gt;

&lt;p&gt;There&amp;nbsp;will&amp;nbsp;most&amp;nbsp;likely&amp;nbsp;be&amp;nbsp;terms&amp;nbsp;that&amp;nbsp;come&amp;nbsp;from&amp;nbsp;the&amp;nbsp;business&amp;nbsp;space,&amp;nbsp;not&amp;nbsp;the&amp;nbsp;software&amp;nbsp;world.&amp;nbsp;But&amp;nbsp;those&amp;nbsp;terms&amp;nbsp;have&amp;nbsp;to&amp;nbsp;be&amp;nbsp;agreed&amp;nbsp;by&amp;nbsp;both&amp;nbsp;sides,&amp;nbsp;domain&amp;nbsp;expert&amp;nbsp;and&amp;nbsp;developer.&amp;nbsp;Make&amp;nbsp;sure&amp;nbsp;there&amp;nbsp;is&amp;nbsp;no&amp;nbsp;confusion&amp;nbsp;or&amp;nbsp;misunderstanding&amp;nbsp;created&amp;nbsp;by&amp;nbsp;the&amp;nbsp;terminology&amp;nbsp;used&amp;nbsp;by&amp;nbsp;various&amp;nbsp;members&amp;nbsp;of&amp;nbsp;the&amp;nbsp;team.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhs78i7jpppxar8yi4or1.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhs78i7jpppxar8yi4or1.jpeg" alt="Illustration" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
Photo&amp;nbsp;by&amp;nbsp;pressfoto&amp;nbsp;—&amp;nbsp;[www.freepik.co](http://www.freepik.com)m



&lt;h4&gt;
  
  
  Domain&amp;nbsp;and&amp;nbsp;subdomains
&lt;/h4&gt;

&lt;p&gt;The&amp;nbsp;core&amp;nbsp;domain&amp;nbsp;is&amp;nbsp;a&amp;nbsp;field&amp;nbsp;of&amp;nbsp;study&amp;nbsp;that&amp;nbsp;defines&amp;nbsp;a&amp;nbsp;set&amp;nbsp;of&amp;nbsp;common&amp;nbsp;requirements,&amp;nbsp;terminology,&amp;nbsp;and&amp;nbsp;functionality&amp;nbsp;for&amp;nbsp;any&amp;nbsp;software&amp;nbsp;program.&amp;nbsp;DDD&amp;nbsp;teaches&amp;nbsp;us&amp;nbsp;how&amp;nbsp;to&amp;nbsp;divide&amp;nbsp;the&amp;nbsp;core&amp;nbsp;domain&amp;nbsp;into&amp;nbsp;smaller&amp;nbsp;subdomains.&lt;/p&gt;

&lt;p&gt;Imagine&amp;nbsp;a&amp;nbsp;company.&amp;nbsp;It&amp;nbsp;can&amp;nbsp;be&amp;nbsp;divided&amp;nbsp;into&amp;nbsp;smaller&amp;nbsp;pieces,&amp;nbsp;departments,&amp;nbsp;for&amp;nbsp;example.&amp;nbsp;With&amp;nbsp;subdomains,&amp;nbsp;the&amp;nbsp;logic&amp;nbsp;stays&amp;nbsp;the&amp;nbsp;same.&amp;nbsp;They&amp;nbsp;are&amp;nbsp;filled&amp;nbsp;with&amp;nbsp;their&amp;nbsp;complexity,&amp;nbsp;terminology,&amp;nbsp;tasks,&amp;nbsp;and&amp;nbsp;challenges.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fom84ux83e60vdboyb72a.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fom84ux83e60vdboyb72a.jpeg" alt="Illustration" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
Image&amp;nbsp;by&amp;nbsp;Starline&amp;nbsp;—&amp;nbsp;[www.freepik.com](http://www.freepik.com)



&lt;p&gt;Many&amp;nbsp;applications&amp;nbsp;just&amp;nbsp;try&amp;nbsp;to&amp;nbsp;do&amp;nbsp;many&amp;nbsp;things&amp;nbsp;at&amp;nbsp;once&amp;nbsp;and&amp;nbsp;introducing&amp;nbsp;new&amp;nbsp;features&amp;nbsp;getting&amp;nbsp;more&amp;nbsp;difficult.&amp;nbsp;With&amp;nbsp;DDD,&amp;nbsp;you&amp;nbsp;will&amp;nbsp;&lt;strong&gt;divide&amp;nbsp;and&amp;nbsp;conquer&lt;/strong&gt;.&amp;nbsp;By&amp;nbsp;tearing&amp;nbsp;software&amp;nbsp;to&amp;nbsp;separated&amp;nbsp;subdomains,&amp;nbsp;we&amp;nbsp;can&amp;nbsp;achieve&amp;nbsp;dividing&amp;nbsp;problems&amp;nbsp;that&amp;nbsp;are&amp;nbsp;much&amp;nbsp;easier&amp;nbsp;to&amp;nbsp;solve.&lt;/p&gt;

&lt;p&gt;The&amp;nbsp;principle&amp;nbsp;of&amp;nbsp;&lt;a href="https://en.wikipedia.org/wiki/Separation_of_concerns" rel="noopener noreferrer"&gt;Separation&amp;nbsp;of&amp;nbsp;Concerns&lt;/a&gt;(SoC)&amp;nbsp;not&amp;nbsp;only&amp;nbsp;plays&amp;nbsp;a&amp;nbsp;critical&amp;nbsp;role&amp;nbsp;in&amp;nbsp;identifying&amp;nbsp;the&amp;nbsp;subdomains&amp;nbsp;but&amp;nbsp;within&amp;nbsp;each&amp;nbsp;subdomain,&amp;nbsp;we&amp;nbsp;use&amp;nbsp;it&amp;nbsp;as&amp;nbsp;well.&lt;/p&gt;

&lt;p&gt;Many&amp;nbsp;applications&amp;nbsp;spread&amp;nbsp;domain&amp;nbsp;logic&amp;nbsp;across&amp;nbsp;the&amp;nbsp;entire&amp;nbsp;project.&amp;nbsp;Across&amp;nbsp;the&amp;nbsp;persistence&amp;nbsp;layer,&amp;nbsp;across&amp;nbsp;the&amp;nbsp;user&amp;nbsp;interface.&amp;nbsp;Such&amp;nbsp;spreading&amp;nbsp;makes&amp;nbsp;it&amp;nbsp;much&amp;nbsp;more&amp;nbsp;difficult&amp;nbsp;to&amp;nbsp;keep&amp;nbsp;all&amp;nbsp;of&amp;nbsp;the&amp;nbsp;business&amp;nbsp;logic&amp;nbsp;consistent.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you want to keep going on the meta-skill side of building software — I'm publishing short, free lessons in &lt;a href="https://drippery.app/subscribe/6e302973-cf5f-47ff-8803-22122ac3bd9d" rel="noopener noreferrer"&gt;The Claude Code Memory Starter&lt;/a&gt;, a tiny email series you can join in one click.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;DDD&amp;nbsp;applies&amp;nbsp;an&amp;nbsp;SoC&amp;nbsp;to&amp;nbsp;help&amp;nbsp;direct&amp;nbsp;you&amp;nbsp;by&amp;nbsp;focusing&amp;nbsp;on&amp;nbsp;the&amp;nbsp;domain.&amp;nbsp;Not&amp;nbsp;on&amp;nbsp;details&amp;nbsp;like&amp;nbsp;how&amp;nbsp;to&amp;nbsp;persist&amp;nbsp;data&amp;nbsp;into&amp;nbsp;the&amp;nbsp;database&amp;nbsp;or&amp;nbsp;how&amp;nbsp;to&amp;nbsp;connect&amp;nbsp;to&amp;nbsp;external&amp;nbsp;API.&amp;nbsp;Those&amp;nbsp;become&amp;nbsp;implementation&amp;nbsp;details&amp;nbsp;that&amp;nbsp;you&amp;nbsp;can&amp;nbsp;ve&amp;nbsp;worry&amp;nbsp;about&amp;nbsp;later.&lt;/p&gt;

&lt;h4&gt;
  
  
  Benefits
&lt;/h4&gt;

&lt;p&gt;The&amp;nbsp;first&amp;nbsp;main&amp;nbsp;benefit&amp;nbsp;is&amp;nbsp;flexibility.&amp;nbsp;Because&amp;nbsp;DDD&amp;nbsp;guides&amp;nbsp;us&amp;nbsp;to&amp;nbsp;focus&amp;nbsp;on&amp;nbsp;small,&amp;nbsp;individual&amp;nbsp;nearly&amp;nbsp;autonomous&amp;nbsp;pieces&amp;nbsp;of&amp;nbsp;our&amp;nbsp;domain,&amp;nbsp;our&amp;nbsp;process&amp;nbsp;and&amp;nbsp;the&amp;nbsp;resulting&amp;nbsp;software&amp;nbsp;is&amp;nbsp;more&amp;nbsp;flexible.&amp;nbsp;We&amp;nbsp;can&amp;nbsp;easily&amp;nbsp;move&amp;nbsp;or&amp;nbsp;modify&amp;nbsp;the&amp;nbsp;small&amp;nbsp;parts&amp;nbsp;with&amp;nbsp;little&amp;nbsp;or&amp;nbsp;no&amp;nbsp;side&amp;nbsp;effects.&amp;nbsp;The&amp;nbsp;project&amp;nbsp;is&amp;nbsp;stable&amp;nbsp;and&amp;nbsp;flexible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0npai0e5yf748fwgtylj.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0npai0e5yf748fwgtylj.jpeg" alt="Illustration" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
Image&amp;nbsp;by&amp;nbsp;master1305&amp;nbsp;—&amp;nbsp;[www.freepik.com](http://www.freepik.com)



&lt;p&gt;Another&amp;nbsp;benefit&amp;nbsp;is&amp;nbsp;that&amp;nbsp;code&amp;nbsp;is&amp;nbsp;closely&amp;nbsp;mapped&amp;nbsp;to&amp;nbsp;the&amp;nbsp;customer’s&amp;nbsp;understanding&amp;nbsp;of&amp;nbsp;the&amp;nbsp;problem.&amp;nbsp;DDD&amp;nbsp;gives&amp;nbsp;you&amp;nbsp;a&amp;nbsp;clear&amp;nbsp;and&amp;nbsp;manageable&amp;nbsp;path&amp;nbsp;through&amp;nbsp;a&amp;nbsp;very&amp;nbsp;complex&amp;nbsp;problem.&amp;nbsp;When&amp;nbsp;you&amp;nbsp;look&amp;nbsp;at&amp;nbsp;the&amp;nbsp;code,&amp;nbsp;you&amp;nbsp;can&amp;nbsp;see&amp;nbsp;that&amp;nbsp;it’s&amp;nbsp;generally&amp;nbsp;well&amp;nbsp;organized&amp;nbsp;and&amp;nbsp;business&amp;nbsp;logic&amp;nbsp;all&amp;nbsp;lives&amp;nbsp;in&amp;nbsp;one&amp;nbsp;place.&lt;/p&gt;

&lt;p&gt;DDD&amp;nbsp;is&amp;nbsp;not&amp;nbsp;a&amp;nbsp;path&amp;nbsp;for&amp;nbsp;every&amp;nbsp;project,&amp;nbsp;but&amp;nbsp;there&amp;nbsp;are&amp;nbsp;many&amp;nbsp;principles&amp;nbsp;and&amp;nbsp;patterns&amp;nbsp;from&amp;nbsp;which&amp;nbsp;you&amp;nbsp;can&amp;nbsp;benefit.&amp;nbsp;Its&amp;nbsp;real&amp;nbsp;benefit&amp;nbsp;is&amp;nbsp;for&amp;nbsp;complex&amp;nbsp;domains.&amp;nbsp;You&amp;nbsp;can&amp;nbsp;describe&amp;nbsp;domain&amp;nbsp;as&amp;nbsp;complex&amp;nbsp;when&amp;nbsp;it&amp;nbsp;gets&amp;nbsp;challenging&amp;nbsp;to&amp;nbsp;domain&amp;nbsp;experts&amp;nbsp;to&amp;nbsp;express&amp;nbsp;their&amp;nbsp;needs.&amp;nbsp;You&amp;nbsp;know.&amp;nbsp;Like&amp;nbsp;women.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcb2osfzitschf4g69a3p.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcb2osfzitschf4g69a3p.jpeg" alt="Illustration" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
Image&amp;nbsp;by&amp;nbsp;Jcomp&amp;nbsp;—&amp;nbsp;[www.freepik.com](http://www.freepik.com)



&lt;p&gt;DDD&amp;nbsp;provides&amp;nbsp;principles&amp;nbsp;and&amp;nbsp;patterns&amp;nbsp;that&amp;nbsp;help&amp;nbsp;us&amp;nbsp;tackle&amp;nbsp;difficult&amp;nbsp;software&amp;nbsp;problems&amp;nbsp;and&amp;nbsp;even&amp;nbsp;business&amp;nbsp;problems.&amp;nbsp;These&amp;nbsp;patterns&amp;nbsp;are&amp;nbsp;historically&amp;nbsp;successful&amp;nbsp;in&amp;nbsp;solving&amp;nbsp;very&amp;nbsp;complex&amp;nbsp;problems.&lt;/p&gt;

&lt;p&gt;The&amp;nbsp;ultimate&amp;nbsp;goal&amp;nbsp;is&amp;nbsp;not&amp;nbsp;to&amp;nbsp;write&amp;nbsp;code,&amp;nbsp;not&amp;nbsp;even&amp;nbsp;building&amp;nbsp;software,&amp;nbsp;but&amp;nbsp;to&amp;nbsp;solve&amp;nbsp;problems.&amp;nbsp;Nobody&amp;nbsp;wants&amp;nbsp;your&amp;nbsp;software;&amp;nbsp;they&amp;nbsp;want&amp;nbsp;what&amp;nbsp;it&amp;nbsp;can&amp;nbsp;give&amp;nbsp;them.&lt;/p&gt;

&lt;h4&gt;
  
  
  Disadvantages
&lt;/h4&gt;

&lt;p&gt;You&amp;nbsp;will&amp;nbsp;spend&amp;nbsp;a&amp;nbsp;lot&amp;nbsp;of&amp;nbsp;time&amp;nbsp;talking&amp;nbsp;about&amp;nbsp;the&amp;nbsp;domain&amp;nbsp;and&amp;nbsp;the&amp;nbsp;problems&amp;nbsp;that&amp;nbsp;need&amp;nbsp;to&amp;nbsp;be&amp;nbsp;solved.&amp;nbsp;And&amp;nbsp;you&amp;nbsp;will&amp;nbsp;spend&amp;nbsp;plenty&amp;nbsp;of&amp;nbsp;time&amp;nbsp;sorting&amp;nbsp;out&amp;nbsp;what&amp;nbsp;is&amp;nbsp;truly&amp;nbsp;domain&amp;nbsp;logic&amp;nbsp;and&amp;nbsp;what&amp;nbsp;is&amp;nbsp;just&amp;nbsp;infrastructure.&lt;/p&gt;

&lt;p&gt;DDD&amp;nbsp;is&amp;nbsp;not&amp;nbsp;always&amp;nbsp;the&amp;nbsp;right&amp;nbsp;path&amp;nbsp;for&amp;nbsp;your&amp;nbsp;applications.&amp;nbsp;And&amp;nbsp;it’s&amp;nbsp;helpful&amp;nbsp;to&amp;nbsp;keep&amp;nbsp;in&amp;nbsp;mind&amp;nbsp;that&amp;nbsp;exist&amp;nbsp;some&amp;nbsp;scenarios&amp;nbsp;where&amp;nbsp;DDD&amp;nbsp;is&amp;nbsp;an&amp;nbsp;overkill.&amp;nbsp;If&amp;nbsp;you&amp;nbsp;have&amp;nbsp;an&amp;nbsp;application&amp;nbsp;or&amp;nbsp;subdomain&amp;nbsp;that&amp;nbsp;is&amp;nbsp;just&amp;nbsp;a&amp;nbsp;data-driven&amp;nbsp;app&amp;nbsp;and&amp;nbsp;doesn’t&amp;nbsp;do&amp;nbsp;much&amp;nbsp;more&amp;nbsp;than&amp;nbsp;a&amp;nbsp;lot&amp;nbsp;of&amp;nbsp;crud&amp;nbsp;logic,&amp;nbsp;there&amp;nbsp;is&amp;nbsp;no&amp;nbsp;needs&amp;nbsp;to&amp;nbsp;use&amp;nbsp;DDD.&amp;nbsp;It&amp;nbsp;would&amp;nbsp;be&amp;nbsp;a&amp;nbsp;waste&amp;nbsp;of&amp;nbsp;time&amp;nbsp;and&amp;nbsp;effort.&lt;/p&gt;

&lt;p&gt;Technical&amp;nbsp;complexity&amp;nbsp;doesn’t&amp;nbsp;mean&amp;nbsp;business&amp;nbsp;domain&amp;nbsp;complexity.&amp;nbsp;DDD&amp;nbsp;may&amp;nbsp;not&amp;nbsp;just&amp;nbsp;be&amp;nbsp;a&amp;nbsp;path&amp;nbsp;because&amp;nbsp;your&amp;nbsp;project&amp;nbsp;is&amp;nbsp;technically&amp;nbsp;challenging.&lt;/p&gt;

&lt;h4&gt;
  
  
  The&amp;nbsp;Book
&lt;/h4&gt;

&lt;p&gt;DDD&amp;nbsp;was&amp;nbsp;presented&amp;nbsp;in&amp;nbsp;2003&amp;nbsp;by&amp;nbsp;&lt;a href="https://twitter.com/ericevans0" rel="noopener noreferrer"&gt;Eric&amp;nbsp;Evans&lt;/a&gt;&amp;nbsp;in&amp;nbsp;a&amp;nbsp;book&amp;nbsp;titled&amp;nbsp;Domain-Driven&amp;nbsp;Design.&amp;nbsp;The&amp;nbsp;book&amp;nbsp;was&amp;nbsp;written&amp;nbsp;for&amp;nbsp;one&amp;nbsp;major&amp;nbsp;reason,&amp;nbsp;and&amp;nbsp;that&amp;nbsp;is&amp;nbsp;for&amp;nbsp;solving&amp;nbsp;complex&amp;nbsp;problems.&amp;nbsp;That’s&amp;nbsp;why&amp;nbsp;a&amp;nbsp;subtitle&amp;nbsp;of&amp;nbsp;Evans's&amp;nbsp;book&amp;nbsp;is&amp;nbsp;“Tackling&amp;nbsp;Complexity&amp;nbsp;in&amp;nbsp;the&amp;nbsp;Heart&amp;nbsp;of&amp;nbsp;Software”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzyh8qdbfqn471bmeaanp.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzyh8qdbfqn471bmeaanp.jpeg" alt="Illustration" width="377" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Summary
&lt;/h4&gt;

&lt;p&gt;Domain-Driven&amp;nbsp;Design&amp;nbsp;helps&amp;nbsp;man&amp;nbsp;to&amp;nbsp;understand&amp;nbsp;women&amp;nbsp;fully.&amp;nbsp;No.&amp;nbsp;I&amp;nbsp;am&amp;nbsp;joking.&amp;nbsp;One&amp;nbsp;book&amp;nbsp;could&amp;nbsp;not&amp;nbsp;be&amp;nbsp;enough.&lt;/p&gt;

&lt;p&gt;What&amp;nbsp;I&amp;nbsp;want&amp;nbsp;you&amp;nbsp;to&amp;nbsp;take&amp;nbsp;with&amp;nbsp;you&amp;nbsp;is&amp;nbsp;this.&amp;nbsp;Be&amp;nbsp;more&amp;nbsp;chatty&amp;nbsp;with&amp;nbsp;your&amp;nbsp;clients&amp;nbsp;and&amp;nbsp;always&amp;nbsp;try&amp;nbsp;to&amp;nbsp;understand&amp;nbsp;their&amp;nbsp;needs.&amp;nbsp;If&amp;nbsp;you&amp;nbsp;don’t&amp;nbsp;understand&amp;nbsp;each&amp;nbsp;other,&amp;nbsp;define&amp;nbsp;your&amp;nbsp;ubiquitous&amp;nbsp;language.&lt;/p&gt;

&lt;p&gt;Divide&amp;nbsp;and&amp;nbsp;conquer&amp;nbsp;your&amp;nbsp;business&amp;nbsp;logic.&amp;nbsp;Separation&amp;nbsp;of&amp;nbsp;Concerns,&amp;nbsp;loose&amp;nbsp;coupling,&amp;nbsp;subdomains.&amp;nbsp;So&amp;nbsp;many&amp;nbsp;titles&amp;nbsp;for&amp;nbsp;one&amp;nbsp;thought.&amp;nbsp;Divide&amp;nbsp;your&amp;nbsp;code&amp;nbsp;into&amp;nbsp;smaller&amp;nbsp;pieces&amp;nbsp;on&amp;nbsp;every&amp;nbsp;level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.danielrusnok.com/daniel-rusnoks-newsletter" rel="noopener noreferrer"&gt;Daniel&amp;nbsp;Rusnok's&amp;nbsp;Newsletter&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://www.danielrusnok.com/daniel-rusnoks-newsletter" rel="noopener noreferrer"&gt;Every&amp;nbsp;month&amp;nbsp;I&amp;nbsp;will&amp;nbsp;send&amp;nbsp;you&amp;nbsp;an&amp;nbsp;email&amp;nbsp;about&amp;nbsp;with&amp;nbsp;list&amp;nbsp;of&amp;nbsp;my&amp;nbsp;newest&amp;nbsp;articles.&amp;nbsp;It&amp;nbsp;will&amp;nbsp;be&amp;nbsp;ofcourse&amp;nbsp;the&amp;nbsp;friendly&amp;nbsp;links…&lt;/a&gt;&lt;/em&gt;&lt;a href="https://www.danielrusnok.com/daniel-rusnoks-newsletter" rel="noopener noreferrer"&gt;www.danielrusnok.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.buymeacoffee.com/danielrusnok" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5tm54rf5555ym89o2ne4.png" alt="Illustration" width="200" height="56"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If this resonated, you might also like &lt;a href="https://drippery.app/subscribe/6e302973-cf5f-47ff-8803-22122ac3bd9d" rel="noopener noreferrer"&gt;The Claude Code Memory Starter&lt;/a&gt; — a short free email series, one bite-sized lesson at a time.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &amp;nbsp;Related&amp;nbsp;Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  [LINK&amp;nbsp;PLACEHOLDER&amp;nbsp;-&amp;nbsp;&lt;a href="https://medium.com/@danielrusnok/what-is-anemic-domain-model-and-why-it-can-be-harmful-2677b1b0a79a%5D%C2%A0%E2%80%94%C2%A0_What%C2%A0is%C2%A0Anemic%C2%A0Domain%C2%A0Model%C2%A0and%C2%A0why%C2%A0it%C2%A0can%C2%A0be%C2%A0harmful?_" rel="noopener noreferrer"&gt;https://medium.com/@danielrusnok/what-is-anemic-domain-model-and-why-it-can-be-harmful-2677b1b0a79a]&amp;nbsp;—&amp;nbsp;_What&amp;nbsp;is&amp;nbsp;Anemic&amp;nbsp;Domain&amp;nbsp;Model&amp;nbsp;and&amp;nbsp;why&amp;nbsp;it&amp;nbsp;can&amp;nbsp;be&amp;nbsp;harmful?_&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  [LINK&amp;nbsp;PLACEHOLDER&amp;nbsp;-&amp;nbsp;&lt;a href="https://medium.com/@danielrusnok/3-domain-centric-architectures-every-software-developer-should-know-a15727ada79f%5D%C2%A0%E2%80%94%C2%A0_3%C2%A0Domain-Centric%C2%A0Architectures%C2%A0Every%C2%A0Software%C2%A0Developer%C2%A0Should%C2%A0Know_" rel="noopener noreferrer"&gt;https://medium.com/@danielrusnok/3-domain-centric-architectures-every-software-developer-should-know-a15727ada79f]&amp;nbsp;—&amp;nbsp;_3&amp;nbsp;Domain-Centric&amp;nbsp;Architectures&amp;nbsp;Every&amp;nbsp;Software&amp;nbsp;Developer&amp;nbsp;Should&amp;nbsp;Know_&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  [LINK&amp;nbsp;PLACEHOLDER&amp;nbsp;-&amp;nbsp;&lt;a href="https://medium.com/@danielrusnok/3-cqrs-architectures-that-every-software-architect-should-know-a7f69aae8b6c%5D%C2%A0%E2%80%94%C2%A0_3%C2%A0CQRS%C2%A0Architectures%C2%A0that%C2%A0Every%C2%A0Software%C2%A0Architect%C2%A0Should%C2%A0Know_" rel="noopener noreferrer"&gt;https://medium.com/@danielrusnok/3-cqrs-architectures-that-every-software-architect-should-know-a7f69aae8b6c]&amp;nbsp;—&amp;nbsp;_3&amp;nbsp;CQRS&amp;nbsp;Architectures&amp;nbsp;that&amp;nbsp;Every&amp;nbsp;Software&amp;nbsp;Architect&amp;nbsp;Should&amp;nbsp;Know_&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  [LINK&amp;nbsp;PLACEHOLDER&amp;nbsp;-&amp;nbsp;&lt;a href="https://ai.plainenglish.io/domain-driven-design-is-the-architecture-ai-agents-were-waiting-for-4742509a44db%5D%C2%A0%E2%80%94%C2%A0_Domain-Driven%C2%A0Design%C2%A0Is%C2%A0the%C2%A0Architecture%C2%A0AI%C2%A0Agents%C2%A0Were%C2%A0Waiting%C2%A0For_" rel="noopener noreferrer"&gt;https://ai.plainenglish.io/domain-driven-design-is-the-architecture-ai-agents-were-waiting-for-4742509a44db]&amp;nbsp;—&amp;nbsp;_Domain-Driven&amp;nbsp;Design&amp;nbsp;Is&amp;nbsp;the&amp;nbsp;Architecture&amp;nbsp;AI&amp;nbsp;Agents&amp;nbsp;Were&amp;nbsp;Waiting&amp;nbsp;For_&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  [LINK&amp;nbsp;PLACEHOLDER&amp;nbsp;-&amp;nbsp;&lt;a href="https://medium.com/@danielrusnok/3-things-that-change-my-way-of-thinking-in-2020-c87d8cf137bd%5D%C2%A0%E2%80%94%C2%A0_3%C2%A0Things%C2%A0that%C2%A0Change%C2%A0my%C2%A0Way%C2%A0of%C2%A0Thinking%C2%A0in%C2%A02020_" rel="noopener noreferrer"&gt;https://medium.com/@danielrusnok/3-things-that-change-my-way-of-thinking-in-2020-c87d8cf137bd]&amp;nbsp;—&amp;nbsp;_3&amp;nbsp;Things&amp;nbsp;that&amp;nbsp;Change&amp;nbsp;my&amp;nbsp;Way&amp;nbsp;of&amp;nbsp;Thinking&amp;nbsp;in&amp;nbsp;2020_&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ddd</category>
      <category>softwareengineering</category>
      <category>architecture</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Recipe to greatly layered architecture!</title>
      <dc:creator>Digital Craft Workshop</dc:creator>
      <pubDate>Sun, 14 Jun 2026 15:04:35 +0000</pubDate>
      <link>https://dev.to/danielrusnok/recipe-to-greatly-layered-architecture-1cd5</link>
      <guid>https://dev.to/danielrusnok/recipe-to-greatly-layered-architecture-1cd5</guid>
      <description>&lt;h3&gt;
  
  
  Recipe&amp;nbsp;to&amp;nbsp;greatly&amp;nbsp;layered&amp;nbsp;architecture!
&lt;/h3&gt;

&lt;p&gt;Is&amp;nbsp;it&amp;nbsp;a&amp;nbsp;recipe?&amp;nbsp;Sure.&amp;nbsp;Is&amp;nbsp;it&amp;nbsp;advice&amp;nbsp;on&amp;nbsp;how&amp;nbsp;to&amp;nbsp;NOT&amp;nbsp;make&amp;nbsp;spaghetti?&amp;nbsp;Also,&amp;nbsp;yes.&amp;nbsp;Does&amp;nbsp;the&amp;nbsp;article&amp;nbsp;belong&amp;nbsp;into&amp;nbsp;Software&amp;nbsp;development&amp;nbsp;category?&amp;nbsp;Of&amp;nbsp;course,&amp;nbsp;what&amp;nbsp;is&amp;nbsp;wrong&amp;nbsp;with&amp;nbsp;you?&amp;nbsp;Are&amp;nbsp;you&amp;nbsp;interested?&amp;nbsp;Great,&amp;nbsp;then&amp;nbsp;read&amp;nbsp;more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqjmwwqdg0sfwzdbzla2o.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqjmwwqdg0sfwzdbzla2o.jpeg" alt="Recipe to greatly layered architecture! cover image" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
Photo&amp;nbsp;by&amp;nbsp;Trang&amp;nbsp;Doan&amp;nbsp;from&amp;nbsp;Pexels



&lt;p&gt;Okay,&amp;nbsp;now&amp;nbsp;it&amp;nbsp;is&amp;nbsp;time&amp;nbsp;to&amp;nbsp;explain&amp;nbsp;what&amp;nbsp;the&amp;nbsp;hell&amp;nbsp;am&amp;nbsp;I&amp;nbsp;talking&amp;nbsp;about.&amp;nbsp;Onion&amp;nbsp;architecture&amp;nbsp;is&amp;nbsp;an&amp;nbsp;approach&amp;nbsp;to&amp;nbsp;design&amp;nbsp;layers&amp;nbsp;of&amp;nbsp;an&amp;nbsp;application.&lt;/p&gt;

&lt;p&gt;Spaghetti&amp;nbsp;or&amp;nbsp;also&amp;nbsp;the&amp;nbsp;spaghetti&amp;nbsp;code&amp;nbsp;is&amp;nbsp;the&amp;nbsp;code,&amp;nbsp;that&amp;nbsp;is&amp;nbsp;harshly&amp;nbsp;separable,&amp;nbsp;tangled&amp;nbsp;and&amp;nbsp;passes&amp;nbsp;through&amp;nbsp;multiple&amp;nbsp;layers&amp;nbsp;of&amp;nbsp;an&amp;nbsp;application.&lt;/p&gt;

&lt;p&gt;Such&amp;nbsp;code&amp;nbsp;is&amp;nbsp;hard&amp;nbsp;to&amp;nbsp;maintain&amp;nbsp;and&amp;nbsp;hard&amp;nbsp;to&amp;nbsp;extend&amp;nbsp;with&amp;nbsp;any&amp;nbsp;new&amp;nbsp;features.&amp;nbsp;So,&amp;nbsp;Onion&amp;nbsp;architecture&amp;nbsp;is&amp;nbsp;some&amp;nbsp;kind&amp;nbsp;of&amp;nbsp;recipe&amp;nbsp;telling&amp;nbsp;you&amp;nbsp;how&amp;nbsp;to&amp;nbsp;NOT&amp;nbsp;make&amp;nbsp;spaghetti.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why&amp;nbsp;onion?
&lt;/h4&gt;

&lt;p&gt;As&amp;nbsp;we&amp;nbsp;know,&amp;nbsp;it&amp;nbsp;is&amp;nbsp;simple&amp;nbsp;to&amp;nbsp;detach&amp;nbsp;layers&amp;nbsp;from&amp;nbsp;an&amp;nbsp;onion&amp;nbsp;as&amp;nbsp;well&amp;nbsp;as&amp;nbsp;attach&amp;nbsp;them&amp;nbsp;back&amp;nbsp;again&amp;nbsp;Alright,&amp;nbsp;I&amp;nbsp;guess&amp;nbsp;nobody&amp;nbsp;is&amp;nbsp;attaching&amp;nbsp;onion&amp;nbsp;layers&amp;nbsp;back&amp;nbsp;to&amp;nbsp;an&amp;nbsp;onion,&amp;nbsp;but&amp;nbsp;it&amp;nbsp;can&amp;nbsp;work&amp;nbsp;well&amp;nbsp;as&amp;nbsp;an&amp;nbsp;example&amp;nbsp;for&amp;nbsp;our&amp;nbsp;imagination.&amp;nbsp;Architecture&amp;nbsp;of&amp;nbsp;an&amp;nbsp;onion&amp;nbsp;consist&amp;nbsp;of&amp;nbsp;core&amp;nbsp;and&amp;nbsp;layers.&amp;nbsp;What&amp;nbsp;should&amp;nbsp;be&amp;nbsp;the&amp;nbsp;core&amp;nbsp;in&amp;nbsp;a&amp;nbsp;software&amp;nbsp;application?&lt;/p&gt;

&lt;p&gt;Should&amp;nbsp;it&amp;nbsp;be&amp;nbsp;a&amp;nbsp;Presentation&amp;nbsp;of&amp;nbsp;an&amp;nbsp;application?&amp;nbsp;Let&amp;nbsp;me&amp;nbsp;explain.&amp;nbsp;By&amp;nbsp;the&amp;nbsp;phrase&amp;nbsp;Presentation&amp;nbsp;of&amp;nbsp;an&amp;nbsp;application,&amp;nbsp;I&amp;nbsp;mean&amp;nbsp;the&amp;nbsp;part&amp;nbsp;of&amp;nbsp;an&amp;nbsp;application,&amp;nbsp;that&amp;nbsp;is&amp;nbsp;responsible&amp;nbsp;for&amp;nbsp;user&amp;nbsp;interface.&amp;nbsp;In&amp;nbsp;software&amp;nbsp;development,&amp;nbsp;such&amp;nbsp;part&amp;nbsp;is&amp;nbsp;powered&amp;nbsp;by&amp;nbsp;different&amp;nbsp;technologies&amp;nbsp;like&amp;nbsp;WPF,&amp;nbsp;Angular&amp;nbsp;and&amp;nbsp;React.&amp;nbsp;And&amp;nbsp;what&amp;nbsp;is&amp;nbsp;the&amp;nbsp;Presentation&amp;nbsp;of&amp;nbsp;onion?&amp;nbsp;I´d&amp;nbsp;say&amp;nbsp;it’s&amp;nbsp;just&amp;nbsp;a&amp;nbsp;peel.&amp;nbsp;From&amp;nbsp;the&amp;nbsp;measurement&amp;nbsp;point&amp;nbsp;of&amp;nbsp;view,&amp;nbsp;the&amp;nbsp;peel&amp;nbsp;is&amp;nbsp;too&amp;nbsp;far&amp;nbsp;away&amp;nbsp;from&amp;nbsp;the&amp;nbsp;core.&amp;nbsp;So,&amp;nbsp;the&amp;nbsp;Presentation&amp;nbsp;of&amp;nbsp;an&amp;nbsp;application&amp;nbsp;cannot&amp;nbsp;be&amp;nbsp;the&amp;nbsp;core.&lt;/p&gt;

&lt;p&gt;Should&amp;nbsp;it&amp;nbsp;be&amp;nbsp;a&amp;nbsp;Database?&amp;nbsp;Let’s&amp;nbsp;think&amp;nbsp;about&amp;nbsp;it.&amp;nbsp;In&amp;nbsp;a&amp;nbsp;database,&amp;nbsp;we&amp;nbsp;can&amp;nbsp;find&amp;nbsp;information.&amp;nbsp;What&amp;nbsp;information&amp;nbsp;about&amp;nbsp;an&amp;nbsp;onion&amp;nbsp;we&amp;nbsp;know?&amp;nbsp;Onion&amp;nbsp;can&amp;nbsp;have&amp;nbsp;a&amp;nbsp;different&amp;nbsp;colour,&amp;nbsp;kind,&amp;nbsp;taste&amp;nbsp;or&amp;nbsp;look.&amp;nbsp;If&amp;nbsp;information&amp;nbsp;can&amp;nbsp;have&amp;nbsp;different&amp;nbsp;values,&amp;nbsp;they&amp;nbsp;are&amp;nbsp;variable.&amp;nbsp;If&amp;nbsp;something&amp;nbsp;is&amp;nbsp;variable,&amp;nbsp;we&amp;nbsp;can’t&amp;nbsp;rely&amp;nbsp;on&amp;nbsp;it.&amp;nbsp;We&amp;nbsp;don’t&amp;nbsp;have&amp;nbsp;a&amp;nbsp;solid&amp;nbsp;core.&amp;nbsp;So,&amp;nbsp;the&amp;nbsp;database&amp;nbsp;cannot&amp;nbsp;be&amp;nbsp;a&amp;nbsp;core&amp;nbsp;either.&lt;/p&gt;

&lt;p&gt;What&amp;nbsp;defines&amp;nbsp;an&amp;nbsp;onion&amp;nbsp;by&amp;nbsp;nature?&amp;nbsp;In&amp;nbsp;my&amp;nbsp;humble&amp;nbsp;opinion,&amp;nbsp;it&amp;nbsp;is&amp;nbsp;its&amp;nbsp;genetic&amp;nbsp;information.&amp;nbsp;Genetic&amp;nbsp;information&amp;nbsp;defines&amp;nbsp;how&amp;nbsp;the&amp;nbsp;onion&amp;nbsp;looks&amp;nbsp;like,&amp;nbsp;how&amp;nbsp;it&amp;nbsp;tastes&amp;nbsp;etc.&amp;nbsp;Genetic&amp;nbsp;information&amp;nbsp;formulates&amp;nbsp;the&amp;nbsp;onion.&amp;nbsp;And&amp;nbsp;what&amp;nbsp;is&amp;nbsp;genetic&amp;nbsp;information?&amp;nbsp;Well,&amp;nbsp;it&amp;nbsp;is&amp;nbsp;nothing&amp;nbsp;more&amp;nbsp;than&amp;nbsp;a&amp;nbsp;set&amp;nbsp;of&amp;nbsp;rules.&lt;/p&gt;

&lt;p&gt;What&amp;nbsp;or&amp;nbsp;who&amp;nbsp;is&amp;nbsp;forming&amp;nbsp;software&amp;nbsp;applications?&amp;nbsp;Developer?&amp;nbsp;I&amp;nbsp;don’t&amp;nbsp;think&amp;nbsp;so.&amp;nbsp;Simply&amp;nbsp;said,&amp;nbsp;the&amp;nbsp;developer&amp;nbsp;is&amp;nbsp;translating&amp;nbsp;tasks&amp;nbsp;from&amp;nbsp;human&amp;nbsp;language&amp;nbsp;to&amp;nbsp;the&amp;nbsp;language&amp;nbsp;of&amp;nbsp;the&amp;nbsp;computer.&amp;nbsp;So,&amp;nbsp;the&amp;nbsp;software&amp;nbsp;applications&amp;nbsp;are&amp;nbsp;shaped&amp;nbsp;from&amp;nbsp;tasks&amp;nbsp;by&amp;nbsp;humans?&amp;nbsp;Yes!&amp;nbsp;And&amp;nbsp;what&amp;nbsp;are&amp;nbsp;those&amp;nbsp;tasks?&amp;nbsp;Tasks&amp;nbsp;are&amp;nbsp;a&amp;nbsp;list&amp;nbsp;of&amp;nbsp;criteria&amp;nbsp;that&amp;nbsp;the&amp;nbsp;application&amp;nbsp;should&amp;nbsp;do.&amp;nbsp;In&amp;nbsp;software&amp;nbsp;engineering,&amp;nbsp;we&amp;nbsp;have&amp;nbsp;a&amp;nbsp;name&amp;nbsp;for&amp;nbsp;one&amp;nbsp;criterium&amp;nbsp;on&amp;nbsp;the&amp;nbsp;list&amp;nbsp;and&amp;nbsp;it&amp;nbsp;is&amp;nbsp;the&amp;nbsp;Business&amp;nbsp;rule.&amp;nbsp;So,&amp;nbsp;the&amp;nbsp;result&amp;nbsp;of&amp;nbsp;our&amp;nbsp;brainstorm&amp;nbsp;is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Core&amp;nbsp;of&amp;nbsp;application&amp;nbsp;is&amp;nbsp;defined&amp;nbsp;by&amp;nbsp;tasks&amp;nbsp;or&amp;nbsp;Bussiness&amp;nbsp;rules,&amp;nbsp;which&amp;nbsp;application&amp;nbsp;should&amp;nbsp;fulfil.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3nqusqc4qojuz6jw20mx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3nqusqc4qojuz6jw20mx.png" alt="Illustration" width="462" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Onion&amp;nbsp;Architecture
&lt;/h4&gt;

&lt;p&gt;In&amp;nbsp;onion&amp;nbsp;architecture,&amp;nbsp;core&amp;nbsp;consists&amp;nbsp;of&amp;nbsp;two&amp;nbsp;layers;&amp;nbsp;The&amp;nbsp;Domain&amp;nbsp;layer&amp;nbsp;and&amp;nbsp;its&amp;nbsp;wrapper,&amp;nbsp;the&amp;nbsp;Application&amp;nbsp;layer.&lt;/p&gt;

&lt;p&gt;In&amp;nbsp;the&amp;nbsp;domain&amp;nbsp;layer,&amp;nbsp;we&amp;nbsp;will&amp;nbsp;keep&amp;nbsp;entities&amp;nbsp;and&amp;nbsp;value&amp;nbsp;objects&amp;nbsp;of&amp;nbsp;our&amp;nbsp;application.&amp;nbsp;Entity&amp;nbsp;is&amp;nbsp;every&amp;nbsp;class&amp;nbsp;that&amp;nbsp;is&amp;nbsp;describing&amp;nbsp;the&amp;nbsp;behaviour&amp;nbsp;of&amp;nbsp;the&amp;nbsp;object&amp;nbsp;in&amp;nbsp;real-world,&amp;nbsp;which&amp;nbsp;uniqueness&amp;nbsp;is&amp;nbsp;defined&amp;nbsp;by&amp;nbsp;the&amp;nbsp;value&amp;nbsp;of&amp;nbsp;its&amp;nbsp;identifier.&amp;nbsp;For&amp;nbsp;example,&amp;nbsp;human&amp;nbsp;uniqueness&amp;nbsp;is&amp;nbsp;defined&amp;nbsp;by&amp;nbsp;his&amp;nbsp;or&amp;nbsp;her&amp;nbsp;personal&amp;nbsp;identification&amp;nbsp;number.&amp;nbsp;On&amp;nbsp;the&amp;nbsp;other&amp;nbsp;hand,&amp;nbsp;the&amp;nbsp;Value&amp;nbsp;Object’s&amp;nbsp;uniqueness&amp;nbsp;is&amp;nbsp;defined&amp;nbsp;by&amp;nbsp;its&amp;nbsp;value.&amp;nbsp;For&amp;nbsp;example,&amp;nbsp;a&amp;nbsp;phone&amp;nbsp;number&amp;nbsp;or&amp;nbsp;an&amp;nbsp;address.&lt;/p&gt;

&lt;p&gt;Part&amp;nbsp;of&amp;nbsp;the&amp;nbsp;core&amp;nbsp;called&amp;nbsp;Application&amp;nbsp;contains&amp;nbsp;every&amp;nbsp;work&amp;nbsp;done&amp;nbsp;with&amp;nbsp;entities&amp;nbsp;or&amp;nbsp;value&amp;nbsp;objects.&amp;nbsp;In&amp;nbsp;the&amp;nbsp;introduction,&amp;nbsp;I&amp;nbsp;talked&amp;nbsp;about&amp;nbsp;tasks&amp;nbsp;or&amp;nbsp;business&amp;nbsp;rules.&amp;nbsp;Example&amp;nbsp;of&amp;nbsp;business&amp;nbsp;rule&amp;nbsp;could&amp;nbsp;be&amp;nbsp;a&amp;nbsp;task&amp;nbsp;“Save&amp;nbsp;customer.”&amp;nbsp;But&amp;nbsp;wait,&amp;nbsp;such&amp;nbsp;a&amp;nbsp;rule&amp;nbsp;will&amp;nbsp;have&amp;nbsp;to&amp;nbsp;work&amp;nbsp;with&amp;nbsp;the&amp;nbsp;database.&amp;nbsp;And&amp;nbsp;at&amp;nbsp;the&amp;nbsp;introduction,&amp;nbsp;we&amp;nbsp;made&amp;nbsp;it&amp;nbsp;clear&amp;nbsp;that&amp;nbsp;the&amp;nbsp;database&amp;nbsp;should&amp;nbsp;not&amp;nbsp;be&amp;nbsp;a&amp;nbsp;part&amp;nbsp;of&amp;nbsp;the&amp;nbsp;application’s&amp;nbsp;core.&amp;nbsp;So&amp;nbsp;where&amp;nbsp;is&amp;nbsp;the&amp;nbsp;database?&lt;/p&gt;

&lt;p&gt;If you liked this and want more, I'm sending a short free email series on the same kind of thinking — &lt;a href="https://drippery.app/subscribe/6e302973-cf5f-47ff-8803-22122ac3bd9d" rel="noopener noreferrer"&gt;The Claude Code Memory Starter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Database&amp;nbsp;can&amp;nbsp;be&amp;nbsp;found&amp;nbsp;in&amp;nbsp;the&amp;nbsp;Persistence&amp;nbsp;layer,&amp;nbsp;marked&amp;nbsp;by&amp;nbsp;green&amp;nbsp;colour&amp;nbsp;in&amp;nbsp;the&amp;nbsp;picture&amp;nbsp;above.&amp;nbsp;In&amp;nbsp;the&amp;nbsp;picture&amp;nbsp;is&amp;nbsp;an&amp;nbsp;arrow&amp;nbsp;pointing&amp;nbsp;at&amp;nbsp;the&amp;nbsp;dependency&amp;nbsp;of&amp;nbsp;the&amp;nbsp;Persistence&amp;nbsp;layer&amp;nbsp;to&amp;nbsp;the&amp;nbsp;Application&amp;nbsp;layer.&amp;nbsp;More&amp;nbsp;precisely&amp;nbsp;Application&amp;nbsp;layer&amp;nbsp;must&amp;nbsp;not&amp;nbsp;use&amp;nbsp;any&amp;nbsp;classes&amp;nbsp;defined&amp;nbsp;in&amp;nbsp;the&amp;nbsp;Persistence&amp;nbsp;layer.&lt;/p&gt;

&lt;p&gt;How&amp;nbsp;can&amp;nbsp;we&amp;nbsp;even&amp;nbsp;implement&amp;nbsp;“Save&amp;nbsp;customer”&amp;nbsp;task&amp;nbsp;without&amp;nbsp;a&amp;nbsp;possibility&amp;nbsp;of&amp;nbsp;invoking&amp;nbsp;methods&amp;nbsp;of&amp;nbsp;class&amp;nbsp;in&amp;nbsp;Persistence&amp;nbsp;layer?&amp;nbsp;Persistence&amp;nbsp;layer&amp;nbsp;in&amp;nbsp;Onion&amp;nbsp;architecture&amp;nbsp;can&amp;nbsp;be&amp;nbsp;pictured&amp;nbsp;as&amp;nbsp;a&amp;nbsp;Plugin&amp;nbsp;for&amp;nbsp;the&amp;nbsp;Application&amp;nbsp;layer.&amp;nbsp;What&amp;nbsp;do&amp;nbsp;we&amp;nbsp;need&amp;nbsp;to&amp;nbsp;plug&amp;nbsp;in&amp;nbsp;the&amp;nbsp;plugin?&amp;nbsp;Interface.&amp;nbsp;Where&amp;nbsp;will&amp;nbsp;be&amp;nbsp;an&amp;nbsp;Interface?&amp;nbsp;Well,&amp;nbsp;in&amp;nbsp;the&amp;nbsp;core&amp;nbsp;of&amp;nbsp;the&amp;nbsp;application.&amp;nbsp;We&amp;nbsp;will&amp;nbsp;hold&amp;nbsp;our&amp;nbsp;interface&amp;nbsp;in&amp;nbsp;the&amp;nbsp;Application&amp;nbsp;layer&amp;nbsp;and&amp;nbsp;its&amp;nbsp;concrete&amp;nbsp;implementation&amp;nbsp;will&amp;nbsp;be&amp;nbsp;implemented&amp;nbsp;in&amp;nbsp;the&amp;nbsp;layer&amp;nbsp;of&amp;nbsp;the&amp;nbsp;plugin,&amp;nbsp;in&amp;nbsp;our&amp;nbsp;case,&amp;nbsp;in&amp;nbsp;the&amp;nbsp;Persistence&amp;nbsp;layer.&amp;nbsp;This&amp;nbsp;approach&amp;nbsp;is&amp;nbsp;called&amp;nbsp;&lt;a href="https://en.wikipedia.org/wiki/Dependency_inversion_principle" rel="noopener noreferrer"&gt;Dependency&amp;nbsp;inversion&amp;nbsp;principle&lt;/a&gt;.&amp;nbsp;By&amp;nbsp;implementing&amp;nbsp;such&amp;nbsp;an&amp;nbsp;approach,&amp;nbsp;we&amp;nbsp;are&amp;nbsp;achieving&amp;nbsp;database&amp;nbsp;technology&amp;nbsp;independence.&amp;nbsp;Database&amp;nbsp;becomes&amp;nbsp;an&amp;nbsp;implementation&amp;nbsp;detail&amp;nbsp;and&amp;nbsp;our&amp;nbsp;core&amp;nbsp;is&amp;nbsp;completely&amp;nbsp;separated&amp;nbsp;from&amp;nbsp;possible&amp;nbsp;external&amp;nbsp;influences&amp;nbsp;caused&amp;nbsp;by&amp;nbsp;the&amp;nbsp;database.&lt;/p&gt;

&lt;p&gt;With&amp;nbsp;the&amp;nbsp;same&amp;nbsp;motivation,&amp;nbsp;the&amp;nbsp;last&amp;nbsp;layers&amp;nbsp;are&amp;nbsp;created.&amp;nbsp;Presentation&amp;nbsp;layer&amp;nbsp;and&amp;nbsp;the&amp;nbsp;Infrastructure&amp;nbsp;layer.&amp;nbsp;As&amp;nbsp;the&amp;nbsp;name&amp;nbsp;suggests,&amp;nbsp;the&amp;nbsp;Presentation&amp;nbsp;layer&amp;nbsp;is&amp;nbsp;responsible&amp;nbsp;for&amp;nbsp;the&amp;nbsp;implementation&amp;nbsp;of&amp;nbsp;the&amp;nbsp;user&amp;nbsp;interface.&amp;nbsp;Because&amp;nbsp;in&amp;nbsp;everyday&amp;nbsp;world&amp;nbsp;there&amp;nbsp;are&amp;nbsp;growing&amp;nbsp;tendencies&amp;nbsp;to&amp;nbsp;use&amp;nbsp;new&amp;nbsp;JavaScript&amp;nbsp;technology,&amp;nbsp;the&amp;nbsp;separation&amp;nbsp;of&amp;nbsp;the&amp;nbsp;presentation&amp;nbsp;layer&amp;nbsp;from&amp;nbsp;the&amp;nbsp;core&amp;nbsp;of&amp;nbsp;an&amp;nbsp;application&amp;nbsp;is&amp;nbsp;a&amp;nbsp;good&amp;nbsp;idea.&lt;/p&gt;

&lt;p&gt;Separation&amp;nbsp;of&amp;nbsp;the&amp;nbsp;Presentation&amp;nbsp;layer&amp;nbsp;also&amp;nbsp;brings&amp;nbsp;another&amp;nbsp;advantage.&amp;nbsp;There&amp;nbsp;are&amp;nbsp;more&amp;nbsp;and&amp;nbsp;more&amp;nbsp;electronic&amp;nbsp;devices&amp;nbsp;and&amp;nbsp;every&amp;nbsp;developer&amp;nbsp;will&amp;nbsp;agree&amp;nbsp;with&amp;nbsp;me&amp;nbsp;if&amp;nbsp;I&amp;nbsp;say&amp;nbsp;that&amp;nbsp;the&amp;nbsp;Presentation&amp;nbsp;layer&amp;nbsp;will&amp;nbsp;work&amp;nbsp;best&amp;nbsp;if&amp;nbsp;it&amp;nbsp;is&amp;nbsp;developed&amp;nbsp;in&amp;nbsp;native&amp;nbsp;technology.&lt;/p&gt;

&lt;p&gt;Last&amp;nbsp;but&amp;nbsp;not&amp;nbsp;least,&amp;nbsp;the&amp;nbsp;Infrastructure&amp;nbsp;layer.&amp;nbsp;The&amp;nbsp;Infrastructure&amp;nbsp;layer&amp;nbsp;serves&amp;nbsp;for&amp;nbsp;bulking&amp;nbsp;implementations&amp;nbsp;of&amp;nbsp;communications&amp;nbsp;with&amp;nbsp;external&amp;nbsp;technologies.&amp;nbsp;We&amp;nbsp;don’t&amp;nbsp;want&amp;nbsp;to&amp;nbsp;depend&amp;nbsp;on&amp;nbsp;external&amp;nbsp;technologies.&amp;nbsp;For&amp;nbsp;example,&amp;nbsp;it&amp;nbsp;can&amp;nbsp;be&amp;nbsp;a&amp;nbsp;technology&amp;nbsp;responsible&amp;nbsp;for&amp;nbsp;sending&amp;nbsp;emails&amp;nbsp;or&amp;nbsp;file&amp;nbsp;printing.&lt;/p&gt;

&lt;p&gt;Before you go: if you want to keep getting practical posts like this in your inbox, &lt;a href="https://drippery.app/subscribe/6e302973-cf5f-47ff-8803-22122ac3bd9d" rel="noopener noreferrer"&gt;The Claude Code Memory Starter&lt;/a&gt; is a short free series I think you'll enjoy.&lt;/p&gt;

&lt;h4&gt;
  
  
  Summary&amp;nbsp;and&amp;nbsp;useful&amp;nbsp;links
&lt;/h4&gt;

&lt;p&gt;Onion&amp;nbsp;architecture&amp;nbsp;is&amp;nbsp;a&amp;nbsp;concrete&amp;nbsp;implementation&amp;nbsp;draft&amp;nbsp;inspired&amp;nbsp;by&amp;nbsp;book&amp;nbsp;Clean&amp;nbsp;Architecture,&amp;nbsp;which&amp;nbsp;author&amp;nbsp;is&amp;nbsp;&lt;a href="https://cleancoders.com/" rel="noopener noreferrer"&gt;Robert&amp;nbsp;C.&amp;nbsp;Martin&lt;/a&gt;.&amp;nbsp;Mentioned&amp;nbsp;version&amp;nbsp;of&amp;nbsp;Onion&amp;nbsp;architecture&amp;nbsp;was&amp;nbsp;designed&amp;nbsp;by&amp;nbsp;software&amp;nbsp;architect&amp;nbsp;&lt;a href="https://twitter.com/jasongtau" rel="noopener noreferrer"&gt;Jason&amp;nbsp;Taylor&lt;/a&gt;.&amp;nbsp;Complete&amp;nbsp;repository&amp;nbsp;with&amp;nbsp;sample&amp;nbsp;of&amp;nbsp;implementation&amp;nbsp;can&amp;nbsp;be&amp;nbsp;found&amp;nbsp;at&amp;nbsp;&lt;a href="https://github.com/JasonGT/NorthwindTraders" rel="noopener noreferrer"&gt;Github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By&amp;nbsp;using&amp;nbsp;Onion&amp;nbsp;architecture,&amp;nbsp;we&amp;nbsp;can&amp;nbsp;reach&amp;nbsp;the&amp;nbsp;separation&amp;nbsp;of&amp;nbsp;vital&amp;nbsp;rules&amp;nbsp;from&amp;nbsp;the&amp;nbsp;outer&amp;nbsp;world’s&amp;nbsp;technologies.&amp;nbsp;If&amp;nbsp;we&amp;nbsp;don’t&amp;nbsp;tangle&amp;nbsp;into&amp;nbsp;our&amp;nbsp;business&amp;nbsp;rules&amp;nbsp;outer&amp;nbsp;technologies,&amp;nbsp;we&amp;nbsp;can&amp;nbsp;manage&amp;nbsp;the&amp;nbsp;change&amp;nbsp;of&amp;nbsp;technologies&amp;nbsp;more&amp;nbsp;easily.&amp;nbsp;Architecture&amp;nbsp;brings&amp;nbsp;easier&amp;nbsp;use&amp;nbsp;of&amp;nbsp;modern&amp;nbsp;development&amp;nbsp;approaches&amp;nbsp;like&amp;nbsp;&lt;a href="https://en.wikipedia.org/wiki/Domain-driven_design" rel="noopener noreferrer"&gt;Domain-Driven&amp;nbsp;Design&lt;/a&gt;&amp;nbsp;and&amp;nbsp;&lt;a href="https://en.wikipedia.org/wiki/Test-driven_development" rel="noopener noreferrer"&gt;Test-Driven&amp;nbsp;Development&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &amp;nbsp;Related&amp;nbsp;Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  [LINK&amp;nbsp;PLACEHOLDER&amp;nbsp;-&amp;nbsp;&lt;a href="https://medium.com/@danielrusnok/3-domain-centric-architectures-every-software-developer-should-know-a15727ada79f%5D%C2%A0%E2%80%94%C2%A0_3%C2%A0Domain-Centric%C2%A0Architectures%C2%A0Every%C2%A0Software%C2%A0Developer%C2%A0Should%C2%A0Know_" rel="noopener noreferrer"&gt;https://medium.com/@danielrusnok/3-domain-centric-architectures-every-software-developer-should-know-a15727ada79f]&amp;nbsp;—&amp;nbsp;_3&amp;nbsp;Domain-Centric&amp;nbsp;Architectures&amp;nbsp;Every&amp;nbsp;Software&amp;nbsp;Developer&amp;nbsp;Should&amp;nbsp;Know_&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  [LINK&amp;nbsp;PLACEHOLDER&amp;nbsp;-&amp;nbsp;&lt;a href="https://medium.com/@danielrusnok/layers-in-software-architecture-that-every-sofware-architect-should-know-76b2452b9d9a%5D%C2%A0%E2%80%94%C2%A0_Layers%C2%A0in%C2%A0Software%C2%A0Architecture%C2%A0that%C2%A0Every%C2%A0Software%C2%A0Architect%C2%A0should%C2%A0Know_" rel="noopener noreferrer"&gt;https://medium.com/@danielrusnok/layers-in-software-architecture-that-every-sofware-architect-should-know-76b2452b9d9a]&amp;nbsp;—&amp;nbsp;_Layers&amp;nbsp;in&amp;nbsp;Software&amp;nbsp;Architecture&amp;nbsp;that&amp;nbsp;Every&amp;nbsp;Software&amp;nbsp;Architect&amp;nbsp;should&amp;nbsp;Know_&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  [LINK&amp;nbsp;PLACEHOLDER&amp;nbsp;-&amp;nbsp;&lt;a href="https://medium.com/@danielrusnok/let-me-hear-you-screaming-architecture-3adcc02f2ca3%5D%C2%A0%E2%80%94%C2%A0_Functional%C2%A0Organization%C2%A0of%C2%A0Folders%C2%A0&amp;amp;%C2%A0Screaming%C2%A0Architecture_" rel="noopener noreferrer"&gt;https://medium.com/@danielrusnok/let-me-hear-you-screaming-architecture-3adcc02f2ca3]&amp;nbsp;—&amp;nbsp;_Functional&amp;nbsp;Organization&amp;nbsp;of&amp;nbsp;Folders&amp;nbsp;&amp;amp;&amp;nbsp;Screaming&amp;nbsp;Architecture_&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  [LINK&amp;nbsp;PLACEHOLDER&amp;nbsp;-&amp;nbsp;&lt;a href="https://medium.com/@danielrusnok/what-is-anemic-domain-model-and-why-it-can-be-harmful-2677b1b0a79a%5D%C2%A0%E2%80%94%C2%A0_What%C2%A0is%C2%A0Anemic%C2%A0Domain%C2%A0Model%C2%A0and%C2%A0why%C2%A0it%C2%A0can%C2%A0be%C2%A0harmful?_" rel="noopener noreferrer"&gt;https://medium.com/@danielrusnok/what-is-anemic-domain-model-and-why-it-can-be-harmful-2677b1b0a79a]&amp;nbsp;—&amp;nbsp;_What&amp;nbsp;is&amp;nbsp;Anemic&amp;nbsp;Domain&amp;nbsp;Model&amp;nbsp;and&amp;nbsp;why&amp;nbsp;it&amp;nbsp;can&amp;nbsp;be&amp;nbsp;harmful?_&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  [LINK&amp;nbsp;PLACEHOLDER&amp;nbsp;-&amp;nbsp;&lt;a href="https://ai.plainenglish.io/domain-driven-design-is-the-architecture-ai-agents-were-waiting-for-4742509a44db%5D%C2%A0%E2%80%94%C2%A0_Domain-Driven%C2%A0Design%C2%A0Is%C2%A0the%C2%A0Architecture%C2%A0AI%C2%A0Agents%C2%A0Were%C2%A0Waiting%C2%A0For_" rel="noopener noreferrer"&gt;https://ai.plainenglish.io/domain-driven-design-is-the-architecture-ai-agents-were-waiting-for-4742509a44db]&amp;nbsp;—&amp;nbsp;_Domain-Driven&amp;nbsp;Design&amp;nbsp;Is&amp;nbsp;the&amp;nbsp;Architecture&amp;nbsp;AI&amp;nbsp;Agents&amp;nbsp;Were&amp;nbsp;Waiting&amp;nbsp;For_&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>cleanarchitecture</category>
      <category>ddd</category>
      <category>programming</category>
    </item>
    <item>
      <title>My introduction into Azure Service Fabric</title>
      <dc:creator>Digital Craft Workshop</dc:creator>
      <pubDate>Fri, 12 Jun 2026 15:04:33 +0000</pubDate>
      <link>https://dev.to/danielrusnok/my-introduction-into-azure-service-fabric-g46</link>
      <guid>https://dev.to/danielrusnok/my-introduction-into-azure-service-fabric-g46</guid>
      <description>&lt;h4&gt;
  
  
  Azure&amp;nbsp;&amp;amp;&amp;nbsp;Cloud
&lt;/h4&gt;

&lt;h3&gt;
  
  
  My&amp;nbsp;Introduction&amp;nbsp;into&amp;nbsp;Azure&amp;nbsp;Service&amp;nbsp;Fabric
&lt;/h3&gt;

&lt;h4&gt;
  
  
  As&amp;nbsp;a&amp;nbsp;monolithic&amp;nbsp;application&amp;nbsp;developer,&amp;nbsp;I&amp;nbsp;was&amp;nbsp;challenged&amp;nbsp;by&amp;nbsp;my&amp;nbsp;CEO&amp;nbsp;to&amp;nbsp;learn&amp;nbsp;something&amp;nbsp;more&amp;nbsp;about&amp;nbsp;an&amp;nbsp;Azure&amp;nbsp;Service&amp;nbsp;Fabric&amp;nbsp;technology.&amp;nbsp;I&amp;nbsp;would&amp;nbsp;like&amp;nbsp;to&amp;nbsp;share&amp;nbsp;with&amp;nbsp;you&amp;nbsp;what&amp;nbsp;I&amp;nbsp;have&amp;nbsp;learned&amp;nbsp;in&amp;nbsp;the&amp;nbsp;last&amp;nbsp;few&amp;nbsp;weeks.
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq4sn3awz6afs07pqhwld.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq4sn3awz6afs07pqhwld.jpeg" alt="My introduction into Azure Service Fabric cover image" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://mailchi.mp/20486df4bb35/medium-subsribe" rel="noopener noreferrer"&gt;Newsletter&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://mailchi.mp/20486df4bb35/medium-subsribe" rel="noopener noreferrer"&gt;Subscribe&amp;nbsp;and&amp;nbsp;be&amp;nbsp;the&amp;nbsp;first&amp;nbsp;who&amp;nbsp;get&amp;nbsp;noticed&amp;nbsp;when&amp;nbsp;the&amp;nbsp;new&amp;nbsp;article&amp;nbsp;about&amp;nbsp;software&amp;nbsp;development&amp;nbsp;comes&amp;nbsp;out!&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-overview" rel="noopener noreferrer"&gt;Azure&amp;nbsp;Service&amp;nbsp;Fabric&lt;/a&gt;&amp;nbsp;is&amp;nbsp;a&amp;nbsp;platform&amp;nbsp;created&amp;nbsp;by&amp;nbsp;Microsoft&amp;nbsp;that&amp;nbsp;facilitates&amp;nbsp;creating&amp;nbsp;and&amp;nbsp;deploying&amp;nbsp;microservice-based&amp;nbsp;architecture&amp;nbsp;systems.&amp;nbsp;It&amp;nbsp;provides&amp;nbsp;scalability,&amp;nbsp;reliability,&amp;nbsp;and&amp;nbsp;availability&amp;nbsp;for&amp;nbsp;your&amp;nbsp;system.&lt;/p&gt;

&lt;p&gt;If&amp;nbsp;you&amp;nbsp;are&amp;nbsp;not&amp;nbsp;familiar&amp;nbsp;with&amp;nbsp;Cloud&amp;nbsp;Computing,&amp;nbsp;here&amp;nbsp;is&amp;nbsp;my&amp;nbsp;Introduction&amp;nbsp;to&amp;nbsp;Cloud&amp;nbsp;Computing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://levelup.gitconnected.com/introduction-to-cloud-computing-235e530b9fe0" rel="noopener noreferrer"&gt;Introduction&amp;nbsp;to&amp;nbsp;Cloud&amp;nbsp;Computing&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://levelup.gitconnected.com/introduction-to-cloud-computing-235e530b9fe0" rel="noopener noreferrer"&gt;Cloud&amp;nbsp;computing&amp;nbsp;is&amp;nbsp;the&amp;nbsp;present&amp;nbsp;and&amp;nbsp;future&amp;nbsp;of&amp;nbsp;software&amp;nbsp;utilization,&amp;nbsp;development&amp;nbsp;and&amp;nbsp;hosting.&amp;nbsp;Let&amp;nbsp;me&amp;nbsp;introduce&amp;nbsp;you&amp;nbsp;its…&lt;/a&gt;&lt;/em&gt;&lt;a href="https://levelup.gitconnected.com/introduction-to-cloud-computing-235e530b9fe0" rel="noopener noreferrer"&gt;levelup.gitconnected.com&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Microservice&amp;nbsp;architecture
&lt;/h3&gt;

&lt;p&gt;If&amp;nbsp;you&amp;nbsp;want&amp;nbsp;to&amp;nbsp;use&amp;nbsp;Azure&amp;nbsp;Service&amp;nbsp;Fabric,&amp;nbsp;first&amp;nbsp;you&amp;nbsp;must&amp;nbsp;understand&amp;nbsp;what&amp;nbsp;microservice&amp;nbsp;architecture&amp;nbsp;represents.&lt;/p&gt;

&lt;p&gt;Microservice&amp;nbsp;architecture&amp;nbsp;is&amp;nbsp;based&amp;nbsp;on&amp;nbsp;multiple&amp;nbsp;independent&amp;nbsp;applications(services),&amp;nbsp;which&amp;nbsp;are&amp;nbsp;communicating&amp;nbsp;mostly&amp;nbsp;via&amp;nbsp;their&amp;nbsp;public&amp;nbsp;API.&amp;nbsp;Service&amp;nbsp;should&amp;nbsp;not&amp;nbsp;see&amp;nbsp;implementation&amp;nbsp;details&amp;nbsp;of&amp;nbsp;other&amp;nbsp;services&amp;nbsp;and&amp;nbsp;vice&amp;nbsp;versa.&amp;nbsp;Microservices&amp;nbsp;provide&amp;nbsp;scalability&amp;nbsp;by&amp;nbsp;design&amp;nbsp;because&amp;nbsp;the&amp;nbsp;system&amp;nbsp;load&amp;nbsp;is&amp;nbsp;divided&amp;nbsp;into&amp;nbsp;more&amp;nbsp;“subsystems”.&lt;/p&gt;

&lt;p&gt;Systems&amp;nbsp;like&amp;nbsp;these&amp;nbsp;are&amp;nbsp;harder&amp;nbsp;to&amp;nbsp;maintain&amp;nbsp;than&amp;nbsp;monolithic&amp;nbsp;based&amp;nbsp;systems&amp;nbsp;because&amp;nbsp;the&amp;nbsp;developer&amp;nbsp;must&amp;nbsp;think&amp;nbsp;a&amp;nbsp;little&amp;nbsp;bit&amp;nbsp;differently&amp;nbsp;than&amp;nbsp;he&amp;nbsp;was&amp;nbsp;used&amp;nbsp;to.&amp;nbsp;Every&amp;nbsp;service&amp;nbsp;is&amp;nbsp;a&amp;nbsp;self-based&amp;nbsp;system&amp;nbsp;that&amp;nbsp;should&amp;nbsp;run&amp;nbsp;by&amp;nbsp;itself&amp;nbsp;without&amp;nbsp;relying&amp;nbsp;on&amp;nbsp;other&amp;nbsp;systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cluster
&lt;/h3&gt;

&lt;p&gt;Every&amp;nbsp;Azure&amp;nbsp;Service&amp;nbsp;Fabric&amp;nbsp;project&amp;nbsp;is&amp;nbsp;made&amp;nbsp;of&amp;nbsp;things&amp;nbsp;called&amp;nbsp;Cluster.&amp;nbsp;Cluster&amp;nbsp;is&amp;nbsp;a&amp;nbsp;bulk&amp;nbsp;of&amp;nbsp;resources&amp;nbsp;(Load&amp;nbsp;balancer,&amp;nbsp;applications,&amp;nbsp;IP&amp;nbsp;addresses)&amp;nbsp;needed&amp;nbsp;for&amp;nbsp;the&amp;nbsp;correct&amp;nbsp;functioning&amp;nbsp;of&amp;nbsp;an&amp;nbsp;application.&amp;nbsp;Cluster&amp;nbsp;is&amp;nbsp;made&amp;nbsp;of&amp;nbsp;so-called&amp;nbsp;Nodes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Node
&lt;/h3&gt;

&lt;p&gt;Nodes&amp;nbsp;are&amp;nbsp;nothing&amp;nbsp;more&amp;nbsp;than&amp;nbsp;usual&amp;nbsp;computers&amp;nbsp;or&amp;nbsp;virtual&amp;nbsp;machines.&amp;nbsp;They&amp;nbsp;use&amp;nbsp;an&amp;nbsp;operating&amp;nbsp;system&amp;nbsp;such&amp;nbsp;as&amp;nbsp;Windows&amp;nbsp;or&amp;nbsp;Linux&amp;nbsp;and&amp;nbsp;have&amp;nbsp;installed&amp;nbsp;an&amp;nbsp;Azure&amp;nbsp;Service&amp;nbsp;Fabric&amp;nbsp;Runtime.&amp;nbsp;The&amp;nbsp;cluster&amp;nbsp;can&amp;nbsp;contain&amp;nbsp;1:N&amp;nbsp;nodes.&amp;nbsp;These&amp;nbsp;nodes&amp;nbsp;are&amp;nbsp;connected&amp;nbsp;to&amp;nbsp;each&amp;nbsp;other&amp;nbsp;in&amp;nbsp;a&amp;nbsp;network-based&amp;nbsp;communication.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2hgfa60028hdy8jyr5nz.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2hgfa60028hdy8jyr5nz.jpeg" alt="Illustration" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The&amp;nbsp;main&amp;nbsp;purpose&amp;nbsp;of&amp;nbsp;the&amp;nbsp;node&amp;nbsp;is&amp;nbsp;to&amp;nbsp;host&amp;nbsp;your&amp;nbsp;application.&amp;nbsp;One&amp;nbsp;node&amp;nbsp;could&amp;nbsp;host&amp;nbsp;multiple&amp;nbsp;applications&amp;nbsp;which&amp;nbsp;means&amp;nbsp;every&amp;nbsp;cluster&amp;nbsp;can&amp;nbsp;host&amp;nbsp;unlimited&amp;nbsp;amounts&amp;nbsp;of&amp;nbsp;application,&amp;nbsp;as&amp;nbsp;long&amp;nbsp;as&amp;nbsp;there&amp;nbsp;are&amp;nbsp;physical&amp;nbsp;resources&amp;nbsp;up&amp;nbsp;to&amp;nbsp;it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application
&lt;/h3&gt;

&lt;p&gt;The&amp;nbsp;result&amp;nbsp;of&amp;nbsp;Azure&amp;nbsp;Service&amp;nbsp;Fabric&amp;nbsp;solution&amp;nbsp;is&amp;nbsp;an&amp;nbsp;application.&amp;nbsp;An&amp;nbsp;application&amp;nbsp;is&amp;nbsp;nothing&amp;nbsp;more&amp;nbsp;than&amp;nbsp;a&amp;nbsp;declaration&amp;nbsp;what&amp;nbsp;does&amp;nbsp;solution&amp;nbsp;contains.&amp;nbsp;An&amp;nbsp;application&amp;nbsp;describes&amp;nbsp;which&amp;nbsp;services&amp;nbsp;it&amp;nbsp;contains.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stateful&amp;nbsp;and&amp;nbsp;Stateless&amp;nbsp;service
&lt;/h3&gt;

&lt;p&gt;Stateless&amp;nbsp;service&amp;nbsp;is,&amp;nbsp;as&amp;nbsp;title&amp;nbsp;hints,&amp;nbsp;a&amp;nbsp;service&amp;nbsp;without&amp;nbsp;any&amp;nbsp;state.&amp;nbsp;Simply&amp;nbsp;speaking,&amp;nbsp;stateless&amp;nbsp;service&amp;nbsp;does&amp;nbsp;not&amp;nbsp;have&amp;nbsp;any&amp;nbsp;storage&amp;nbsp;to&amp;nbsp;save&amp;nbsp;the&amp;nbsp;state&amp;nbsp;of&amp;nbsp;data.&amp;nbsp;As&amp;nbsp;a&amp;nbsp;stateless&amp;nbsp;service&amp;nbsp;can&amp;nbsp;be&amp;nbsp;also&amp;nbsp;understood&amp;nbsp;a&amp;nbsp;project&amp;nbsp;with&amp;nbsp;database&amp;nbsp;connection.&amp;nbsp;How&amp;nbsp;is&amp;nbsp;it&amp;nbsp;possible?&lt;/p&gt;

&lt;p&gt;A&amp;nbsp;project&amp;nbsp;with&amp;nbsp;a&amp;nbsp;database&amp;nbsp;connection&amp;nbsp;is&amp;nbsp;not&amp;nbsp;storing&amp;nbsp;data.&amp;nbsp;The&amp;nbsp;database&amp;nbsp;is&amp;nbsp;storing&amp;nbsp;data.&amp;nbsp;Storing&amp;nbsp;data&amp;nbsp;means&amp;nbsp;saving&amp;nbsp;the&amp;nbsp;state.&lt;/p&gt;

&lt;p&gt;On&amp;nbsp;the&amp;nbsp;other&amp;nbsp;hand,&amp;nbsp;Stateful&amp;nbsp;service&amp;nbsp;can&amp;nbsp;store&amp;nbsp;data&amp;nbsp;in&amp;nbsp;special&amp;nbsp;so-called&amp;nbsp;Reliable&amp;nbsp;Collections.&amp;nbsp;I&amp;nbsp;will&amp;nbsp;provide&amp;nbsp;more&amp;nbsp;information&amp;nbsp;about&amp;nbsp;the&amp;nbsp;reliable&amp;nbsp;collection&amp;nbsp;in&amp;nbsp;the&amp;nbsp;next&amp;nbsp;article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Actor&amp;nbsp;service
&lt;/h3&gt;

&lt;p&gt;As&amp;nbsp;a&amp;nbsp;special&amp;nbsp;kind&amp;nbsp;of&amp;nbsp;Stateful&amp;nbsp;service&amp;nbsp;performs&amp;nbsp;Actor&amp;nbsp;service.&amp;nbsp;Actor&amp;nbsp;service&amp;nbsp;is&amp;nbsp;service&amp;nbsp;based&amp;nbsp;on&amp;nbsp;the&amp;nbsp;Actor&amp;nbsp;Model.&amp;nbsp;The&amp;nbsp;Actor&amp;nbsp;Model&amp;nbsp;is&amp;nbsp;not&amp;nbsp;a&amp;nbsp;product&amp;nbsp;of&amp;nbsp;Azure&amp;nbsp;Fabric&amp;nbsp;Service&amp;nbsp;and&amp;nbsp;it&amp;nbsp;is&amp;nbsp;not&amp;nbsp;invented&amp;nbsp;by&amp;nbsp;Microsoft&amp;nbsp;either.&amp;nbsp;The&amp;nbsp;Actor&amp;nbsp;Model&amp;nbsp;Theory&amp;nbsp;is&amp;nbsp;a&amp;nbsp;simplification&amp;nbsp;of&amp;nbsp;multithreading&amp;nbsp;programming.&amp;nbsp;You&amp;nbsp;can&amp;nbsp;find&amp;nbsp;detailed&amp;nbsp;information&amp;nbsp;about&amp;nbsp;the&amp;nbsp;Actor&amp;nbsp;Model&amp;nbsp;&lt;a href="https://www.youtube.com/watch?v=7erJ1DV_Tlo" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;How&amp;nbsp;I&amp;nbsp;think&amp;nbsp;about&amp;nbsp;Actor&amp;nbsp;service&amp;nbsp;is&amp;nbsp;that&amp;nbsp;there&amp;nbsp;are&amp;nbsp;multiple&amp;nbsp;instances&amp;nbsp;of&amp;nbsp;service&amp;nbsp;with&amp;nbsp;own&amp;nbsp;storage&amp;nbsp;per&amp;nbsp;unit&amp;nbsp;that&amp;nbsp;you&amp;nbsp;desire.&amp;nbsp;For&amp;nbsp;example,&amp;nbsp;one&amp;nbsp;user&amp;nbsp;of&amp;nbsp;your&amp;nbsp;application&amp;nbsp;can&amp;nbsp;have&amp;nbsp;his&amp;nbsp;instance&amp;nbsp;of&amp;nbsp;Actor&amp;nbsp;service.&lt;/p&gt;

&lt;p&gt;Every&amp;nbsp;Actor&amp;nbsp;service&amp;nbsp;must&amp;nbsp;be&amp;nbsp;provided&amp;nbsp;with&amp;nbsp;identification.&amp;nbsp;It&amp;nbsp;may&amp;nbsp;be&amp;nbsp;string,&amp;nbsp;it&amp;nbsp;may&amp;nbsp;be&amp;nbsp;guid,&amp;nbsp;etc.&amp;nbsp;If&amp;nbsp;it&amp;nbsp;can&amp;nbsp;be&amp;nbsp;string,&amp;nbsp;it&amp;nbsp;can&amp;nbsp;be&amp;nbsp;mostly&amp;nbsp;every&amp;nbsp;casted&amp;nbsp;primitive&amp;nbsp;type&amp;nbsp;of&amp;nbsp;variable&amp;nbsp;you&amp;nbsp;want&amp;nbsp;to&amp;nbsp;use.&lt;/p&gt;

&lt;p&gt;If&amp;nbsp;you&amp;nbsp;think&amp;nbsp;about&amp;nbsp;it&amp;nbsp;you&amp;nbsp;can&amp;nbsp;find&amp;nbsp;out,&amp;nbsp;that&amp;nbsp;it&amp;nbsp;can&amp;nbsp;nicely&amp;nbsp;handle&amp;nbsp;one&amp;nbsp;to&amp;nbsp;many&amp;nbsp;relationships&amp;nbsp;between&amp;nbsp;entities.&amp;nbsp;If&amp;nbsp;there&amp;nbsp;is&amp;nbsp;always&amp;nbsp;one&amp;nbsp;instance&amp;nbsp;of&amp;nbsp;service&amp;nbsp;per&amp;nbsp;user&amp;nbsp;ID,&amp;nbsp;there&amp;nbsp;is&amp;nbsp;also&amp;nbsp;dedicated&amp;nbsp;storage&amp;nbsp;for&amp;nbsp;one&amp;nbsp;user.&amp;nbsp;It&amp;nbsp;is&amp;nbsp;one&amp;nbsp;to&amp;nbsp;many&amp;nbsp;relationships&amp;nbsp;solved&amp;nbsp;at&amp;nbsp;the&amp;nbsp;virtual&amp;nbsp;or&amp;nbsp;physical&amp;nbsp;machine&amp;nbsp;level.&lt;/p&gt;

&lt;p&gt;So&amp;nbsp;instead&amp;nbsp;of&amp;nbsp;creating&amp;nbsp;queries&amp;nbsp;using&amp;nbsp;where&amp;nbsp;clause&amp;nbsp;with&amp;nbsp;user&amp;nbsp;ID&amp;nbsp;condition&amp;nbsp;you&amp;nbsp;just&amp;nbsp;create&amp;nbsp;an&amp;nbsp;instance&amp;nbsp;of&amp;nbsp;the&amp;nbsp;whole&amp;nbsp;service.&amp;nbsp;And&amp;nbsp;that&amp;nbsp;is&amp;nbsp;what&amp;nbsp;I&amp;nbsp;find&amp;nbsp;very&amp;nbsp;powerful&amp;nbsp;as&amp;nbsp;a&amp;nbsp;developer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.danielrusnok.com/daniel-rusnoks-newsletter" rel="noopener noreferrer"&gt;Daniel&amp;nbsp;Rusnok's&amp;nbsp;Newsletter&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://www.danielrusnok.com/daniel-rusnoks-newsletter" rel="noopener noreferrer"&gt;Every&amp;nbsp;month&amp;nbsp;I&amp;nbsp;will&amp;nbsp;send&amp;nbsp;you&amp;nbsp;an&amp;nbsp;email&amp;nbsp;about&amp;nbsp;with&amp;nbsp;list&amp;nbsp;of&amp;nbsp;my&amp;nbsp;newest&amp;nbsp;articles.&amp;nbsp;It&amp;nbsp;will&amp;nbsp;be&amp;nbsp;ofcourse&amp;nbsp;the&amp;nbsp;friendly&amp;nbsp;links…&lt;/a&gt;&lt;/em&gt;&lt;a href="https://www.danielrusnok.com/daniel-rusnoks-newsletter" rel="noopener noreferrer"&gt;www.danielrusnok.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.buymeacoffee.com/danielrusnok" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5tm54rf5555ym89o2ne4.png" alt="Illustration" width="200" height="56"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://itixo.com/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffq3mxusyxmg4a7ry63sq.png" alt="Illustration" width="90" height="48"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &amp;nbsp;Related&amp;nbsp;Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  [LINK&amp;nbsp;PLACEHOLDER&amp;nbsp;-&amp;nbsp;&lt;a href="https://medium.com/@danielrusnok/introduction-to-cloud-computing-235e530b9fe0%5D%C2%A0%E2%80%94%C2%A0_Introduction%C2%A0to%C2%A0Cloud%C2%A0Computing_" rel="noopener noreferrer"&gt;https://medium.com/@danielrusnok/introduction-to-cloud-computing-235e530b9fe0]&amp;nbsp;—&amp;nbsp;_Introduction&amp;nbsp;to&amp;nbsp;Cloud&amp;nbsp;Computing_&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  [LINK&amp;nbsp;PLACEHOLDER&amp;nbsp;-&amp;nbsp;&lt;a href="https://medium.com/@danielrusnok/from-monolith-to-microservices-in-5-minutes-83069677d021%5D%C2%A0%E2%80%94%C2%A0_From%C2%A0Monolith%C2%A0to%C2%A0Microservices%C2%A0in%C2%A05%C2%A0Minutes_" rel="noopener noreferrer"&gt;https://medium.com/@danielrusnok/from-monolith-to-microservices-in-5-minutes-83069677d021]&amp;nbsp;—&amp;nbsp;_From&amp;nbsp;Monolith&amp;nbsp;to&amp;nbsp;Microservices&amp;nbsp;in&amp;nbsp;5&amp;nbsp;Minutes_&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  [LINK&amp;nbsp;PLACEHOLDER&amp;nbsp;-&amp;nbsp;&lt;a href="https://medium.com/@danielrusnok/how-to-implement-azure-functions-in-any-language-with-custom-handlers-78e627264ccb%5D%C2%A0%E2%80%94%C2%A0_How%C2%A0to%C2%A0Implement%C2%A0Azure%C2%A0Functions%C2%A0in%C2%A0any%C2%A0Language%C2%A0with%C2%A0Custom%C2%A0Handlers_" rel="noopener noreferrer"&gt;https://medium.com/@danielrusnok/how-to-implement-azure-functions-in-any-language-with-custom-handlers-78e627264ccb]&amp;nbsp;—&amp;nbsp;_How&amp;nbsp;to&amp;nbsp;Implement&amp;nbsp;Azure&amp;nbsp;Functions&amp;nbsp;in&amp;nbsp;any&amp;nbsp;Language&amp;nbsp;with&amp;nbsp;Custom&amp;nbsp;Handlers_&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  [LINK&amp;nbsp;PLACEHOLDER&amp;nbsp;-&amp;nbsp;&lt;a href="https://medium.com/@danielrusnok/understanding-the-azure-resource-groups-and-azure-resources-d89ce92d25a6%5D%C2%A0%E2%80%94%C2%A0_Understanding%C2%A0the%C2%A0Azure%C2%A0Resource%C2%A0Groups%C2%A0and%C2%A0Azure%C2%A0Resources_" rel="noopener noreferrer"&gt;https://medium.com/@danielrusnok/understanding-the-azure-resource-groups-and-azure-resources-d89ce92d25a6]&amp;nbsp;—&amp;nbsp;_Understanding&amp;nbsp;the&amp;nbsp;Azure&amp;nbsp;Resource&amp;nbsp;Groups&amp;nbsp;and&amp;nbsp;Azure&amp;nbsp;Resources_&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>azure</category>
      <category>csharp</category>
      <category>microservices</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>3 Things That Bit Me Before My Claude Code Memory Setup Worked</title>
      <dc:creator>Digital Craft Workshop</dc:creator>
      <pubDate>Fri, 12 Jun 2026 05:34:50 +0000</pubDate>
      <link>https://dev.to/danielrusnok/3-things-that-bit-me-before-my-claude-code-memory-setup-worked-10be</link>
      <guid>https://dev.to/danielrusnok/3-things-that-bit-me-before-my-claude-code-memory-setup-worked-10be</guid>
      <description>&lt;p&gt;&lt;em&gt;Not a member? &lt;a href="https://medium.com/@danielrusnok/membership" rel="noopener noreferrer"&gt;Use this link.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I gave Claude Code a memory, and within a couple of days it had eaten every credit on one of my API keys.&lt;/p&gt;

&lt;p&gt;That was version three. Version one had built a loop that triggered itself. Version two had my laptop fan screaming every time I closed a terminal. None of them were the clean recall-in, capture-out setup I run now — they were the three wrecks I drove on the way to it.&lt;/p&gt;

&lt;p&gt;So this is the part I left out of the clean write-up. &lt;strong&gt;Three things that bit me, and what each one is actually warning you about if you go build memory into Claude Code yourself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;None of them were typos or a missing semicolon. Each one looked completely reasonable at the moment I wrote it. That's what makes them worth writing down — these are the mistakes that don't announce themselves until they're running.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The hook that triggered itself
&lt;/h2&gt;

&lt;p&gt;My capture step runs on &lt;code&gt;SessionEnd&lt;/code&gt;. When I close a Claude Code session, a hook fires, the session's decisions get extracted, and they get written to the memory store. Simple enough.&lt;/p&gt;

&lt;p&gt;The first version extracted those decisions by spawning a Claude process to read the transcript. Which meant the capture step was itself a Claude session. Which meant &lt;em&gt;it&lt;/em&gt; ended too. Which fired &lt;code&gt;SessionEnd&lt;/code&gt; again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The hook was extracting the act of extracting.&lt;/strong&gt; Each meta-session closed, triggered another meta-session to capture &lt;em&gt;its&lt;/em&gt; decisions, and that one closed and triggered the next. A snake eating its own tail, one link at a time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnst1skpvwyyq9owt6153.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnst1skpvwyyq9owt6153.jpg" alt="A SessionEnd hook spawns a Claude process that is itself a session, which fires SessionEnd again" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It did not run away to infinity, because other limits caught it first. But it ran far enough to scare me, and the lesson stuck harder than most: &lt;strong&gt;a capture process must never be the same kind of thing as the event that triggers capture.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your "extract what happened" step is itself a session, and sessions are what trigger extraction, you have a recursion with no base case that you didn't write on purpose.&lt;/p&gt;

&lt;p&gt;The fix is to break the type. The capture process has to be something the lifecycle doesn't watch — a plain script, a detached subprocess, anything that isn't itself a session. Once the extractor stopped being a Claude session, the loop had nothing left to feed on.&lt;/p&gt;

&lt;p&gt;This is the kind of bug that's obvious in hindsight and invisible while you're writing it, because at write-time "just have Claude read the transcript and pull out the decisions" is the most natural instruction in the world. The recursion is hiding in the word "session," and you don't see it until it's spinning.&lt;/p&gt;

&lt;p&gt;I'd actually been bitten by a cousin of this before — an earlier version of the memory setup spawned seven parallel Claude instances from one "helpful" hook. The full story of that first build is in &lt;a href="https://generativeai.pub/how-i-added-persistent-semantic-memory-to-claude-code-in-15-minutes-9b91f9399a76" rel="noopener noreferrer"&gt;how I added persistent semantic memory to Claude Code in 15 minutes&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The embedder that cooked my laptop
&lt;/h2&gt;

&lt;p&gt;Second version. The loop was gone, capture worked, and then I noticed my laptop fan spinning up every single time I closed a session.&lt;/p&gt;

&lt;p&gt;The culprit was where I'd put the embedding step. I was generating embeddings locally with &lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt;, a small sentence-transformer model. The word "small" is carrying a lot of weight in that sentence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Small for a model is not small for something that runs on every SessionEnd.&lt;/strong&gt; The model file is around 80 MB. Loading it meant spinning up a Python runtime, pulling the weights into memory, and running the encode — and the whole spawn footprint landed near 500 MB of RAM and a real bite of CPU, every time I closed a terminal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffgm63zizdudikc6436mn.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffgm63zizdudikc6436mn.jpg" alt="Simulated top output: a python process at high CPU and ~500MB RES, spawned at session close" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On a desktop you might never notice. On a laptop, closing several sessions an evening, you notice. The machine ran warm, the fan ran loud, and the cause was a memory feature I'd added to make my life easier, quietly taxing the machine every time I stepped away from it.&lt;/p&gt;

&lt;p&gt;The thing to internalize here isn't "MiniLM is bad." MiniLM is fine. &lt;strong&gt;It's that the cost of a memory pipeline is dominated by where the heavy steps run, and how often they run there.&lt;/strong&gt; Embedding locally, on CPU, on every session close, is a lot of repeated heavy lifting for a background nicety nobody asked to be expensive.&lt;/p&gt;

&lt;p&gt;The move is to get the heavy step out of the hot path. Push embedding to a hosted endpoint, or batch it into one job that runs on a timer instead of once-per-close, or skip local embedding on a laptop entirely. The decision that matters is placement and frequency. The model name barely registers next to it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want the Claude Code memory setup I build with? &lt;a href="https://drippery.app/subscribe/6e302973-cf5f-47ff-8803-22122ac3bd9d" rel="noopener noreferrer"&gt;The Claude Code Memory Starter&lt;/a&gt; is a free email series.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The frontier model that quietly ate my credits
&lt;/h2&gt;

&lt;p&gt;The extraction step — the part that reads a transcript and answers "what did we actually decide here" — needs a language model to do it. When I first wired it, I pointed it at the model I already had keys for: &lt;strong&gt;Claude Sonnet.&lt;/strong&gt; A frontier model, on a key I was also using for a production app.&lt;/p&gt;

&lt;p&gt;One transcript through Sonnet is cheap. The problem was the multiplier I'd stopped thinking about. &lt;strong&gt;At the end of an evening I close sessions in a burst&lt;/strong&gt; — three, four, five at once — and each close fired its own extraction against that same shared key.&lt;/p&gt;

&lt;p&gt;Within a couple of days the credits on it were gone. Not because extraction is expensive in principle, but because I'd wired the most expensive available model to the most frequent available trigger, drawing from a budget that something real depended on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ygega70pxvnvt9kbr2j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ygega70pxvnvt9kbr2j.jpg" alt="Before and after: SessionEnd to Sonnet on a shared prod key drains credits; to Gemini Flash Lite on its own key, free" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fix had two halves, and both are worth stealing.&lt;/p&gt;

&lt;p&gt;First, the right model for the job. &lt;strong&gt;Transcript extraction is not a frontier-model task.&lt;/strong&gt; You're asking it to summarize the decisions in a block of text, not to reason about a hard problem. A free-tier Gemini Flash Lite handles it without complaint, and a small OpenAI model — cheaper than Haiku — sits in as a fallback for the days the free tier hits its rate limit. For this specific job the quality difference is nil and the cost difference is the whole ballgame.&lt;/p&gt;

&lt;p&gt;This is the same instinct I wrote up separately: when a cheap model clears the bar, don't quietly reach for a pricier one. More on that in &lt;a href="https://medium.com/generative-ai/i-automated-away-an-hour-of-project-setup-with-a-claude-code-skill-1ff025e43814" rel="noopener noreferrer"&gt;how a Claude Code skill turns an hour of setup into 90 seconds&lt;/a&gt; — same lens, different chore.&lt;/p&gt;

&lt;p&gt;Second, the key. I'd been extracting on the same Anthropic key my production app billed against, so a runaway background hook and a live product were both pulling from one budget. Separating them — memory extraction on its own provider, production on its own — means a mistake in the plumbing can't take down the thing that actually pays for itself.&lt;/p&gt;

&lt;p&gt;So the rule, in one line: &lt;strong&gt;match the model to the difficulty of the task, and never let a background convenience share a budget with something that matters.&lt;/strong&gt; A hook firing on every session close is exactly the kind of process that belongs on the cheapest model that clears the bar, on a key that can fail quietly without taking anything else with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the three have in common
&lt;/h2&gt;

&lt;p&gt;A loop, a hot fan, a drained wallet. They look like three unrelated failures. They're the same one in three disguises.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every one of them came from underestimating how often a session-lifecycle hook fires.&lt;/strong&gt; A thing that runs "when I close a session" doesn't run occasionally. It runs constantly, in bursts, in the background, while your attention is on something else entirely. Whatever you put in that path — a process type, a model load, an API call — gets multiplied by a number you are not currently looking at.&lt;/p&gt;

&lt;p&gt;The setup I run now is boring for exactly that reason: nothing heavy lives in the hot path anymore. Extraction is detached from the session. The model is cheap and isolated on its own key. The embedding doesn't fight my CPU. None of it can loop, cook, or bill, because the expensive parts were moved out of the path that fires the most.&lt;/p&gt;

&lt;p&gt;If you build memory into Claude Code, that's the lens I'd hand you. The question to keep asking isn't &lt;strong&gt;"does this work when I run it once."&lt;/strong&gt; It's &lt;strong&gt;"what does this cost when it runs in the background, in a burst, while I'm not watching."&lt;/strong&gt; Get that one right and the memory layer disappears into the background, which is the only place it's any use.&lt;/p&gt;

&lt;p&gt;The clean wiring all of this eventually became — the recall-in, capture-out shape, end to end — is its own write-up, linked below, if you'd rather read the version that works than the three that didn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://generativeai.pub/how-i-wired-persistent-memory-into-every-claude-code-session-001970318cd5" rel="noopener noreferrer"&gt;How I wired persistent memory into every Claude Code session&lt;/a&gt; — the clean version this post is the blooper reel for&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://generativeai.pub/how-i-added-persistent-semantic-memory-to-claude-code-in-15-minutes-9b91f9399a76" rel="noopener noreferrer"&gt;How I added persistent semantic memory to Claude Code in 15 minutes&lt;/a&gt; — the first build, including the seven-parallel-instances hook&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://code.claude.com/docs/en/hooks" rel="noopener noreferrer"&gt;Claude Code hooks reference&lt;/a&gt; — lifecycle events and JSON schema (official docs)&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2" rel="noopener noreferrer"&gt;all-MiniLM-L6-v2&lt;/a&gt; — the local embedding model in question&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://github.com/mem0ai/mem0" rel="noopener noreferrer"&gt;mem0&lt;/a&gt; — the memory layer this builds on (GitHub)&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://ai.google.dev/gemini-api/docs" rel="noopener noreferrer"&gt;Gemini API docs&lt;/a&gt; — Flash Lite rates and free tier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Want the Claude Code memory setup I build with? &lt;a href="https://drippery.app/subscribe/6e302973-cf5f-47ff-8803-22122ac3bd9d" rel="noopener noreferrer"&gt;The Claude Code Memory Starter&lt;/a&gt; is a free email series.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Captions That Read Along, One Word at a Time</title>
      <dc:creator>Digital Craft Workshop</dc:creator>
      <pubDate>Wed, 10 Jun 2026 15:55:26 +0000</pubDate>
      <link>https://dev.to/danielrusnok/captions-that-read-along-one-word-at-a-time-3alg</link>
      <guid>https://dev.to/danielrusnok/captions-that-read-along-one-word-at-a-time-3alg</guid>
      <description>&lt;p&gt;One evening, the reel looks perfect — until I read along. My voice says "memory," and the word glowing on screen is "billing." One word ahead. The whole reel, every scene, the highlight runs ahead of me, like a bad karaoke machine.&lt;/p&gt;

&lt;p&gt;The fix was one character. Finding it took longer than it should have.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://danielrusnok.substack.com/p/why-i-shipped-the-free-tts-i-dont" rel="noopener noreferrer"&gt;Part 3&lt;/a&gt; ended with a &lt;code&gt;SceneAudio&lt;/code&gt; object — an audio clip, and a list of words, each with a &lt;code&gt;start&lt;/code&gt; and &lt;code&gt;end&lt;/code&gt; in seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SceneAudio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;audioPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;words&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;word&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}[];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This post is about how that data gets spent. It colors each word the moment the voice reaches it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why captions at all
&lt;/h2&gt;

&lt;p&gt;Reels are watched on mute. Most of the feed scrolls past with the sound off — on a train, in a meeting, next to a sleeping kid. Without the words on screen, it's just images changing with no context.&lt;/p&gt;

&lt;p&gt;Captions carry the reel. For most people who see it, they're its only voice. Static captions — a block that sits there for the whole scene — are the lazy version.&lt;/p&gt;

&lt;p&gt;The eye reads the whole block at once, then leaves.&lt;/p&gt;

&lt;p&gt;Word-by-word highlighting fixes that. The color fill is a progress bar your eye can't look away from. It pulls you word to word at the speed it's spoken.&lt;/p&gt;

&lt;p&gt;That's the karaoke effect — the part of the caption stage that actually earns its place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why ASS, not a caption library
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/SubStation_Alpha" rel="noopener noreferrer"&gt;ASS&lt;/a&gt; — Advanced SubStation Alpha — is a subtitle format: a plain text file describing which words show, when, and how they're styled. &lt;a href="https://github.com/libass/libass" rel="noopener noreferrer"&gt;libass&lt;/a&gt; renders it, and FFmpeg already speaks libass.&lt;/p&gt;

&lt;p&gt;My first instinct, though, wasn't ASS. It was a caption library — a React renderer, a canvas animator, one of the npm packages built for animated subtitles.&lt;/p&gt;

&lt;p&gt;Every one of them wanted to own the render:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;draw to a canvas&lt;/li&gt;
&lt;li&gt;spin up a browser&lt;/li&gt;
&lt;li&gt;re-encode the video their way&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I already had an &lt;a href="https://danielrusnok.substack.com/p/my-article-to-reel-pipeline-in-one" rel="noopener noreferrer"&gt;FFmpeg pipeline&lt;/a&gt; compositing scenes. I didn't want a second rendering engine bolted to the side of the first.&lt;/p&gt;

&lt;p&gt;ASS is older than all of them, and it does one thing well. The whole caption stage comes down to this: generate a text file, hand it to a filter I'm already running.&lt;/p&gt;

&lt;p&gt;No new dependency. No second renderer. A &lt;code&gt;.ass&lt;/code&gt; file is just text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[V4+ Styles]&lt;/span&gt;
&lt;span class="err"&gt;Style:&lt;/span&gt; &lt;span class="err"&gt;Caption,Inter&lt;/span&gt; &lt;span class="err"&gt;Black,68,&amp;amp;H002626DC,&amp;amp;H00FFFFFF,&amp;amp;H00000000,&amp;amp;H80000000,1,0,0,0,100,100,0,0,1,6,3,2,80,80,120,1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one line is the entire look: &lt;strong&gt;Inter Black, 68pt&lt;/strong&gt;, a 6px black outline and a soft shadow so white text survives over any background. The two colors that matter are &lt;code&gt;PrimaryColour&lt;/code&gt; &lt;code&gt;&amp;amp;H002626DC&lt;/code&gt; and &lt;code&gt;SecondaryColour&lt;/code&gt; &lt;code&gt;&amp;amp;H00FFFFFF&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;ASS colors are BGR, not RGB, and read backwards — &lt;code&gt;&amp;amp;H002626DC&lt;/code&gt; is &lt;code&gt;#DC2626&lt;/code&gt;, the accent color. That's the color a word &lt;em&gt;becomes&lt;/em&gt;: white until the voice reaches it, the accent once it has.&lt;/p&gt;

&lt;h2&gt;
  
  
  The karaoke tag is the whole trick
&lt;/h2&gt;

&lt;p&gt;Here's the function that turns one scene's words into one subtitle line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildDialogue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LineWord&lt;/span&gt;&lt;span class="p"&gt;[][],&lt;/span&gt; &lt;span class="nx"&gt;offsetSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allWords&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allWords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;offsetSeconds&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allWords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;allWords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;offsetSeconds&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lineStrings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;line&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;durCs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`{&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;kf&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;durCs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;escapeAss&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)}${&lt;/span&gt;&lt;span class="nx"&gt;wi&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lineStrings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;N&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;positioned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`{&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;an2&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;pos(540,1800)}&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`Dialogue: 0,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;fmtTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;fmtTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;,Caption,,0,0,0,karaoke,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;positioned&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every word becomes &lt;code&gt;{\kf52}word&lt;/code&gt;, where 52 is the word's duration in centiseconds — a word that takes 0.52 seconds to say. libass walks the line left to right, filling each word over its &lt;code&gt;\kf&lt;/code&gt; duration, then moving on. Add up all the &lt;code&gt;\kf&lt;/code&gt; values and you get the line's length.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Math.max(2, …)&lt;/code&gt; is there for a reason. A word can land with a zero-width timing, like punctuation or a render glitch. That gives it a &lt;code&gt;\kf0&lt;/code&gt; and makes it flash. Flooring it at two centiseconds keeps it animating.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpgur3fieyu78pu0dv75x.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpgur3fieyu78pu0dv75x.jpg" alt="Each word fills left-to-right exactly as the voice reaches it." width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pinning it so it never jumps
&lt;/h2&gt;

&lt;p&gt;The first version let ASS auto-place the subtitle. A one-word hook sat low; a three-line caption pushed itself up to fit.&lt;/p&gt;

&lt;p&gt;Between scenes, the captions hopped around the bottom third of the screen.&lt;/p&gt;

&lt;p&gt;The fix is one tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{\an2\pos(540,1800)}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;\an2&lt;/code&gt; anchors the text by its &lt;strong&gt;bottom-center&lt;/strong&gt;. &lt;code&gt;\pos(540,1800)&lt;/code&gt; pins that anchor to x=540 (dead center of a 1080-wide frame) and y=1800 (120px off the bottom of a 1920-tall one).&lt;/p&gt;

&lt;p&gt;Whether a caption is one line or three, its bottom edge is always at y=1800. It grows upward from a fixed floor instead of floating.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-line bug that highlighted the wrong word
&lt;/h2&gt;

&lt;p&gt;Back to that misaligned reel and the word that was always one ahead.&lt;/p&gt;

&lt;p&gt;Everything else in my pipeline runs on &lt;strong&gt;absolute time&lt;/strong&gt;. A scene starts a few seconds in. A word starts a moment later. Absolute timestamps, all the way down.&lt;/p&gt;

&lt;p&gt;So when I wrote the karaoke layer, I reached for the same model. Each word gets the timestamp it starts at.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxayym96hdbtq67w6ta7c.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxayym96hdbtq67w6ta7c.jpg" alt="The fill running one word ahead, and the aligned version after the fix." width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is not how &lt;code&gt;\kf&lt;/code&gt; works. Karaoke durations are &lt;strong&gt;sequential offsets from the line's start&lt;/strong&gt;. Each value measures a span. It says how long to fill this word before moving to the next.&lt;/p&gt;

&lt;p&gt;I'd built the line by feeding each word the next word's figure. It was a classic index slip. I wrote &lt;code&gt;words[i + 1]&lt;/code&gt; where I needed &lt;code&gt;words[i]&lt;/code&gt;. Every fill ran one word long, so the highlight stayed one word ahead of the voice.&lt;/p&gt;

&lt;p&gt;The fix was deleting the &lt;code&gt;+1&lt;/code&gt;. Each &lt;code&gt;\kf&lt;/code&gt; now comes from the word's own &lt;code&gt;end - start&lt;/code&gt;, the same line shown above.&lt;/p&gt;

&lt;p&gt;When a format looks like one you already know, check whether its numbers mean the same thing. Here they didn't. Everything in the pipeline was absolute, but the karaoke timings were relative.&lt;/p&gt;

&lt;h2&gt;
  
  
  When scenes don't start at zero
&lt;/h2&gt;

&lt;p&gt;The caption stage has one more job. The &lt;a href="https://danielrusnok.substack.com/p/teaching-haiku-to-write-a-screenplay" rel="noopener noreferrer"&gt;scenes&lt;/a&gt; don't start at zero — each word's &lt;code&gt;start&lt;/code&gt; is relative to its own scene's audio, but the final reel is all scenes concatenated with a 0.4-second pause between them.&lt;/p&gt;

&lt;p&gt;So the renderer carries a running offset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;INTER_SCENE_GAP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cumulativeOffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;scenario&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scenes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... synthesize audio, collect per-word timings ...&lt;/span&gt;
  &lt;span class="nx"&gt;sceneTimings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;audioOffsetSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cumulativeOffset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;words&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isLast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;scenario&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scenes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;cumulativeOffset&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;narrationDuration&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isLast&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;INTER_SCENE_GAP&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;buildDialogue&lt;/code&gt; adds that &lt;code&gt;audioOffsetSeconds&lt;/code&gt; to every word, so scene 3's captions land at scene 3's real position in the finished video. The 0.4s gaps sit &lt;em&gt;between&lt;/em&gt; scenes, where there are no words, so the karaoke clock never accounts for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Burning it in
&lt;/h2&gt;

&lt;p&gt;The last step is one FFmpeg call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; concat.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"subtitles=filename='captions.ass'"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"loudnorm=I=-14:TP=-1.5:LRA=11"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-pix_fmt&lt;/span&gt; yuv420p &lt;span class="nt"&gt;-r&lt;/span&gt; 30 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 160k &lt;span class="nt"&gt;-ar&lt;/span&gt; 48000 &lt;span class="nt"&gt;-movflags&lt;/span&gt; +faststart final.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;subtitles=&lt;/code&gt; hands the &lt;code&gt;.ass&lt;/code&gt; to libass, which renders every &lt;code&gt;\kf&lt;/code&gt;, &lt;code&gt;\an&lt;/code&gt;, and &lt;code&gt;\pos&lt;/code&gt; onto the frames.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;-af loudnorm&lt;/code&gt; next to it is unrelated but worth a note. It normalizes the audio to &lt;strong&gt;-14 LUFS&lt;/strong&gt;, the level Instagram, TikTok, and YouTube Shorts target. Skip it and the reel plays quieter than everything else in the feed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's still flat
&lt;/h2&gt;

&lt;p&gt;At the end of the caption stage I have a vertical video. Narrated in my voice. Karaoke words pinned to a fixed line, loudness-matched to the platforms. It plays. It reads. On mute it still works.&lt;/p&gt;

&lt;p&gt;It's also &lt;strong&gt;completely still&lt;/strong&gt;. Behind those captions is a static card — a background color, a headline, a stat.&lt;/p&gt;

&lt;p&gt;Nothing moves. A frozen image with animated text on top is barely better than a slideshow.&lt;/p&gt;

&lt;p&gt;Part 5 takes on the next problem: motion. Each scene gets three SVG layers, rendered through &lt;code&gt;sharp&lt;/code&gt; (a Node image library) at 2160×3840, then animated with FFmpeg's &lt;code&gt;zoompan&lt;/code&gt;. That's the Ken Burns effect, a slow zoom that makes a still image feel alive. It's the motion baseline, before any GPU gets involved.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Want the Claude Code memory setup I build with? &lt;a href="https://drippery.app/subscribe/6e302973-cf5f-47ff-8803-22122ac3bd9d" rel="noopener noreferrer"&gt;The Claude Code Memory Starter&lt;/a&gt; is a free email series.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>3 Domain-Centric Architectures Every Software Developer Should Know</title>
      <dc:creator>Digital Craft Workshop</dc:creator>
      <pubDate>Mon, 04 May 2026 10:53:59 +0000</pubDate>
      <link>https://dev.to/danielrusnok/3-domain-centric-architectures-every-software-developer-should-know-3b6e</link>
      <guid>https://dev.to/danielrusnok/3-domain-centric-architectures-every-software-developer-should-know-3b6e</guid>
      <description>&lt;h4&gt;
  
  
  SOFTWARE ARCHITECTURE &amp;amp; REFACTORING
&lt;/h4&gt;

&lt;h3&gt;
  
  
  3 Domain-Centric Architectures Every Software Architect Should Know
&lt;/h3&gt;

&lt;h4&gt;
  
  
  The first concern of the architect is to make sure that the house is usable; it is not to ensure that the house is made of brick. — Uncle Bob
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2uszh2jgcmghxz58upm5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2uszh2jgcmghxz58upm5.png" alt="3 Domain-Centric Architectures Every Software Developer Should Know cover image" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Domain
&lt;/h3&gt;

&lt;p&gt;The expression domain is occurring in software bibles for a very long time now and is heavily discussed in the book &lt;a href="https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215" rel="noopener noreferrer"&gt;Domain-Driven Design by Eric Evans&lt;/a&gt;. The domain is the real-world context which you are attempting to solve by writing a piece of software. Simply said, the domain is the thing why software exists and is about.&lt;/p&gt;

&lt;p&gt;Evans divides the domain into three subdomains.&lt;/p&gt;

&lt;h4&gt;
  
  
  Core Domain
&lt;/h4&gt;

&lt;p&gt;The core domain represents a competitive advantage and the software's reason for being. It is a hearth of the software with the most fundamental business rules, use cases, and domain models. It usually is the smallest part of the codebase. In other words, it is the most critical 20% part of the software.&lt;/p&gt;

&lt;h4&gt;
  
  
  Supporting Domain
&lt;/h4&gt;

&lt;p&gt;Supporting domains are supporting subsystems of software. The core can still exist without them but does not makes it alone to its users. There can be multiple supporting domains in one system. For example, the supporting domain is for the core domain, the same thing as invoicing for Amazon.&lt;/p&gt;

&lt;h4&gt;
  
  
  Generic Domain
&lt;/h4&gt;

&lt;p&gt;A generic domain is the least custom part and the least important part of the software. It is not specific to your domain, and it is widespread in other systems. I am currently working on an employee's timesheets information system, and the generic domain for this software is an external time tracker for each of the employees.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jfofozb1ubrv01hnkw5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jfofozb1ubrv01hnkw5.png" alt="Illustration" width="799" height="213"&gt;&lt;/a&gt;&lt;/p&gt;
Left: Common domain, Right: Concrete e-shop example



&lt;h4&gt;
  
  
  Summary
&lt;/h4&gt;

&lt;p&gt;It is important to decide what your core domain is and focus your development resources here from a business perspective.&lt;/p&gt;

&lt;h3&gt;
  
  
  Domain-Centric Architecture
&lt;/h3&gt;

&lt;p&gt;Now when we know what the domain is, you can logically derive that Domain-Centric Architecture puts the domain into the center. Each software architect has a huge responsibility for proper recognition of what is an essential use case and what is just an implementation detail.&lt;/p&gt;

&lt;p&gt;Let's pretend that you are buying the house. What is essential for your choice, and what is in second place?&lt;/p&gt;

&lt;p&gt;From my perspective, space and usability are the most fundamental reasons for buying a new house. Ornamentation and building material may come right after that and change my decision, but when I am looking at real estate offers online, I am looking at the space and usability for my needs first.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzfe0yta09lx8t1qowja6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzfe0yta09lx8t1qowja6.png" alt="Illustration" width="800" height="132"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Hexagonal Architecture
&lt;/h3&gt;

&lt;p&gt;Alistair Cockburn invented hexagonal Architecture in 2005. Also known as Ports and Adapters Architecture. Few authors marked the Hexagonal Architecture as an origin of Microservices Architecture.&lt;/p&gt;

&lt;p&gt;It is a plug-in system that consists of multiple loosely coupled components which you can connect to the core. The advantage of such division is in easy exchange of each component.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvkajhlap1sd9qo1n1sjm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvkajhlap1sd9qo1n1sjm.png" alt="Illustration" width="610" height="352"&gt;&lt;/a&gt;&lt;/p&gt;
Source and Credits of image: [https://ebrary.net/85161/computer\_science/hexagons\_layers](https://ebrary.net/85161/computer_science/hexagons_layers)



&lt;p&gt;Components communicate with each other via exposed public interfaces(ports). Such interfaces are typically used for notifications or database querying and storing.&lt;/p&gt;

&lt;p&gt;Adapters are the glue between components and the outside world. More adapters can be glued to one port. For example, Azure deploying can be managed through CLI, web portal, or Windows PowerShell.&lt;/p&gt;

&lt;h3&gt;
  
  
  Onion Architecture
&lt;/h3&gt;

&lt;p&gt;Founded by Jeffrey Palermo in 2008. The perspective of Onion architecture is to provide better testability, maintainability, and dependability. It is an architecture based on the &lt;a href="https://en.wikipedia.org/wiki/Inversion_of_control" rel="noopener noreferrer"&gt;Inversion of Control Principle&lt;/a&gt;. It is biased towards Object-oriented programming.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyo7u6rkaobq5tws1byj5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyo7u6rkaobq5tws1byj5.png" alt="Illustration" width="467" height="401"&gt;&lt;/a&gt;&lt;/p&gt;
Source and Credits of the image to [CodeGuru](https://codeguru.com)



&lt;p&gt;The architecture consists of multiple concentric layers interfacing towards the core domain, moving all coupling towards the center.&lt;/p&gt;

&lt;h4&gt;
  
  
  Domain Layer
&lt;/h4&gt;

&lt;p&gt;In this layer, you will find all business and behavioral objects. It also contains the domain interfaces. The domain layer can't have any dependency. On the contrary, other layers should depend on the domain layer.&lt;/p&gt;

&lt;h4&gt;
  
  
  Repository Layer
&lt;/h4&gt;

&lt;p&gt;This is an abstraction for saving and retrieving data from persistence. The layer is also mapping data from persistence to business entities. If you are not familiar with the Repository Pattern, here is the medium post from my friend &lt;a href="https://medium.com/@sena.kilicarslan" rel="noopener noreferrer"&gt;Sena Kilicarslan&lt;/a&gt;&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/net-core/repository-pattern-implementation-in-asp-net-core-21e01c6664d7" rel="noopener noreferrer"&gt;Repository Pattern Implementation in ASP.NET Core&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Service Layer
&lt;/h4&gt;

&lt;p&gt;Consists of interfaces of common operations. It provides and encapsulates the communication between the UI Layer and Repository Layer. Also, it can hold a business logic for an entity.&lt;/p&gt;

&lt;h4&gt;
  
  
  UI Layer
&lt;/h4&gt;

&lt;p&gt;This is the most outer layer. Here you will find the implementation of the dependency injection principle. It can be a web application, unit test project, or web API.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pros
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  Flexible, sustainable, and portable architecture.&lt;/li&gt;
&lt;li&gt;  No need to create a common and shared project.&lt;/li&gt;
&lt;li&gt;  It can be quickly tested.&lt;/li&gt;
&lt;li&gt;  Coupling towards the center.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cons
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  It is not an easy architecture for beginners.&lt;/li&gt;
&lt;li&gt;  Interfaces and abstractions are heavily used, and move through code can be confusing.&lt;/li&gt;
&lt;li&gt;  Architects mostly messed up, splitting responsibilities between layers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Clean Architecture
&lt;/h3&gt;

&lt;p&gt;Robert C. Martin, also known as Uncle Bob, invented Clean Architecture in 2012. The architecture is based on the &lt;a href="https://en.wikipedia.org/wiki/Separation_of_concerns" rel="noopener noreferrer"&gt;Separation of Concerns&lt;/a&gt; principle. It's achieving the separation by dividing the software into layers. The architecture is easily testable, independent of UI, database, or any external agency.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8cirdy1zj6vpwbr7s7eh.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8cirdy1zj6vpwbr7s7eh.jpeg" alt="Illustration" width="772" height="567"&gt;&lt;/a&gt;&lt;/p&gt;
Source and credits of image: [https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)



&lt;p&gt;The architecture consists of concentric circles that differ in areas of software. The more outer the circle is, the higher level of software it is. Outer circles are mechanisms, and inner circles are policies.&lt;/p&gt;

&lt;p&gt;Source code dependencies can only point inwards. Anything declared in the outer circle must not be mentioned in the inner circle, including classes, functions, and variables.&lt;/p&gt;

&lt;h4&gt;
  
  
  Entities
&lt;/h4&gt;

&lt;p&gt;The Entities layer encapsulates business rules. It can be an object with methods or a set of data structures and functions. It encapsulates the most general high-level rules, also known as Domain.&lt;/p&gt;

&lt;h4&gt;
  
  
  Use cases
&lt;/h4&gt;

&lt;p&gt;These are application-specific business rules. Here you implement all of the use cases of the system. Rules do not affect the entities. The layer is not affected by externalities such as database or UI.&lt;/p&gt;

&lt;h4&gt;
  
  
  Interface adapters
&lt;/h4&gt;

&lt;p&gt;This layer is responsible for converting data from use-cases and entities to format for an external agency. Typical usage is the MVC architecture of GUI. Here is where data is converted from Entities to database tables vice versa. The layer's main goal is a conversion from an internal form of data to an external form.&lt;/p&gt;

&lt;h4&gt;
  
  
  Frameworks &amp;amp; Drivers
&lt;/h4&gt;

&lt;p&gt;The outermost layer. It is composed of glue code for every external agency, such as a database or web framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;Each of the listed architecture works the same. They all have the Domain in the center wrapped by Application Layer as use-cases, and Persistence, Presentation, and Infrastructure in the outer circle represent the implementation details pointing towards the center.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pros
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  Heavy focus on the domain.&lt;/li&gt;
&lt;li&gt;  Less coupled.&lt;/li&gt;
&lt;li&gt;  Allows using &lt;a href="https://en.wikipedia.org/wiki/Domain-driven_design" rel="noopener noreferrer"&gt;Domain-Driven Design&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cons
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  Change is difficult.&lt;/li&gt;
&lt;li&gt;  Requires more thought.&lt;/li&gt;
&lt;li&gt;  Initial higher cost.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://alistair.cockburn.us/hexagonal-architecture/" rel="noopener noreferrer"&gt;The original blog post about Hexagonal Architecture by Alistair Cockburn.&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/" rel="noopener noreferrer"&gt;Blogpost about onion architecture by Jeffrey Palermo.&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://www.codeguru.com/csharp/csharp/cs_misc/designtechniques/understanding-onion-architecture.html" rel="noopener noreferrer"&gt;Understanding Onion Architecture by CodeGuru.&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html" rel="noopener noreferrer"&gt;Uncle Bob's 2012 blog post about Clean Architecture.&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://app.pluralsight.com/library/courses/clean-architecture-patterns-practices-principles/table-of-contents" rel="noopener noreferrer"&gt;Pluralsight course Clean architecture patterns, practices and principles&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://en.wikipedia.org/wiki/Hexagonal_architecture_%28software%29" rel="noopener noreferrer"&gt;Hexagonal architecture on Wikipedia.&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://softwareengineering.stackexchange.com/questions/359592/what-is-a-domain" rel="noopener noreferrer"&gt;Stackoverflow answers the question — What is Domain.&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://www.codeguru.com/csharp/csharp/cs_misc/designtechniques/understanding-onion-architecture.htm" rel="noopener noreferrer"&gt;CodeGuru article about Onion architecture.&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/@danielrusnok/onion-architecture-or-how-to-not-make-spaghetti-2112d139c086" rel="noopener noreferrer"&gt;Recipe to greatly layered architecture! (Onion)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@danielrusnok/3-cqrs-architectures-that-every-software-architect-should-know-a7f69aae8b6c" rel="noopener noreferrer"&gt;3 CQRS Architectures that Every Software Architect Should Know&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@danielrusnok/layers-in-software-architecture-that-every-sofware-architect-should-know-76b2452b9d9a" rel="noopener noreferrer"&gt;Layers in Software Architecture that Every Software Architect should Know&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@danielrusnok/what-is-anemic-domain-model-and-why-it-can-be-harmful-2677b1b0a79a" rel="noopener noreferrer"&gt;What is Anemic Domain Model and why it can be harmful?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.plainenglish.io/domain-driven-design-is-the-architecture-ai-agents-were-waiting-for-4742509a44db" rel="noopener noreferrer"&gt;Domain-Driven Design Is the Architecture AI Agents Were Waiting For&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Want the Claude Code memory setup I build with? &lt;a href="https://drippery.app/subscribe/6e302973-cf5f-47ff-8803-22122ac3bd9d" rel="noopener noreferrer"&gt;The Claude Code Memory Starter&lt;/a&gt; is a free email series.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>How to Switch the Algorithms at Runtime with Strategy Pattern in C#</title>
      <dc:creator>Digital Craft Workshop</dc:creator>
      <pubDate>Tue, 28 Apr 2026 07:32:16 +0000</pubDate>
      <link>https://dev.to/danielrusnok/how-to-switch-the-algorithms-at-runtime-with-strategy-pattern-in-c-35ga</link>
      <guid>https://dev.to/danielrusnok/how-to-switch-the-algorithms-at-runtime-with-strategy-pattern-in-c-35ga</guid>
      <description>&lt;h4&gt;
  
  
  When, Why, and How to use Strategy Pattern in C#. The quick and easy introduction.
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5coxm432dix6sr9b27s.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5coxm432dix6sr9b27s.jpeg" alt="Four switch button" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;
[Source](https://www.freepik.com/free-photos-vectors/button)



&lt;h3&gt;
  
  
  The Strategy Pattern
&lt;/h3&gt;

&lt;p&gt;Strategy Pattern or SP enables selecting an algorithm at runtime. Simply said, we choose an algorithm based on the incoming dynamic parameter.&lt;/p&gt;

&lt;p&gt;Sounds like decision-statement, right? Switching an algorithm based on parameter value? We are talking about a &lt;code&gt;switch&lt;/code&gt; or &lt;code&gt;if-else&lt;/code&gt; statements, correct?&lt;/p&gt;

&lt;p&gt;Well, yes. SP is some kind of decision-statement. But SP follows rules of &lt;em&gt;Object-oriented programming languages&lt;/em&gt; and brings theirs advantages to the table like reusability and extensibility. It also meets the requirements of the &lt;a href="https://en.wikipedia.org/wiki/SOLID" rel="noopener noreferrer"&gt;SOLID code.&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages of the Strategy Pattern
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  It's easy to switch between different algorithms (strategies) in runtime because you're using polymorphism in the interfaces.&lt;/li&gt;
&lt;li&gt;  Clean code because you avoid decision-statements (not complicated).&lt;/li&gt;
&lt;li&gt;  More clean code because you separate the concerns into classes (a class to each Strategy).&lt;/li&gt;
&lt;li&gt;  Decoupled and easily testable code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  When to Use the Strategy Pattern
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; When you need to use several algorithms with different variations, you need to create a concrete class to implement your algorithm.&lt;/li&gt;
&lt;li&gt; When you see conditional statements around several related algorithms.&lt;/li&gt;
&lt;li&gt; When most of your classes have related behaviors.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Components of SP
&lt;/h3&gt;

&lt;p&gt;The pattern consists of three components; &lt;em&gt;Context, Strategy, and Concrete Strategies.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Context&lt;/em&gt; — Scope, where we are using Strategy. For example, class &lt;code&gt;PhoneNumber&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Strategy&lt;/em&gt; — &lt;em&gt;Strategy&lt;/em&gt; is a common interface for &lt;em&gt;Concrete Strategies&lt;/em&gt;. For example, interface &lt;code&gt;IToStringStrategy&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Concrete Strategies&lt;/em&gt; — Concrete classes that are implementing a &lt;em&gt;Strategy&lt;/em&gt; interface. For example, &lt;code&gt;IrelandToStringStrategy&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc8e1b3td3kzv4ffo9qe9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc8e1b3td3kzv4ffo9qe9.png" alt="Strategy Pattern Class Diagram" width="722" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mostly, the &lt;em&gt;Context&lt;/em&gt; holds the object of Strategy in field or property. We will apply the &lt;em&gt;Concrete Strategy&lt;/em&gt; based on the incoming parameter and then execute the algorithm by invoking the Strategy's method.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tutorial
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Introduction of Project
&lt;/h4&gt;

&lt;p&gt;Here is the &lt;a href="https://github.com/DannyRusnok/StrategyPattern.Example" rel="noopener noreferrer"&gt;repository&lt;/a&gt; with Project. It is a .NET Core console application with simple functionality. It detects whenever you press key 'C' for &lt;em&gt;Czech&lt;/em&gt; or 'I' for &lt;em&gt;Ireland&lt;/em&gt;. Depended on the pressed key, the application displays the Czech or Irish format of an exemplary phone number.&lt;/p&gt;

&lt;p&gt;Console servers as a tool for switching &lt;code&gt;CultureInfo&lt;/code&gt; of the application. &lt;code&gt;CultureInfo&lt;/code&gt; is mostly driven by an environment like a browser or operating system, but for easier manipulation, I created a simple interactive console.&lt;/p&gt;

&lt;h4&gt;
  
  
  Strategy
&lt;/h4&gt;

&lt;p&gt;Interface &lt;code&gt;IToStringStrategy&lt;/code&gt; works like the prescription of Concrete Strategies. It contains the definition of &lt;code&gt;ToString&lt;/code&gt; method receiving phone number value. Implementations of the defined method will convert and format phone number value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/DannyRusnok/StrategyPattern.Example/blob/master/StrategyPattern.ConsoleApplication/IToStringStrategy.cs" rel="noopener noreferrer"&gt;IToStringStrategy.cs&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Concrete Strategies
&lt;/h4&gt;

&lt;p&gt;Next are &lt;em&gt;Concrete Strategies&lt;/em&gt; of the phone number that are transforming the value into formatted &lt;code&gt;string&lt;/code&gt; type.&lt;/p&gt;

&lt;p&gt;For Czechia country is the typical format "&lt;em&gt;+420 XXX XXX XXX"&lt;/em&gt;. I googled that one of the Ireland telephone number formats is "&lt;em&gt;+353 X XXXX XXXX"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Unchanging prefixes are defined as constants in the &lt;code&gt;Prefixes&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/DannyRusnok/StrategyPattern.Example/blob/master/StrategyPattern.ConsoleApplication/Prefixes.cs" rel="noopener noreferrer"&gt;Prefixes.cs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Class &lt;code&gt;CzechiaToStringStrategy&lt;/code&gt; holds the implementation of Czech phone numbers. I am using string interpolation for formatting. The syntax is short, readable, and &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated" rel="noopener noreferrer"&gt;string interpolation&lt;/a&gt; takes care of the conversion from &lt;code&gt;long&lt;/code&gt; type by itself. After a colon, you can simply define where specific characters of converted value should be placed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/DannyRusnok/StrategyPattern.Example/blob/master/StrategyPattern.ConsoleApplication/CzechiaToStringStrategy.cs" rel="noopener noreferrer"&gt;CzechiaToStringStrategy.cs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Class &lt;code&gt;IrelandToStringStrategy&lt;/code&gt; implements Ireland's phone number formatting. It is using the same tools to brings the desired result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/DannyRusnok/StrategyPattern.Example/blob/master/StrategyPattern.ConsoleApplication/CzechiaToStringStrategy.cs" rel="noopener noreferrer"&gt;IrelandToStringStrategy.cs&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Context of Strategy
&lt;/h4&gt;

&lt;p&gt;Class &lt;code&gt;PhoneNumber&lt;/code&gt; serves as &lt;em&gt;Context&lt;/em&gt;. It also contains &lt;code&gt;ResolveStrategyByCulture&lt;/code&gt; method, which returns an instance of &lt;em&gt;Concrete Strategy&lt;/em&gt; figured out from the value of &lt;code&gt;CurrentCulture.Name.&lt;/code&gt; My application supports only Ireland or Czech culture, so otherwise, an exception will be thrown.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/DannyRusnok/StrategyPattern.Example/blob/master/StrategyPattern.ConsoleApplication/PhoneNumber.cs" rel="noopener noreferrer"&gt;PhoneNumber.cs&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Program
&lt;/h4&gt;

&lt;p&gt;Last at the podium will be the &lt;code&gt;Program&lt;/code&gt; class with &lt;code&gt;Main&lt;/code&gt; method, which is an entry point of every console application in .NET and .NET Core frameworks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/DannyRusnok/StrategyPattern.Example/blob/master/StrategyPattern.ConsoleApplication/Program.cs" rel="noopener noreferrer"&gt;Program.cs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, I am reading the incoming keys from keyboards and depend on the value I am setting &lt;code&gt;CultureInfo.CurrentCulture.&lt;/code&gt; Next, the phone number object is initialized and printed on the screen.&lt;/p&gt;

&lt;p&gt;I don't need to call &lt;code&gt;Tostring&lt;/code&gt; method, because I am again using string interpolation, and method is invoked automatically.&lt;/p&gt;

&lt;h4&gt;
  
  
  Summary
&lt;/h4&gt;

&lt;p&gt;Algorithms of &lt;code&gt;ToString&lt;/code&gt; methods are dynamically switched by the user's action at runtime. You can reach the same goal with decision-statements, but it has its drawbacks.&lt;/p&gt;

&lt;p&gt;The shown tutorial is a very simple demonstration of how to implement the strategy pattern in C# language. Its simplicity is also its weakness and it may leave you unconvinced to use the &lt;em&gt;Strategy Pattern&lt;/em&gt;. Below I collect a few interesting links as more proof that taking advantage of the &lt;em&gt;Strategy Pattern&lt;/em&gt; has its benefits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where next?
&lt;/h3&gt;

&lt;p&gt;To more exploration of Strategy Pattern, I would recommend the Pluralsight course by &lt;a href="http://blog.filipekberg.se/" rel="noopener noreferrer"&gt;Filip Ekberg&lt;/a&gt;; "&lt;a href="http://%22C#%20Design%20Patterns:%20Strategy%22%20https://app.pluralsight.com/library/courses/edd31b0e-9972-49e8-8ea2-ab949f1bff58" rel="noopener noreferrer"&gt;C# Design Patterns: Strategy&lt;/a&gt;."&lt;/p&gt;

&lt;p&gt;Take a look also at &lt;a href="https://ufukhaciogullari.com/blog/strategy-pattern-with-dependency-injection/" rel="noopener noreferrer"&gt;this blog post&lt;/a&gt; about Strategy Pattern and Dependency Injection. I found it very easy to understand.&lt;/p&gt;

&lt;p&gt;You can always go back to the roots of Design Patterns by reading a book of Gang of Four; "&lt;a href="https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612" rel="noopener noreferrer"&gt;Design Patterns: Elements of Reusable Object-Oriented Software."&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, if you want to explore more my work about design patterns, here are a few of them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/swlh/how-to-dynamically-add-behaviors-with-decorator-pattern-6a130c41b7d4" rel="noopener noreferrer"&gt;How to Dynamically Add Behaviors With Decorator Pattern&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/net-core/decoupling-with-chain-of-responsibility-pattern-in-c-1273329ed923" rel="noopener noreferrer"&gt;Decoupling with Chain of Responsibility Pattern in C#&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/net-core/how-to-manage-states-with-state-design-pattern-in-c-d4ca47ec6aa" rel="noopener noreferrer"&gt;How to manage states with State Design Pattern in C#?&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/@danielrusnok/how-to-dynamically-add-behaviors-with-decorator-pattern-6a130c41b7d4" rel="noopener noreferrer"&gt;How to Dynamically Add Behaviors With Decorator Pattern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@danielrusnok/how-to-manage-states-with-state-design-pattern-in-c-d4ca47ec6aa" rel="noopener noreferrer"&gt;How to manage states with State Design Pattern in C#?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@danielrusnok/decoupling-with-chain-of-responsibility-pattern-in-c-1273329ed923" rel="noopener noreferrer"&gt;Decoupling with Chain of Responsibility Pattern in C#&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@danielrusnok/how-to-avoid-exponential-growth-of-classes-by-applying-bridge-pattern-in-c-2d437e203f82" rel="noopener noreferrer"&gt;How to Avoid Exponential Growth of Classes by Applying Bridge Pattern in C#&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@danielrusnok/how-to-rewrite-your-code-to-achieve-more-flexible-design-3c86dad822e" rel="noopener noreferrer"&gt;How to Rewrite your Code to Achieve More Flexible Design&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Want the Claude Code memory setup I build with? &lt;a href="https://drippery.app/subscribe/6e302973-cf5f-47ff-8803-22122ac3bd9d" rel="noopener noreferrer"&gt;The Claude Code Memory Starter&lt;/a&gt; is a free email series.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>designpatterns</category>
      <category>dotnet</category>
      <category>programming</category>
    </item>
    <item>
      <title>Decoupling with Chain of Responsibility Pattern in C#</title>
      <dc:creator>Digital Craft Workshop</dc:creator>
      <pubDate>Sun, 26 Apr 2026 13:13:11 +0000</pubDate>
      <link>https://dev.to/danielrusnok/decoupling-with-chain-of-responsibility-pattern-in-c-53jp</link>
      <guid>https://dev.to/danielrusnok/decoupling-with-chain-of-responsibility-pattern-in-c-53jp</guid>
      <description>&lt;p&gt;The article cooperates with the &lt;a href="https://github.com/DannyRusnok/Chain.Of.Responsibility.Example/tree/master" rel="noopener noreferrer"&gt;sample project&lt;/a&gt;. Walkthrough with the downloaded project is not required, but I recommend it for better understanding.&lt;/p&gt;

&lt;p&gt;The sample project consists of two different approaches to validating the user's data within a registration. Two processors simulate the user's registration process.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BasicUserRegistrationProcessor.cs&lt;/code&gt; follows the simple path of if statements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BasicUserRegistrationProcessor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Username is required."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Password&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Password is required."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BirthDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Year&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Year&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Age under 18 is not allowed."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ChainPatternRegistrationProcessor.cs&lt;/code&gt; is taking advantage of the Chain of Responsibility Pattern. This implementation applies the same set of validations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChainPatternRegistrationProcessor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;UsernameRequiredValidationHandler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PasswordRequiredValidationHandler&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OnlyAdultValidationHandler&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Chain of Responsibility Pattern
&lt;/h3&gt;

&lt;p&gt;Chain of Responsibility Pattern(ChoRP) is a pattern firstly presented in great book &lt;a href="https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612" rel="noopener noreferrer"&gt;Design Patterns: Elements of Reusable Object-Oriented Software&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5qze5kazz7ibnvv8kg6h.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5qze5kazz7ibnvv8kg6h.jpeg" alt="Illustration" width="399" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can achieve loose coupling in software design by practicing ChoRP. The pattern is a very powerful yet pretty simple to implement in our applications. It allows us to easily decouple parts of code to make it more readable, testable, extensible, and maintainable.&lt;/p&gt;

&lt;p&gt;ChoRP comes from a family of the behavioral pattern, also like State Pattern.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/net-core/how-to-manage-states-with-state-design-pattern-in-c-d4ca47ec6aa" rel="noopener noreferrer"&gt;How to manage states with State Design Pattern in C#?&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Components of ChoRP
&lt;/h3&gt;

&lt;p&gt;ChoRP consists of three components.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Request&lt;/li&gt;
&lt;li&gt; Abstraction of Handler&lt;/li&gt;
&lt;li&gt; Concrete Handlers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One or more Concrete Handlers will take care of Request in the chained process. Mostly, the Request is some kind of contract or Data Transfer Object of the handling event. In the sample project, it is a User class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Password&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt; &lt;span class="n"&gt;BirthDate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I am usually using the interface and the abstract class together as the abstraction for the Concrete Handlers. It contains two methods; &lt;code&gt;Handle&lt;/code&gt; and &lt;code&gt;SetNext.&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;
&lt;span class="err"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;private&lt;/span&gt; &lt;span class="n"&gt;IHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Next&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;SetNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;Next&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;
&lt;span class="err"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;IHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;SetNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;T is a generic type of class which represents the Request. Method &lt;code&gt;SetNext()&lt;/code&gt; sets a private property &lt;code&gt;Next&lt;/code&gt; , which is subsequently invoked in the virtual method &lt;code&gt;Handle(T request)&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;Concrete Handler inherits from the abstract class &lt;code&gt;Handler&lt;/code&gt; and implements an interface &lt;code&gt;IHandler.&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OnlyAdultValidationHandler&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="n"&gt;IHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BirthDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Year&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Year&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Age under 18 is not allowed."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;OnlyAdultValidationHandler&lt;/code&gt; class overrides &lt;code&gt;Handle&lt;/code&gt; method of its parent but also invokes the parent's &lt;code&gt;Handle&lt;/code&gt; the method at the end of the method's body. This is a crucial factor for chaining Concrete Handlers. Also, by inheriting from the &lt;code&gt;Handler&lt;/code&gt; class, the &lt;code&gt;OnlyAdultValidationHandler&lt;/code&gt; gains &lt;code&gt;SetNext&lt;/code&gt; method threw which we will set another chain segment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chaining Handlers
&lt;/h3&gt;

&lt;p&gt;First, you need to set up the chain via the &lt;code&gt;SetNext&lt;/code&gt; method. The method returns &lt;code&gt;IHandler&lt;/code&gt; type so you can set Handlers in a row.&lt;/p&gt;

&lt;p&gt;SetNext(new Handler()).SetNext(new Handler())&lt;/p&gt;

&lt;p&gt;Once we finish the chain definition, we can continue by invoking &lt;code&gt;Handler.Handle(request)&lt;/code&gt; which will call &lt;code&gt;base.Handle(user)&lt;/code&gt; therefore &lt;code&gt;Next?.Handle(request)&lt;/code&gt; ensures that the chain reaction is triggered.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9mf3b7txd0xy2d9ea36w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9mf3b7txd0xy2d9ea36w.png" alt="Illustration" width="574" height="689"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Drawbacks of Coupled Implementations
&lt;/h3&gt;

&lt;p&gt;Why so much workaround? The basic implementation looks much more straightforward and readable than ChoRP implementation, right?&lt;/p&gt;

&lt;p&gt;Well, it is not clear at first look, but those validations are tightly coupled. Password Validation is dependent on the result of Username Validation. Only Adult Age Validation is dependent on the result of previous validations.&lt;/p&gt;

&lt;p&gt;It is even more evident if you write unit tests that are going to test different scenarios of user registration. You are not able to test Password Validation without introducing value into the user's Username property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BasicUserRegistrationTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;When_Username_Is_Empty_Then_Exception&lt;/span&gt;
    &lt;span class="nf"&gt;_Should_Be_Thrown&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//Arrange&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;User&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;//Act&lt;/span&gt;
        &lt;span class="n"&gt;Action&lt;/span&gt; &lt;span class="n"&gt;act&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BasicUserRegistrationProcessor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;//Assert&lt;/span&gt;
        &lt;span class="n"&gt;act&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Throw&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;When_Password_Is_Empty_Then_Exception&lt;/span&gt;
    &lt;span class="nf"&gt;_Should_Be_Thrown&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//Arrange&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Daniel Rusnok"&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="c1"&gt;//Act&lt;/span&gt;
        &lt;span class="n"&gt;Action&lt;/span&gt; &lt;span class="n"&gt;act&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BasicUserRegistrationProcessor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;//Assert&lt;/span&gt;
        &lt;span class="n"&gt;act&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Throw&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But once you apply a ChoRP, then you are not only able to test Password Validation result, but also you are able to test both possible results of Password Validation which are &lt;code&gt;Throw&amp;lt;Exception&amp;gt;&lt;/code&gt; when the password property is empty and &lt;code&gt;NotThrow&amp;lt;Exception&amp;gt;&lt;/code&gt; when the password property is filled.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChainPatternRegistrationTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;When_Password_Is_Empty_Then_Exception&lt;/span&gt;
    &lt;span class="nf"&gt;_Should_Be_Thrown&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//Arrange&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="c1"&gt;//Act&lt;/span&gt;
        &lt;span class="n"&gt;Action&lt;/span&gt; &lt;span class="n"&gt;act&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PasswordRequiredValidationHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;//Assert&lt;/span&gt;
        &lt;span class="n"&gt;act&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Throw&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;When_Password_Is_Filled_Then_Exception&lt;/span&gt;
    &lt;span class="nf"&gt;_Should_NOT_Be_Thrown&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//Arrange&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()};&lt;/span&gt;

        &lt;span class="c1"&gt;//Act&lt;/span&gt;
        &lt;span class="n"&gt;Action&lt;/span&gt; &lt;span class="n"&gt;act&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PasswordRequiredValidationHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;//Assert&lt;/span&gt;
        &lt;span class="n"&gt;act&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;NotThrow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;BasicUserRegistrationProcessor&lt;/code&gt; we are only able to test if Password Validation &lt;code&gt;NotThrow&amp;lt;Exception&amp;gt;&lt;/code&gt; when the whole user object is valid. And this is the biggest demonstration of how basic registration implementation is coupled.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;When_All_Properties_Are_valid_Then_Exception_Should_NOT_Be_Thrown&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//Arrange&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Daniel Rusnok"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;BirthDate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddYears&lt;/span&gt;&lt;span class="p"&gt;(-&lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="c1"&gt;//Act&lt;/span&gt;
    &lt;span class="n"&gt;Action&lt;/span&gt; &lt;span class="n"&gt;act&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BasicUserRegistrationProcessor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;//Assert&lt;/span&gt;
    &lt;span class="n"&gt;act&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;NotThrow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;The power of decoupling is not only in its independent testability but also in its reusability. Such a Handler does not always have to accept a concrete type as a parameter. It can take an abstraction of the specific type like an interface.&lt;/p&gt;

&lt;p&gt;Multiple types of Requests can implement such an interface. In such situations, the Handler becomes useable for various processes, and you are not repeating yourself. So ChoRP also supports the &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself" rel="noopener noreferrer"&gt;DRY&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/@danielrusnok/how-to-dynamically-add-behaviors-with-decorator-pattern-6a130c41b7d4" rel="noopener noreferrer"&gt;How to Dynamically Add Behaviors With Decorator Pattern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@danielrusnok/ugly-dependency-graph-the-mediator-design-pattern-is-the-solution-for-you-8b35df60558b" rel="noopener noreferrer"&gt;Ugly dependency graph? The Mediator Design Pattern is the solution for you.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@danielrusnok/how-to-manage-states-with-state-design-pattern-in-c-d4ca47ec6aa" rel="noopener noreferrer"&gt;How to manage states with State Design Pattern in C#?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@danielrusnok/how-i-upgrade-my-code-style-of-mediatr-pipeline-using-net-6-ed49aca61f47" rel="noopener noreferrer"&gt;How I Upgrade my Code-Style of MediatR Pipeline using .NET 6&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Want the Claude Code memory setup I build with? &lt;a href="https://drippery.app/subscribe/6e302973-cf5f-47ff-8803-22122ac3bd9d" rel="noopener noreferrer"&gt;The Claude Code Memory Starter&lt;/a&gt; is a free email series.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Soft-deleting Postgres rows without losing the URL slug</title>
      <dc:creator>Digital Craft Workshop</dc:creator>
      <pubDate>Fri, 10 Apr 2026 17:17:11 +0000</pubDate>
      <link>https://dev.to/danielrusnok/soft-deleting-postgres-rows-without-losing-the-url-slug-351k</link>
      <guid>https://dev.to/danielrusnok/soft-deleting-postgres-rows-without-losing-the-url-slug-351k</guid>
      <description>&lt;h3&gt;
  
  
  &lt;em&gt;Six lines of code that let you release a unique-constrained slug back into the namespace without dropping the constraint.&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;I want soft-delete on my blog posts. I also want &lt;code&gt;slug&lt;/code&gt; to be &lt;code&gt;UNIQUE NOT NULL&lt;/code&gt;. These two requirements quietly fight each other, and the standard fixes — partial unique indexes, &lt;code&gt;deleted_at IS NULL&lt;/code&gt; predicates, separate archive tables — are all heavier than what I needed.&lt;/p&gt;

&lt;p&gt;Here's the six-line trick I shipped instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The conflict
&lt;/h2&gt;

&lt;p&gt;A blog post has a URL slug like &lt;code&gt;my-cool-post&lt;/code&gt;. I want &lt;code&gt;UNIQUE&lt;/code&gt; on &lt;code&gt;slug&lt;/code&gt; so I can serve &lt;code&gt;/blog/my-cool-post&lt;/code&gt; without ambiguity. I also want &lt;code&gt;DELETE&lt;/code&gt; to be soft — set &lt;code&gt;status = 'archived'&lt;/code&gt; so I can recover the post if I change my mind, and so historical analytics don't get orphaned.&lt;/p&gt;

&lt;p&gt;Now the user re-uses the title "My Cool Post" two months later. They want a fresh post at &lt;code&gt;/blog/my-cool-post&lt;/code&gt;. The &lt;code&gt;INSERT&lt;/code&gt; fails because the archived row still owns the slug.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://drippery.app/subscribe/bff5d361-264c-485a-bd96-70a8c11a42f0" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdrippery.app%2Fapi%2Fog%2Fbff5d361-264c-485a-bd96-70a8c11a42f0" height="630" class="m-0" width="1200"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://drippery.app/subscribe/bff5d361-264c-485a-bd96-70a8c11a42f0" rel="noopener noreferrer" class="c-link"&gt;
            Master TypeScript as a C# Developer
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Get 5 practical emails covering the exact TypeScript concepts that trip up C# developers.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdrippery.app%2Ficon.svg%3Ficon.0ggb9ka.au30r.svg" width="32" height="32"&gt;
          drippery.app
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Standard answers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Partial unique index:&lt;/strong&gt; &lt;code&gt;CREATE UNIQUE INDEX ON posts(slug) WHERE status != 'archived'&lt;/code&gt;. Works, but it's a separate index to maintain and it doesn't handle the case where you want to look up the archived post by its old slug at all (your &lt;code&gt;/blog/&amp;lt;slug&amp;gt;&lt;/code&gt; lookup needs to know the old slug is gone).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;deleted_at IS NULL&lt;/code&gt; everywhere:&lt;/strong&gt; Now every query needs a predicate. One missed &lt;code&gt;WHERE&lt;/code&gt; and you leak archived data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate &lt;code&gt;archived_posts&lt;/code&gt; table:&lt;/strong&gt; Schema duplication, two places to query, FK headaches with analytics tables that point at &lt;code&gt;posts.id&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The trick
&lt;/h2&gt;

&lt;p&gt;When you archive, &lt;strong&gt;rename the slug&lt;/strong&gt;. Append &lt;code&gt;-archived-${Date.now()}&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;archivedSlug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-archived-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blogPosts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;archived&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;archivedSlug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;updatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blogPosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;revalidatePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/blog/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;revalidatePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/blog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Six lines of business logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this gives you
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The original slug is now free. The next &lt;code&gt;INSERT&lt;/code&gt; of a post titled "My Cool Post" succeeds with no contention.&lt;/li&gt;
&lt;li&gt;The unique constraint stays a plain &lt;code&gt;UNIQUE NOT NULL&lt;/code&gt; — no partial index, no predicate, no rewrites of every query.&lt;/li&gt;
&lt;li&gt;The archived row is still queryable by ID, by status, by content. You haven't lost anything.&lt;/li&gt;
&lt;li&gt;The timestamp suffix makes collisions impossible across multiple archive/restore cycles. &lt;code&gt;Date.now()&lt;/code&gt; is millisecond-precise; you won't archive two posts within the same millisecond unless you're shipping at scale I'm not at.&lt;/li&gt;
&lt;li&gt;The old URL stops resolving, which is exactly what you want for a deleted post. &lt;code&gt;revalidatePath&lt;/code&gt; flushes the ISR cache so the public-facing site reflects the deletion immediately.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What this doesn't give you
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Restore doesn't auto-rename back.&lt;/strong&gt; If you want a "restore" action, you need to handle the case where the original slug is now occupied by a newer post — typically by appending &lt;code&gt;-2&lt;/code&gt; or by failing with a 409 and asking the user to pick a new slug. I haven't built restore yet because nobody's asked for it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search engines that already indexed &lt;code&gt;/blog/&amp;lt;old-slug&amp;gt;-archived-1234&lt;/code&gt; will get a 404&lt;/strong&gt; when they re-crawl. This is correct behavior for deleted content. If you instead want a 410 Gone, add a route handler that detects the &lt;code&gt;-archived-&lt;/code&gt; suffix and returns 410.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit trails get noisy slugs.&lt;/strong&gt; Your activity log will show &lt;code&gt;my-cool-post → my-cool-post-archived-1739812340000&lt;/code&gt;. If that bothers you, store the original slug in a separate &lt;code&gt;original_slug&lt;/code&gt; column at archive time, and use it for display.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When this is the wrong tool
&lt;/h2&gt;

&lt;p&gt;If you need to keep the archived URL working for legal or SEO reasons (e.g. blog posts that shouldn't 404 because they're cited from elsewhere), this pattern is wrong. You want the partial unique index approach instead, and a "tombstone" version of the page that explains the post was archived and links to a successor.&lt;/p&gt;

&lt;p&gt;But if archive means "this is gone, the URL should die, and I want to be able to reuse the title later" — and that's the common case for blog post archives — six lines of slug-renaming is all the complexity you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  The schema
&lt;/h2&gt;

&lt;p&gt;For completeness, the relevant column on &lt;code&gt;drippery_blog_posts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;slug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;draft&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No partial index. No &lt;code&gt;deleted_at&lt;/code&gt;. No second table. Just &lt;code&gt;UNIQUE&lt;/code&gt; on the slug, a status column, and a six-line archive handler that renames the slug on the way out.&lt;/p&gt;

&lt;p&gt;Sometimes the best soft-delete is the one that briefly hard-deletes the namespace.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;The blog engine behind this post is part of &lt;a href="https://drippery.app" rel="noopener noreferrer"&gt;Drippery&lt;/a&gt; — a drip email tool I'm building for creators who want automated email sequences without the $39/month price tag.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Want the Claude Code memory setup I build with? &lt;a href="https://drippery.app/subscribe/6e302973-cf5f-47ff-8803-22122ac3bd9d" rel="noopener noreferrer"&gt;The Claude Code Memory Starter&lt;/a&gt; is a free email series.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>postgressql</category>
    </item>
    <item>
      <title>5 Functions, 1 Route, $0/Month: My Entire SaaS Background Job Architecture</title>
      <dc:creator>Digital Craft Workshop</dc:creator>
      <pubDate>Thu, 09 Apr 2026 18:51:16 +0000</pubDate>
      <link>https://dev.to/drippery/5-functions-1-route-0month-my-entire-saas-background-job-architecture-2915</link>
      <guid>https://dev.to/drippery/5-functions-1-route-0month-my-entire-saas-background-job-architecture-2915</guid>
      <description>&lt;p&gt;BullMQ needs Redis. Cron has no retries. &lt;strong&gt;I needed background jobs that can sleep for 48 hours, fan out to parallel workers, and survive server restarts&lt;/strong&gt; — without paying for infrastructure I don't have users to justify yet.&lt;/p&gt;

&lt;p&gt;I replaced the entire Redis + BullMQ + cron + monitoring stack with five Inngest functions. &lt;strong&gt;Total cost: $0.&lt;/strong&gt;&lt;/p&gt;





&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://drippery.app/subscribe/a8ff09ab-6665-409f-a8e4-acb87e2f1a12" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpub-ca661161370d461a9fec22ff4d33f29e.r2.dev%2F09021f01-fc15-4c06-93ff-8eb4e56f9d30%2Fog%2F5557c316-8aea-4531-9662-14c125be04e8.jpg" height="450" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://drippery.app/subscribe/a8ff09ab-6665-409f-a8e4-acb87e2f1a12" rel="noopener noreferrer" class="c-link"&gt;
            AI as a Solo Founder's Tool
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            A 5-part email series on using AI as more than a search engine — for building, marketing, and shipping alone.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdrippery.app%2Ficon.svg%3Ficon.0ggb9ka.au30r.svg" width="32" height="32"&gt;
          drippery.app
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  The Full Picture
&lt;/h2&gt;

&lt;p&gt;Drippery — my drip email SaaS — runs five background functions. Together they cover scheduled email delivery, DNS domain verification polling, orphaned file cleanup, and beta user lifecycle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;All five are registered in one Next.js API route.&lt;/strong&gt; Inngest calls it via webhook. No Redis, no worker process, no cron container.&lt;/p&gt;

&lt;p&gt;Here's what that replaces:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Without Inngest&lt;/th&gt;
&lt;th&gt;With Inngest&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Redis instance (~$10/month on Render)&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BullMQ worker process&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cron container or external service&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual retry logic&lt;/td&gt;
&lt;td&gt;Built-in, per-step&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability dashboard&lt;/td&gt;
&lt;td&gt;Inngest dashboard (free)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Distributed locking for cron&lt;/td&gt;
&lt;td&gt;Handled automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long-running job persistence&lt;/td&gt;
&lt;td&gt;&lt;code&gt;step.sleep()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fan-out worker pool&lt;/td&gt;
&lt;td&gt;&lt;code&gt;step.sendEvent()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let me walk through each function.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Email Pipeline: Scheduler + Parallel Sender
&lt;/h2&gt;

&lt;p&gt;The core email system is a two-function pipeline. &lt;strong&gt;The scheduler runs every 15 minutes&lt;/strong&gt; and asks: which subscribers are due for their next drip email?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendPendingEmails&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;send-pending-emails&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*/15 * * * *&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscribers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch-subscribers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nf"&gt;findActiveSubscribersWithSequences&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// For each subscriber: find next due email, check dayOffset + send window&lt;/span&gt;
    &lt;span class="c1"&gt;// Dispatch one event per match&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dispatch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eventsToDispatch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It walks through each active subscriber, finds their position in the sequence, and checks if enough days have passed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creators in different timezones shouldn't get emails at 3 AM&lt;/strong&gt; — the scheduler verifies the current time matches the tenant's preferred send window before dispatching anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The key decision: fan-out.&lt;/strong&gt; Instead of sending emails inside the loop, the scheduler dispatches &lt;code&gt;email/send&lt;/code&gt; events. Each event triggers a separate function invocation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;50 subscribers due = 50 parallel runs.&lt;/strong&gt; No worker pool, no concurrency config.&lt;/p&gt;

&lt;p&gt;The other critical detail: &lt;strong&gt;drip semantics.&lt;/strong&gt; One email per subscriber per tick, never the whole sequence at once.&lt;/p&gt;

&lt;p&gt;Each &lt;code&gt;email/send&lt;/code&gt; event triggers the sender — one subscriber, one email, full isolation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendSingleEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;send-single-email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email/send&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;subscriberId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;emailId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tenantId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;subscriber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nf"&gt;fetchEmailContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subscriberId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;emailId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;finalHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;replaceMergeTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mergeTags&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;send-email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nf"&gt;sendEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subscriber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;finalSubject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;finalHtml&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;record-sent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sentEmails&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;subscriberId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;emailId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sentAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Each &lt;code&gt;step.run()&lt;/code&gt; is independently retryable.&lt;/strong&gt; If the Resend API times out on &lt;code&gt;send-email&lt;/code&gt;, only that step retries — the DB fetch doesn't re-execute.&lt;/p&gt;

&lt;p&gt;If the server crashes after sending but before recording, Inngest resumes at &lt;code&gt;record-sent&lt;/code&gt;. &lt;strong&gt;No double-sends, no lost records.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The 48-Hour Function: Domain Verification Polling
&lt;/h2&gt;

&lt;p&gt;This is the function I couldn't have built with BullMQ — at least not without a lot of scheduling glue.&lt;/p&gt;

&lt;p&gt;When a user adds a custom sender domain, Drippery registers it with Resend and starts polling for DNS verification. &lt;strong&gt;This can take 5 minutes or 24 hours&lt;/strong&gt; — you don't know upfront.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkDomainVerification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;check-domain-verification&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;domain/check-verification&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;senderDomainId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAX_ATTEMPTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;576&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 48 hours at 5-minute intervals&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;check-resend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Resend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RESEND_API_KEY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;resend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domains&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resendDomainId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isVerified&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;verified&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wait&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;5m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-check&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;domain/check-verification&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;senderDomainId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;This function can run for up to 48 hours.&lt;/strong&gt; &lt;code&gt;step.sleep()&lt;/code&gt; suspends the function, frees server resources, then resumes exactly where it left off.&lt;/p&gt;

&lt;p&gt;With BullMQ, you'd need explicit delayed job scheduling, persistence logic, and retry handling. &lt;strong&gt;Here it's one line:&lt;/strong&gt; &lt;code&gt;await step.sleep('5m')&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Maintenance Jobs: Cleanup + Beta Lifecycle
&lt;/h2&gt;

&lt;p&gt;The remaining two functions are simpler but follow the same pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Orphan image cleanup&lt;/strong&gt; runs every Sunday at 3 AM. It compares images in Cloudflare R2 against database references and deletes anything unreferenced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cleanupOrphanImages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cleanup-orphan-images&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0 3 * * 0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r2Keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;list-r2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;listAllObjects&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dbKeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get-refs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;extractReferencedUrls&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orphans&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;r2Keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;dbKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;delete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;deleteObjects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orphans&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Email images are excluded&lt;/strong&gt; — once delivered, they can't be deleted without breaking past recipients' inboxes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Beta expiry&lt;/strong&gt; runs daily at 9 AM. It warns testers 3 days before their 14-day trial ends, then downgrades them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkBetaExpiry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;check-beta-expiry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0 9 * * *&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;warningTesters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;find-warning&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nf"&gt;findTenantsByBetaWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tenant&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;warningTesters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`warn-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendWarningEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;markWarned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Each tenant gets its own &lt;code&gt;step.run()&lt;/code&gt;.&lt;/strong&gt; If one tenant's email lookup fails, only that step retries — the rest still get processed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cost
&lt;/h2&gt;

&lt;p&gt;Inngest's free tier gives you &lt;strong&gt;50,000 executions per month.&lt;/strong&gt; My actual usage is well under 10,000 — scheduler ticks, dispatched sends, daily and weekly jobs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For an early-stage SaaS, the free tier has plenty of headroom.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When I outgrow it, paid plans start at $75/month. &lt;strong&gt;By that point, I'll have enough paying users to cover it.&lt;/strong&gt; That's the scaling curve I wanted.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Five functions today. The next one will probably be a &lt;strong&gt;subscriber re-engagement job&lt;/strong&gt; — if someone hasn't opened the last 3 emails, automatically pause their sequence and notify the tenant.&lt;/p&gt;

&lt;p&gt;Same pattern: one more function file, zero infrastructure changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The entire background job layer of my SaaS is five TypeScript functions, one API route, and zero managed services.&lt;/strong&gt; When you're building alone, that's the architecture that lets you ship.&lt;/p&gt;




&lt;p&gt;If you're curious about Drippery — dead-simple drip email sequences for creators, starting at $0/month — check it out at &lt;a href="https://drippery.app" rel="noopener noreferrer"&gt;drippery.app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I write weekly about what I'm shipping and what's breaking on &lt;a href="https://danielrusnok.substack.com" rel="noopener noreferrer"&gt;Substack&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I Thought I Understood Git. Then I Actually Learned the Difference Between Rebase and Merge.</title>
      <dc:creator>Digital Craft Workshop</dc:creator>
      <pubDate>Wed, 25 Mar 2026 13:27:36 +0000</pubDate>
      <link>https://dev.to/danielrusnok/i-thought-i-understood-git-then-i-actually-learned-the-difference-between-rebase-and-merge-88g</link>
      <guid>https://dev.to/danielrusnok/i-thought-i-understood-git-then-i-actually-learned-the-difference-between-rebase-and-merge-88g</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5qlxow6dz17r0pfh1vaw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5qlxow6dz17r0pfh1vaw.png" alt="Merge | Rebase graph" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I used &lt;code&gt;git rebase&lt;/code&gt; long before I actually understood what it does.&lt;/p&gt;

&lt;p&gt;I picked it up from a senior developer on my first team. The rule was simple: &lt;em&gt;always rebase before you merge, keep the history clean.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I followed it. I typed the commands. I never questioned it.&lt;/p&gt;

&lt;p&gt;Then one day I rebased the wrong branch and rewrote a week of shared history. That was the day I finally sat down and learned what I had been doing.&lt;/p&gt;




&lt;h2&gt;
  
  
  What merge actually does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;git merge&lt;/code&gt; takes two branches and joins them. It creates a new commit — a &lt;strong&gt;merge commit&lt;/strong&gt; — that has two parents: the tip of your branch and the tip of the branch you're merging into.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# You're on feature/my-feature&lt;/span&gt;
git merge main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is a non-destructive operation. Nothing is rewritten. Your history grows a new node with two parents, and both lines of development are preserved exactly as they happened.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      A---B---C  feature
     /         \
D---E---F---G---H  main (merge commit)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The upside&lt;/strong&gt; — it's safe.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;The downside&lt;/strong&gt; — if you merge large feature branches frequently, the commit history can become harder to read.&lt;/p&gt;


&lt;h2&gt;
  
  
  What rebase actually does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;git rebase&lt;/code&gt; moves your commits. It takes the commits on your branch and replays them on top of another branch, one by one, as if you had started your work from there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# You're on feature/my-feature&lt;/span&gt;
git rebase main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Before
      A---B---C  feature
     /
D---E---F---G  main

# After
              A'--B'--C'  feature
             /
D---E---F---G  main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the prime marks — &lt;code&gt;A'&lt;/code&gt;, &lt;code&gt;B'&lt;/code&gt;, &lt;code&gt;C'&lt;/code&gt; are &lt;strong&gt;new commits&lt;/strong&gt;. Same changes, new hashes. Rebase rewrites history.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The upside&lt;/strong&gt; — your history looks linear and clean.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;The downside&lt;/strong&gt; — if anyone else has those original commits (&lt;code&gt;A&lt;/code&gt;, &lt;code&gt;B&lt;/code&gt;, &lt;code&gt;C&lt;/code&gt;), their history now diverges from yours. This is what I did wrong.&lt;/p&gt;


&lt;h2&gt;
  
  
  The golden rule of rebase
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Never rebase commits that exist outside your local repository.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you've pushed a branch and someone else has pulled it, rebasing rewrites commits they already have. Their history diverges. Pulling becomes painful. You'll see duplicate commits, conflicts that make no sense, and a very confused colleague.&lt;/p&gt;

&lt;p&gt;The safe version — rebase only your local, unpushed work. Once commits are shared, merge.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ✅ Safe — rebasing local work before pushing&lt;/span&gt;
git rebase main
git push origin feature/my-feature

&lt;span class="c"&gt;# ❌ Dangerous — rebasing already-pushed commits&lt;/span&gt;
git push origin feature/my-feature  &lt;span class="c"&gt;# already done&lt;/span&gt;
git rebase main                      &lt;span class="c"&gt;# rewrites shared history&lt;/span&gt;
git push &lt;span class="nt"&gt;--force&lt;/span&gt;                     &lt;span class="c"&gt;# now you've broken everyone else&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Interactive rebase
&lt;/h2&gt;

&lt;p&gt;Once you understand what rebase does, &lt;code&gt;git rebase -i&lt;/code&gt; becomes one of the most useful tools in Git.&lt;/p&gt;

&lt;p&gt;It lets you rewrite your local commit history before you share it — squash messy WIP commits into clean ones, reorder commits, edit commit messages, or split a large commit into smaller ones.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Rewrite the last 3 commits interactively&lt;/span&gt;
git rebase &lt;span class="nt"&gt;-i&lt;/span&gt; HEAD~3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This opens an editor where you choose what to do with each commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pick a1b2c3 Add order validation
pick d4e5f6 fix typo
pick g7h8i9 WIP: still debugging

&lt;span class="c"&gt;# Change to:&lt;/span&gt;
pick a1b2c3 Add order validation
squash d4e5f6 fix typo
drop g7h8i9 WIP: still debugging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result — one clean commit instead of three messy ones.&lt;/p&gt;

&lt;p&gt;This is what "clean history" actually means in practice — not avoiding merge commits, but being &lt;strong&gt;intentional about what you leave behind&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to use which
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use merge when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Integrating a completed feature branch into &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;develop&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Working on a shared branch where others have the same commits&lt;/li&gt;
&lt;li&gt;You want to preserve the full context of when and how branches diverged&lt;/li&gt;
&lt;li&gt;Merging release branches or hotfixes where an audit trail matters&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use rebase when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Updating your local feature branch with changes from &lt;code&gt;main&lt;/code&gt; before pushing&lt;/li&gt;
&lt;li&gt;Cleaning up local commit history before opening a PR (with &lt;code&gt;-i&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Keeping a long-lived feature branch up to date with the base branch&lt;/li&gt;
&lt;li&gt;Working solo on a branch nobody else has touched&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Never rebase:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Commits that have already been pushed and shared&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;main&lt;/code&gt;, &lt;code&gt;develop&lt;/code&gt;, or any branch others are working from&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The workflow that actually works
&lt;/h2&gt;

&lt;p&gt;In practice, most teams land somewhere between "always merge" and "always rebase." Here's a workflow that's served me well:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Work locally on a feature branch&lt;/li&gt;
&lt;li&gt;Before opening a PR, &lt;code&gt;git rebase -i&lt;/code&gt; to clean up commits&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git rebase main&lt;/code&gt; to update your branch with latest changes&lt;/li&gt;
&lt;li&gt;Push and open the PR&lt;/li&gt;
&lt;li&gt;Merge the PR into &lt;code&gt;main&lt;/code&gt; (with a merge commit or squash merge — team's call)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This gives you a clean PR history while preserving the merge commit that marks when a feature landed in &lt;code&gt;main&lt;/code&gt;. Best of both approaches.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I wish I had understood earlier
&lt;/h2&gt;

&lt;p&gt;Rebase and merge aren't competing philosophies. They're different tools for different moments in a workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Merge preserves history. Rebase rewrites it.&lt;/strong&gt; Both are useful — but rewriting shared history is always a mistake.&lt;/p&gt;

&lt;p&gt;I learned that the hard way. The senior developer who taught me to "always rebase" wasn't wrong — he just assumed I understood what rebase does. I didn't. I copied the command without understanding the consequence.&lt;/p&gt;

&lt;p&gt;Now I understand both. And I still rebase — just never the wrong branch.&lt;/p&gt;




&lt;p&gt;How does your team handle this? Do you have a strict rebase-only policy, or do you let developers decide? I'm curious whether the "clean history" argument is still winning in 2026. 👇&lt;/p&gt;

</description>
      <category>git</category>
      <category>webdev</category>
      <category>programming</category>
      <category>todayilearned</category>
    </item>
  </channel>
</rss>
