<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Rick Dev]]></title><description><![CDATA[Rick Dev]]></description><link>https://override.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 16 Apr 2026 08:04:51 GMT</lastBuildDate><atom:link href="https://override.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[.NET Aspire Deployment: The Hidden Costs Nobody Tells You About]]></title><description><![CDATA[I Needed My CRM to Scale Properly, So I Chose Microsoft Orleans with Aspire
I needed my CRM to scale properly, so I decided to use Microsoft Orleans in the infrastructure layer. With Aspire, you can scale each silo automatically.
Sounds great, right?...]]></description><link>https://override.dev/net-aspire-deployment-the-hidden-costs-nobody-tells-you-about</link><guid isPermaLink="true">https://override.dev/net-aspire-deployment-the-hidden-costs-nobody-tells-you-about</guid><category><![CDATA[Aspire ]]></category><category><![CDATA[dotnet]]></category><dc:creator><![CDATA[Rick]]></dc:creator><pubDate>Fri, 19 Dec 2025 14:39:21 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-i-needed-my-crm-to-scale-properly-so-i-chose-microsoft-orleans-with-aspire">I Needed My CRM to Scale Properly, So I Chose Microsoft Orleans with Aspire</h2>
<p>I needed my CRM to scale properly, so I decided to use Microsoft Orleans in the infrastructure layer. With Aspire, you can scale each silo automatically.</p>
<p>Sounds great, right? <strong>But there's a catch.</strong></p>
<hr />
<h2 id="heading-why-aspire-is-worth-it">Why Aspire is Worth It</h2>
<p>Aspire was built to simplify deployment and deliver one of the best developer experiences Microsoft has created. In my opinion, it sits alongside <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core and Microsoft Orleans as the best things Microsoft has shipped in years.</p>
<p>When you deploy with Aspire, the Azure Developer CLI (<code>azd</code>) generates a manifest file, creates Bicep infrastructure files, pushes container images to Azure Container Registry (ACR), and updates your Azure Container Apps with the new versions. All with a single command.</p>
<p><strong>But if you deploy with default settings, several things will hit you hard in production.</strong></p>
<hr />
<h2 id="heading-the-difference-between-provisioning-and-deploying">The Difference Between Provisioning and Deploying</h2>
<p>This is the first trap.</p>
<p><code>azd up</code> does both provisioning AND deployment. If you run it again, it will replace your entire infrastructure and <strong>eliminate all your data</strong>. Be extremely careful with this command in production.</p>
<p><code>azd deploy</code> is what you want for regular deployments. It only deploys your code changes without touching your infrastructure. Use this for your CI/CD pipelines after the initial setup.</p>
<blockquote>
<p><strong>The lesson here is simple: provision once, deploy many times.</strong></p>
</blockquote>
<hr />
<h2 id="heading-secrets-dont-persist-by-default">Secrets Don't Persist by Default</h2>
<p>Here's another gotcha that will waste hours of your time.</p>
<p>When you first run <code>azd up</code>, it prompts you for parameters and secrets. Those values get stored locally in <code>~/.aspire/deployments/{AppHostSha}/{environment}.json</code>. The problem? Every time you deploy, you might need to reconfigure environment variables in the Azure portal.</p>
<p><strong>The solution</strong> is using <code>AddParameter</code> with the <code>secret: true</code> flag. This creates proper parameter resources that persist across deployments. When combined with Azure Key Vault integration, your secrets remain stable between deployments instead of being regenerated or lost.</p>
<p>Additionally, using <code>azd provision</code> separately will prompt for secrets and store them in a local vault file, making subsequent <code>azd deploy</code> commands smoother.</p>
<hr />
<h2 id="heading-the-acr-cost-explosion">The ACR Cost Explosion</h2>
<p>Now let's talk about what will really kill your wallet: <strong>Azure Container Registry</strong>.</p>
<p>ACR has no free tier. Here's the pricing breakdown:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Tier</td><td>Daily Cost</td><td>Monthly Cost</td><td>Storage Included</td></tr>
</thead>
<tbody>
<tr>
<td>Basic</td><td>~$0.167</td><td>~$5</td><td>10 GB</td></tr>
<tr>
<td>Standard</td><td>~$0.667</td><td>~$20</td><td>100 GB</td></tr>
<tr>
<td>Premium</td><td>~$1.66</td><td>~$50</td><td>500 GB</td></tr>
</tbody>
</table>
</div><h3 id="heading-heres-where-it-gets-ugly">Here's where it gets ugly</h3>
<p>Every single deploy creates a new container image. These images are stored for rollback purposes—or as I see it, it's Microsoft's way of making money from accumulated storage.</p>
<p>If you have multiple services in your Aspire solution (and you probably do), each one generates a new image on every deployment. Deploy 3 services twice a day for a month? That's <strong>180 images</strong>. At the end of the month, you could easily have 10+ GB of space used in ACR.</p>
<p>And it doesn't stop there. The storage overage charges apply daily. Within 6 months, you could be paying up to <strong>500% more</strong> than you expected.</p>
<blockquote>
<p><strong>This is a business. Always approach technology pragmatically.</strong></p>
</blockquote>
<hr />
<h2 id="heading-how-i-solved-it">How I Solved It</h2>
<p>ACR has a feature called <strong>ACR Tasks</strong> that can run scheduled commands. You can create a purge task that automatically cleans up old images.</p>
<p>What I did is simple: every 6 hours, a scheduled task searches for all images in the ACR and deletes everything except the 3 most recent images per repository.</p>
<p>The command uses <code>acr purge</code> with these key parameters:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Parameter</td><td>Purpose</td></tr>
</thead>
<tbody>
<tr>
<td><code>--filter '*:.*'</code></td><td>Target all repositories and tags</td></tr>
<tr>
<td><code>--ago 0d</code></td><td>Include all images regardless of age</td></tr>
<tr>
<td><code>--keep 3</code></td><td>Retain only the 3 newest images</td></tr>
<tr>
<td><code>--untagged</code></td><td>Also clean up orphaned manifests</td></tr>
</tbody>
</table>
</div><p>You can run a <strong>dry-run</strong> first to see what would be deleted without actually removing anything. Once you're confident, schedule the task using a cron expression.</p>
<p>This approach has saved me significant money over time. In the long run, your wallet will thank you.</p>
<hr />
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>Aspire is an incredible technology for local development and cloud deployment. Azure Container Apps gives you serverless scaling with built-in Kubernetes (KEDA) for autoscaling based on CPU, memory, or custom events.</p>
<p><strong>But remember:</strong></p>
<ol>
<li><p>Use <code>azd deploy</code> for regular deployments, not <code>azd up</code></p>
</li>
<li><p>Configure your parameters properly with <code>AddParameter</code> to persist secrets</p>
</li>
<li><p>Set up ACR purge tasks from day one to control storage costs</p>
</li>
<li><p>Monitor your ACR usage regularly in the Azure Portal</p>
</li>
</ol>
<hr />
<p><strong>The convenience of Aspire is real. The costs of not understanding these details are also very real.</strong></p>
]]></content:encoded></item><item><title><![CDATA[Component-Based Capability Pattern: A Better Way to Handle Platform Differences]]></title><description><![CDATA[The Problem We Keep Running Into
Building a system that talks to multiple platforms? You've probably written code like this:
public class Platform
{
    public string Name { get; set; }

    // Text stuff
    public bool SupportsText { get; set; }
  ...]]></description><link>https://override.dev/component-based-capability-pattern-a-better-way-to-handle-platform-differences</link><guid isPermaLink="true">https://override.dev/component-based-capability-pattern-a-better-way-to-handle-platform-differences</guid><category><![CDATA[dotnet]]></category><category><![CDATA[asp.net core]]></category><category><![CDATA[C#]]></category><dc:creator><![CDATA[Rick]]></dc:creator><pubDate>Sun, 09 Nov 2025 09:51:36 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-the-problem-we-keep-running-into">The Problem We Keep Running Into</h2>
<p>Building a system that talks to multiple platforms? You've probably written code like this:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Platform</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Name { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-comment">// Text stuff</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> SupportsText { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> MaxTextLength { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> SupportsMarkdown { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> SupportsHtml { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-comment">// Voice stuff</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> SupportsVoice { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> MaxVoiceDuration { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span>[] SupportedVoiceCodecs { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-comment">// Files</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> SupportsFiles { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">long</span> MaxFileSize { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span>[] AllowedMimeTypes { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-comment">// Reactions</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> SupportsReactions { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span>[] AllowedReactions { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-comment">// ...and it keeps growing</span>
}
</code></pre>
<p>This works at first. Then it becomes a nightmare. You end up with 50+ properties, half of them nullable, and you're never quite sure which combinations are valid. <code>SupportsVoice = true</code> but <code>SupportedVoiceCodecs = null</code>? That's a runtime bug waiting to happen.</p>
<p>The real pain hits when you're trying to figure out what a platform can actually do. You scan through dozens of boolean flags, hoping you didn't miss one. Your validation logic is scattered everywhere. Testing means setting up massive mock objects.</p>
<h2 id="heading-what-if-we-treated-capabilities-as-components">What If We Treated Capabilities as Components?</h2>
<p>Here's a different approach. Instead of boolean flags with loosely-related configuration, we make each capability its own thing - a well-defined interface that either exists or doesn't.</p>
<p>The idea is straightforward:</p>
<ul>
<li><p>Each capability is an interface with its own rules</p>
</li>
<li><p>Platforms compose themselves by adding capabilities they support</p>
</li>
<li><p>Your code asks "do you have X?" before trying to use it</p>
</li>
<li><p>Everything is type-safe and the compiler helps you</p>
</li>
</ul>
<p>Let's build it.</p>
<h2 id="heading-defining-capabilities">Defining Capabilities</h2>
<p>Start with interfaces for each capability:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IPlatformCapability</span> 
{
    <span class="hljs-keyword">string</span> CapabilityName { <span class="hljs-keyword">get</span>; }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">ITextMessageCapability</span> : <span class="hljs-title">IPlatformCapability</span>
{
    <span class="hljs-keyword">int</span> MaxLength { <span class="hljs-keyword">get</span>; }
    <span class="hljs-keyword">bool</span> SupportsMarkdown { <span class="hljs-keyword">get</span>; }
    <span class="hljs-keyword">bool</span> SupportsHtml { <span class="hljs-keyword">get</span>; }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IVoiceMessageCapability</span> : <span class="hljs-title">IPlatformCapability</span>
{
    <span class="hljs-keyword">int</span> MaxDurationSeconds { <span class="hljs-keyword">get</span>; }
    List&lt;<span class="hljs-keyword">string</span>&gt; SupportedCodecs { <span class="hljs-keyword">get</span>; }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IFileUploadCapability</span> : <span class="hljs-title">IPlatformCapability</span>
{
    <span class="hljs-keyword">long</span> MaxFileSizeBytes { <span class="hljs-keyword">get</span>; }
    List&lt;<span class="hljs-keyword">string</span>&gt; AllowedMimeTypes { <span class="hljs-keyword">get</span>; }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IReactionCapability</span> : <span class="hljs-title">IPlatformCapability</span>
{
    List&lt;<span class="hljs-keyword">string</span>&gt; AllowedReactions { <span class="hljs-keyword">get</span>; }
    <span class="hljs-keyword">int</span> MaxReactionsPerMessage { <span class="hljs-keyword">get</span>; }
}
</code></pre>
<p>Each interface groups related properties together. If you have <code>IVoiceMessageCapability</code>, you know you'll get <code>MaxDurationSeconds</code> and <code>SupportedCodecs</code> - they come as a package.</p>
<h2 id="heading-platform-specific-implementations">Platform-Specific Implementations</h2>
<p>Now implement these for each platform. Here's where it gets interesting - you can add platform-specific properties that go beyond the interface:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">WhatsAppTextCapability</span> : <span class="hljs-title">ITextMessageCapability</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> CapabilityName =&gt; <span class="hljs-string">"Text"</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> MaxLength =&gt; <span class="hljs-number">4096</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> SupportsMarkdown =&gt; <span class="hljs-literal">true</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> SupportsHtml =&gt; <span class="hljs-literal">false</span>;

    <span class="hljs-comment">// WhatsApp-specific stuff</span>
    <span class="hljs-keyword">public</span> List&lt;<span class="hljs-keyword">string</span>&gt; SupportedMentionTypes =&gt; <span class="hljs-keyword">new</span>() { <span class="hljs-string">"@user"</span>, <span class="hljs-string">"@all"</span> };
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">WhatsAppVoiceCapability</span> : <span class="hljs-title">IVoiceMessageCapability</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> CapabilityName =&gt; <span class="hljs-string">"Voice"</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> MaxDurationSeconds =&gt; <span class="hljs-number">900</span>;
    <span class="hljs-keyword">public</span> List&lt;<span class="hljs-keyword">string</span>&gt; SupportedCodecs =&gt; <span class="hljs-keyword">new</span>() { <span class="hljs-string">"opus"</span>, <span class="hljs-string">"aac"</span> };

    <span class="hljs-comment">// WhatsApp does auto-transcription</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> AutoTranscriptionAvailable =&gt; <span class="hljs-literal">true</span>;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">TelegramTextCapability</span> : <span class="hljs-title">ITextMessageCapability</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> CapabilityName =&gt; <span class="hljs-string">"Text"</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> MaxLength =&gt; <span class="hljs-number">4096</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> SupportsMarkdown =&gt; <span class="hljs-literal">true</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> SupportsHtml =&gt; <span class="hljs-literal">true</span>; <span class="hljs-comment">// Telegram supports HTML!</span>

    <span class="hljs-comment">// Telegram-specific</span>
    <span class="hljs-keyword">public</span> List&lt;<span class="hljs-keyword">string</span>&gt; SupportedEntityTypes =&gt; <span class="hljs-keyword">new</span>() 
    { 
        <span class="hljs-string">"mention"</span>, <span class="hljs-string">"hashtag"</span>, <span class="hljs-string">"url"</span>, <span class="hljs-string">"bot_command"</span> 
    };
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">TelegramFileCapability</span> : <span class="hljs-title">IFileUploadCapability</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> CapabilityName =&gt; <span class="hljs-string">"FileUpload"</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">long</span> MaxFileSizeBytes =&gt; <span class="hljs-number">2L</span> * <span class="hljs-number">1024</span> * <span class="hljs-number">1024</span> * <span class="hljs-number">1024</span>; <span class="hljs-comment">// 2GB</span>
    <span class="hljs-keyword">public</span> List&lt;<span class="hljs-keyword">string</span>&gt; AllowedMimeTypes =&gt; <span class="hljs-keyword">new</span>() { <span class="hljs-string">"*/*"</span> };

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> SupportsFileStreaming =&gt; <span class="hljs-literal">true</span>;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">SmsTextCapability</span> : <span class="hljs-title">ITextMessageCapability</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> CapabilityName =&gt; <span class="hljs-string">"Text"</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> MaxLength =&gt; <span class="hljs-number">160</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> SupportsMarkdown =&gt; <span class="hljs-literal">false</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> SupportsHtml =&gt; <span class="hljs-literal">false</span>;

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> AutoSplitLongMessages =&gt; <span class="hljs-literal">true</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> MaxConcatenatedSegments =&gt; <span class="hljs-number">10</span>;
}
</code></pre>
<p>Notice how SMS doesn't have voice or file capabilities - it just doesn't implement those interfaces. That's the point.</p>
<h2 id="heading-the-platform-class">The Platform Class</h2>
<p>This is where capabilities live:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Platform</span>
{
    <span class="hljs-keyword">public</span> Guid Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Name { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Provider { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> Dictionary&lt;Type, IPlatformCapability&gt; _capabilities = <span class="hljs-keyword">new</span>();

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">AddCapability</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params">T capability</span>) <span class="hljs-keyword">where</span> T : IPlatformCapability</span>
    {
        _capabilities[<span class="hljs-keyword">typeof</span>(T)] = capability;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">HasCapability</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params"></span>) <span class="hljs-keyword">where</span> T : IPlatformCapability</span>
    {
        <span class="hljs-keyword">return</span> _capabilities.ContainsKey(<span class="hljs-keyword">typeof</span>(T));
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> T <span class="hljs-title">GetCapability</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params"></span>) <span class="hljs-keyword">where</span> T : IPlatformCapability</span>
    {
        <span class="hljs-keyword">return</span> _capabilities.TryGetValue(<span class="hljs-keyword">typeof</span>(T), <span class="hljs-keyword">out</span> <span class="hljs-keyword">var</span> capability) 
            ? (T)capability 
            : <span class="hljs-keyword">default</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> TConcrete <span class="hljs-title">GetConcreteCapability</span>&lt;<span class="hljs-title">TConcrete</span>&gt;(<span class="hljs-params"></span>) 
        <span class="hljs-keyword">where</span> TConcrete : class, IPlatformCapability</span>
    {
        <span class="hljs-keyword">return</span> _capabilities.Values.OfType&lt;TConcrete&gt;().FirstOrDefault();
    }
}
</code></pre>
<p>Pretty simple. It's just a type-safe registry of capabilities.</p>
<h2 id="heading-setting-up-platforms">Setting Up Platforms</h2>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">PlatformFactory</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> Platform <span class="hljs-title">CreateWhatsApp</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">var</span> platform = <span class="hljs-keyword">new</span> Platform 
        { 
            Id = Guid.NewGuid(),
            Name = <span class="hljs-string">"WhatsApp Business"</span>,
            Provider = <span class="hljs-string">"whatsapp"</span>
        };

        platform.AddCapability(<span class="hljs-keyword">new</span> WhatsAppTextCapability());
        platform.AddCapability(<span class="hljs-keyword">new</span> WhatsAppVoiceCapability());
        platform.AddCapability(<span class="hljs-keyword">new</span> WhatsAppReactionCapability());
        platform.AddCapability(<span class="hljs-keyword">new</span> WhatsAppFileCapability());

        <span class="hljs-keyword">return</span> platform;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> Platform <span class="hljs-title">CreateTelegram</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">var</span> platform = <span class="hljs-keyword">new</span> Platform 
        { 
            Id = Guid.NewGuid(),
            Name = <span class="hljs-string">"Telegram Bot"</span>,
            Provider = <span class="hljs-string">"telegram"</span>
        };

        platform.AddCapability(<span class="hljs-keyword">new</span> TelegramTextCapability());
        platform.AddCapability(<span class="hljs-keyword">new</span> TelegramFileCapability());
        <span class="hljs-comment">// No reactions - Telegram doesn't support them</span>

        <span class="hljs-keyword">return</span> platform;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> Platform <span class="hljs-title">CreateSMS</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">var</span> platform = <span class="hljs-keyword">new</span> Platform 
        { 
            Id = Guid.NewGuid(),
            Name = <span class="hljs-string">"SMS Gateway"</span>,
            Provider = <span class="hljs-string">"sms"</span>
        };

        platform.AddCapability(<span class="hljs-keyword">new</span> SmsTextCapability());
        <span class="hljs-comment">// Just text, nothing else</span>

        <span class="hljs-keyword">return</span> platform;
    }
}
</code></pre>
<p>Look how clear this is. You can see exactly what each platform supports.</p>
<h2 id="heading-using-it">Using It</h2>
<p>Here's where it pays off:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MessageService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;Result&gt; <span class="hljs-title">SendTextMessage</span>(<span class="hljs-params">Platform platform, <span class="hljs-keyword">string</span> text</span>)</span>
    {
        <span class="hljs-comment">// Check if the platform can do this</span>
        <span class="hljs-keyword">if</span> (!platform.HasCapability&lt;ITextMessageCapability&gt;())
        {
            <span class="hljs-keyword">return</span> Result.Fail(<span class="hljs-string">$"<span class="hljs-subst">{platform.Name}</span> doesn't support text messages"</span>);
        }

        <span class="hljs-comment">// Get the capability - it's never null here</span>
        <span class="hljs-keyword">var</span> textCap = platform.GetCapability&lt;ITextMessageCapability&gt;();

        <span class="hljs-comment">// Validate</span>
        <span class="hljs-keyword">if</span> (text.Length &gt; textCap.MaxLength)
        {
            <span class="hljs-keyword">return</span> Result.Fail(<span class="hljs-string">$"Text too long (max <span class="hljs-subst">{textCap.MaxLength}</span> characters)"</span>);
        }

        <span class="hljs-comment">// Handle markdown</span>
        <span class="hljs-keyword">var</span> processedText = textCap.SupportsMarkdown 
            ? ProcessMarkdown(text) 
            : StripMarkdown(text);

        <span class="hljs-keyword">var</span> handler = GetHandler(platform.Provider);
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> handler.SendTextAsync(processedText);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;Result&gt; <span class="hljs-title">SendVoiceMessage</span>(<span class="hljs-params">
        Platform platform,
        Stream audioStream,
        <span class="hljs-keyword">string</span> codec</span>)</span>
    {
        <span class="hljs-keyword">if</span> (!platform.HasCapability&lt;IVoiceMessageCapability&gt;())
        {
            <span class="hljs-keyword">return</span> Result.Fail(<span class="hljs-string">$"<span class="hljs-subst">{platform.Name}</span> doesn't support voice"</span>);
        }

        <span class="hljs-keyword">var</span> voiceCap = platform.GetCapability&lt;IVoiceMessageCapability&gt;();

        <span class="hljs-keyword">if</span> (!voiceCap.SupportedCodecs.Contains(codec))
        {
            <span class="hljs-keyword">return</span> Result.Fail(
                <span class="hljs-string">$"Codec '<span class="hljs-subst">{codec}</span>' not supported. Use: <span class="hljs-subst">{<span class="hljs-keyword">string</span>.Join(<span class="hljs-string">", "</span>, voiceCap.SupportedCodecs)}</span>"</span>);
        }

        <span class="hljs-keyword">var</span> handler = GetHandler(platform.Provider);
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> handler.SendVoiceAsync(audioStream, codec);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;Result&gt; <span class="hljs-title">AddReaction</span>(<span class="hljs-params">
        Platform platform,
        <span class="hljs-keyword">string</span> messageId,
        <span class="hljs-keyword">string</span> emoji</span>)</span>
    {
        <span class="hljs-keyword">if</span> (!platform.HasCapability&lt;IReactionCapability&gt;())
        {
            <span class="hljs-keyword">return</span> Result.Fail(<span class="hljs-string">$"<span class="hljs-subst">{platform.Name}</span> doesn't support reactions"</span>);
        }

        <span class="hljs-keyword">var</span> reactionCap = platform.GetCapability&lt;IReactionCapability&gt;();

        <span class="hljs-keyword">if</span> (!reactionCap.AllowedReactions.Contains(emoji))
        {
            <span class="hljs-keyword">return</span> Result.Fail(
                <span class="hljs-string">$"Emoji '<span class="hljs-subst">{emoji}</span>' not allowed on <span class="hljs-subst">{platform.Name}</span>"</span>);
        }

        <span class="hljs-keyword">var</span> handler = GetHandler(platform.Provider);
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> handler.AddReactionAsync(messageId, emoji);
    }
}
</code></pre>
<p>The pattern is consistent: check for capability, get it, validate with it, delegate to handler.</p>
<h2 id="heading-when-you-need-platform-specific-stuff">When You Need Platform-Specific Stuff</h2>
<p>Sometimes you need to access properties that only exist on a specific platform:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;Result&gt; <span class="hljs-title">SendWithAutoTranscription</span>(<span class="hljs-params">
    Platform platform,
    Stream voiceStream</span>)</span>
{
    <span class="hljs-comment">// First check the interface</span>
    <span class="hljs-keyword">if</span> (!platform.HasCapability&lt;IVoiceMessageCapability&gt;())
    {
        <span class="hljs-keyword">return</span> Result.Fail(<span class="hljs-string">"Voice not supported"</span>);
    }

    <span class="hljs-comment">// Get the concrete WhatsApp implementation</span>
    <span class="hljs-keyword">var</span> whatsappVoice = platform.GetConcreteCapability&lt;WhatsAppVoiceCapability&gt;();

    <span class="hljs-keyword">if</span> (whatsappVoice == <span class="hljs-literal">null</span>)
    {
        <span class="hljs-keyword">return</span> Result.Fail(<span class="hljs-string">"Auto-transcription only available on WhatsApp"</span>);
    }

    <span class="hljs-keyword">if</span> (!whatsappVoice.AutoTranscriptionAvailable)
    {
        <span class="hljs-keyword">return</span> Result.Fail(<span class="hljs-string">"Auto-transcription not available"</span>);
    }

    <span class="hljs-comment">// Use WhatsApp-specific feature</span>
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> SendWithTranscription(voiceStream);
}
</code></pre>
<h2 id="heading-comparing-approaches">Comparing Approaches</h2>
<p>Let's see how this stacks up against the traditional ways.</p>
<h3 id="heading-feature-flags-the-common-way">Feature Flags (The Common Way)</h3>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Platform</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> SupportsVoice { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> MaxVoiceDuration { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span>[] SupportedVoiceCodecs { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-comment">// ... 50 more properties</span>
}

<span class="hljs-comment">// Using it</span>
<span class="hljs-keyword">if</span> (!platform.SupportsVoice)
    <span class="hljs-keyword">return</span> Result.Fail(<span class="hljs-string">"Not supported"</span>);

<span class="hljs-keyword">if</span> (platform.MaxVoiceDuration &lt;= <span class="hljs-number">0</span>)
    <span class="hljs-keyword">return</span> Result.Fail(<span class="hljs-string">"Invalid config"</span>);

<span class="hljs-keyword">if</span> (platform.SupportedVoiceCodecs == <span class="hljs-literal">null</span> || platform.SupportedVoiceCodecs.Length == <span class="hljs-number">0</span>)
    <span class="hljs-keyword">return</span> Result.Fail(<span class="hljs-string">"No codecs configured"</span>);
</code></pre>
<p><strong>Problems:</strong></p>
<ul>
<li><p>Properties can be inconsistent (<code>SupportsVoice = true</code> but <code>SupportedVoiceCodecs = null</code>)</p>
</li>
<li><p>Class gets huge as you add platforms</p>
</li>
<li><p>No way to know which properties go together</p>
</li>
<li><p>Easy to forget validation checks</p>
</li>
<li><p>Testing means mocking tons of properties</p>
</li>
</ul>
<h3 id="heading-strategy-pattern-the-oop-way">Strategy Pattern (The OOP Way)</h3>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IMessageSender</span>
{
    <span class="hljs-function">Task&lt;Result&gt; <span class="hljs-title">SendTextAsync</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> text</span>)</span>;
    <span class="hljs-function">Task&lt;Result&gt; <span class="hljs-title">SendVoiceAsync</span>(<span class="hljs-params">Stream audio, <span class="hljs-keyword">string</span> codec</span>)</span>;
    <span class="hljs-function">Task&lt;Result&gt; <span class="hljs-title">SendFileAsync</span>(<span class="hljs-params">Stream file, <span class="hljs-keyword">string</span> mimeType</span>)</span>;
    <span class="hljs-function">Task&lt;Result&gt; <span class="hljs-title">AddReactionAsync</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> messageId, <span class="hljs-keyword">string</span> emoji</span>)</span>;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">SmsSender</span> : <span class="hljs-title">IMessageSender</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> Task&lt;Result&gt; <span class="hljs-title">SendTextAsync</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> text</span>)</span>
    {
        <span class="hljs-comment">// Actually implemented</span>
    }

    <span class="hljs-comment">// SMS doesn't support these, but we're forced to implement them</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Task&lt;Result&gt; <span class="hljs-title">SendVoiceAsync</span>(<span class="hljs-params">Stream audio, <span class="hljs-keyword">string</span> codec</span>)</span>
    {
        <span class="hljs-keyword">return</span> Task.FromResult(Result.Fail(<span class="hljs-string">"Not supported"</span>));
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> Task&lt;Result&gt; <span class="hljs-title">AddReactionAsync</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> messageId, <span class="hljs-keyword">string</span> emoji</span>)</span>
    {
        <span class="hljs-keyword">return</span> Task.FromResult(Result.Fail(<span class="hljs-string">"Not supported"</span>));
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> Task&lt;Result&gt; <span class="hljs-title">SendFileAsync</span>(<span class="hljs-params">Stream file, <span class="hljs-keyword">string</span> mimeType</span>)</span>
    {
        <span class="hljs-keyword">return</span> Task.FromResult(Result.Fail(<span class="hljs-string">"Not supported"</span>));
    }
}
</code></pre>
<p><strong>Problems:</strong></p>
<ul>
<li><p>Interface forces you to implement methods you don't support</p>
</li>
<li><p>No way to check capabilities before calling</p>
</li>
<li><p>Validation logic duplicated in every implementation</p>
</li>
<li><p>Testing requires mocking entire interface</p>
</li>
</ul>
<h3 id="heading-capability-pattern-our-way">Capability Pattern (Our Way)</h3>
<pre><code class="lang-csharp"><span class="hljs-comment">// Platform only has capabilities it supports</span>
<span class="hljs-keyword">var</span> sms = <span class="hljs-keyword">new</span> Platform();
sms.AddCapability(<span class="hljs-keyword">new</span> SmsTextCapability());

<span class="hljs-comment">// Check before using</span>
<span class="hljs-keyword">if</span> (!sms.HasCapability&lt;IVoiceMessageCapability&gt;())
{
    <span class="hljs-comment">// We know voice isn't supported - don't even try</span>
}

<span class="hljs-comment">// If it has the capability, we know it's fully configured</span>
<span class="hljs-keyword">var</span> textCap = sms.GetCapability&lt;ITextMessageCapability&gt;();
<span class="hljs-comment">// textCap is never null here, and all its properties are valid</span>
</code></pre>
<p><strong>Benefits:</strong></p>
<ul>
<li><p>Impossible to have inconsistent state</p>
</li>
<li><p>Clear what each platform supports</p>
</li>
<li><p>Validation is centralized</p>
</li>
<li><p>Easy to test (mock just the capability you need)</p>
</li>
<li><p>New platforms don't change existing code</p>
</li>
</ul>
<h2 id="heading-real-world-benefits">Real-World Benefits</h2>
<p>I've been using this pattern in production for my CRM that integrates with messaging platforms. Here's what I've seen:</p>
<p><strong>Adding a new platform used to take 2-3 days.</strong> Now it takes 2-3 hours. I just implement the capabilities it supports and I'm done.</p>
<p><strong>Bug rate dropped significantly.</strong> The compiler catches most mistakes. If I try to use a capability that doesn't exist, I get a compilation error, not a runtime crash.</p>
<p><strong>Code is self-documenting.</strong> Want to know what WhatsApp supports? Look at which capabilities it has. Want to know the limitations? Look at the capability implementation.</p>
<p><strong>Testing is easier.</strong> I mock just the capability I'm testing, not the entire platform.</p>
<p><strong>Onboarding would be faster.</strong> When I eventually bring on other developers, they'll understand the system quickly because capabilities are explicit and well-defined.</p>
<h2 id="heading-when-to-use-this">When To Use This</h2>
<p>This pattern makes sense when:</p>
<ul>
<li><p>You integrate with 5+ platforms with different features</p>
</li>
<li><p>Capabilities change frequently (platforms add features)</p>
</li>
<li><p>You need type-safe feature detection</p>
</li>
<li><p>You want to avoid giant classes with dozens of boolean flags</p>
</li>
<li><p>Testing is important to you</p>
</li>
</ul>
<p>It might be overkill if:</p>
<ul>
<li><p>You only have 2-3 platforms that rarely change</p>
</li>
<li><p>All platforms support mostly the same things</p>
</li>
<li><p>You're building an MVP and need to ship fast</p>
</li>
</ul>
<h2 id="heading-implementation-tips">Implementation Tips</h2>
<p><strong>Start small.</strong> Don't try to model every capability upfront. Start with 3-4 core capabilities and expand as needed.</p>
<p><strong>Group related properties.</strong> If properties always go together, they belong in the same capability interface.</p>
<p><strong>Make capabilities immutable.</strong> Use <code>{ get; init; }</code> or readonly properties. Capabilities shouldn't change after creation.</p>
<p><strong>Serialize carefully.</strong> If you're using Orleans or need persistence, you'll need a serialization strategy. JSON with type discriminators works well.</p>
<p><strong>Document platform-specific properties.</strong> When you add properties beyond the interface, comment why they exist.</p>
<h2 id="heading-code-that-inspired-this">Code That Inspired This</h2>
<p>This pattern draws from several places:</p>
<p><strong>ASP.NET Core's Feature Collections</strong> use a similar approach for HTTP features:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> feature = httpContext.Features.Get&lt;IHttpConnectionFeature&gt;();
</code></pre>
<p><strong>ECS (Entity Component System)</strong> in game engines uses composable components, though without the systems loop.</p>
<p><strong>Browser feature detection</strong> checks for capabilities at runtime:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">if</span> (<span class="hljs-string">'geolocation'</span> <span class="hljs-keyword">in</span> navigator) {
    <span class="hljs-comment">// Use geolocation</span>
}
</code></pre>
<p>I took these ideas and applied them to platform integration with type-safe interfaces.</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>The Component-Based Capability Pattern isn't revolutionary - it's a synthesis of existing ideas applied to a specific problem. But it works really well for handling platform differences.</p>
<p>Instead of maintaining a growing list of boolean flags, you compose platforms from well-defined capabilities. Your code checks for capabilities before using them. The compiler helps you catch mistakes. Testing is easier. New platforms are straightforward to add.</p>
<p>If you're building something that integrates with multiple platforms, give this pattern a shot. Start with a couple of capabilities and see how it feels. You might find it's exactly what you needed.</p>
<hr />
<p><em>I'm using this pattern in production in</em> <a target="_blank" href="https://github.com/yourrepo"><em>Kasumi</em></a><em>, my CRM built with ASP.NET Core and Microsoft Orleans.</em></p>
]]></content:encoded></item><item><title><![CDATA[Caring About Performance Is Nitpicking Now, I Guess]]></title><description><![CDATA[Last week, I found myself in one of those situations that every senior developer knows all too well. You know the one—you raise a legitimate architectural concern, and someone dismisses it as "nitpicking."
Here's what happened: I was reviewing some A...]]></description><link>https://override.dev/caring-about-performance-is-nitpicking-now-i-guess</link><guid isPermaLink="true">https://override.dev/caring-about-performance-is-nitpicking-now-i-guess</guid><category><![CDATA[efcore]]></category><category><![CDATA[performance]]></category><category><![CDATA[dotnet]]></category><dc:creator><![CDATA[Rick]]></dc:creator><pubDate>Mon, 18 Aug 2025 10:27:58 GMT</pubDate><content:encoded><![CDATA[<p>Last week, I found myself in one of those situations that every senior developer knows all too well. You know the one—you raise a legitimate architectural concern, and someone dismisses it as "nitpicking."</p>
<p>Here's what happened: I was reviewing some Azure Service Bus message processing code that used the standard documented approach with <code>IServiceScopeFactory</code>. When I mentioned that creating scopes for every message could impact performance, especially if message volume grows, I got hit with: <em>"Stop nitpicking. This is the documented best practice, and our volume is low anyway."</em></p>
<p>This got me thinking about the fine line between nitpicking and legitimate technical concerns. Spoiler alert: they're not the same thing.</p>
<h2 id="heading-so-heres-what-went-down">So here's what went down</h2>
<p>The implementation looked like this:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ServiceBusMessageHandler</span> : <span class="hljs-title">BackgroundService</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IServiceScopeFactory _scopeFactory;

    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">ExecuteAsync</span>(<span class="hljs-params">CancellationToken stoppingToken</span>)</span>
    {
        <span class="hljs-keyword">await</span> <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> message <span class="hljs-keyword">in</span> messageStream)
        {
            <span class="hljs-keyword">using</span> <span class="hljs-keyword">var</span> scope = _scopeFactory.CreateScope();
            <span class="hljs-keyword">var</span> dbContext = scope.ServiceProvider.GetRequiredService&lt;MyDbContext&gt;();

            <span class="hljs-keyword">await</span> ProcessMessage(message, dbContext);
        }
    }
}
</code></pre>
<p>Is this correct? Absolutely. Is it the safest approach? Yes. Could it become a bottleneck? Also yes.</p>
<p>But apparently, pointing this out made me "nitpicky."</p>
<h2 id="heading-look-theres-a-difference-between-being-picky-and-being-concerned">Look, there's a difference between being picky and being concerned</h2>
<p>Let me clear something up. <strong>Real nitpicking</strong> in code reviews looks like this:</p>
<ul>
<li><p>"This variable should be called <code>userData</code> instead of <code>data</code>"</p>
</li>
<li><p>"You're missing a space after the <code>if</code> statement"</p>
</li>
<li><p>"Use <code>var</code> instead of the explicit type here"</p>
</li>
<li><p>"This comment has a typo"</p>
</li>
</ul>
<p><strong>Legitimate architectural concerns</strong> look like this:</p>
<ul>
<li><p>"This approach might not scale well"</p>
</li>
<li><p>"This could create memory pressure under load"</p>
</li>
<li><p>"Have we considered the performance implications?"</p>
</li>
<li><p>"This pattern has caused issues in similar systems"</p>
</li>
</ul>
<p>See the difference? One is about style preferences. The other is about system design and future-proofing.</p>
<h2 id="heading-but-wait-the-volume-is-low-so-it-doesnt-matter-right">But wait, the volume is low so it doesn't matter, right?</h2>
<p>Here's where things got really frustrating. The response to my concern was essentially: <em>"Our message volume is low, so it doesn't matter."</em></p>
<p>This is such a dangerous mindset. Here's why:</p>
<h3 id="heading-systems-grow">Systems Grow</h3>
<p>That "low volume" system today could be processing 10x the messages next month. I've seen it happen countless times. You start with 100 messages per day, and suddenly you're dealing with 100,000.</p>
<h3 id="heading-performance-habits-matter">Performance Habits Matter</h3>
<p>Building performance awareness into your architecture from day one is way easier than retrofitting it later. It's not about premature optimization—it's about informed design decisions.</p>
<h3 id="heading-the-real-cost-of-later">The Real Cost of "Later"</h3>
<p>Refactoring for performance after the fact typically costs 10x more than considering it upfront. Plus, you're often doing it under pressure when the system is already struggling.</p>
<h2 id="heading-the-thing-is-most-devs-today-missed-the-ef-core-pain-years">The thing is, most devs today missed the EF Core pain years</h2>
<p>Here's something that drives me crazy: many developers today only know EF Core 6+ era, which is genuinely fast and well-optimized. But those of us who lived through EF Core 1.x through 5.x remember the pain:</p>
<ul>
<li><p>Context creation was expensive</p>
</li>
<li><p>Memory tracking issues were common</p>
</li>
<li><p>Connection pooling was suboptimal</p>
</li>
<li><p>Change tracking could kill performance</p>
</li>
</ul>
<p>When I raise concerns about EF Core patterns, I'm not being paranoid—I'm remembering production systems that fell over because we trusted the "best practice" documentation without understanding the trade-offs.</p>
<h2 id="heading-microsoft-docs-are-great-but-theyre-not-optimizing-for-what-you-think">Microsoft docs are great, but they're not optimizing for what you think</h2>
<p>Don't get me wrong—Microsoft's documentation is excellent. When they say using <code>IServiceScopeFactory</code> is the best practice for hosted services, they're absolutely right. But "best practice" optimizes for:</p>
<ul>
<li><p>✅ Safety and correctness</p>
</li>
<li><p>✅ Memory leak prevention</p>
</li>
<li><p>✅ Proper service lifetimes</p>
</li>
<li><p>✅ Threading safety</p>
</li>
</ul>
<p>It doesn't necessarily optimize for:</p>
<ul>
<li><p>❌ Maximum throughput</p>
</li>
<li><p>❌ Minimum resource usage</p>
</li>
<li><p>❌ Cost efficiency</p>
</li>
<li><p>❌ Performance at scale</p>
</li>
</ul>
<p>Understanding this distinction is crucial.</p>
<h2 id="heading-if-you-do-need-to-optimize-later-and-you-probably-will">If you do need to optimize later (and you probably will)</h2>
<p>If and when performance becomes important, here are some alternatives:</p>
<h3 id="heading-batch-processing">Batch Processing</h3>
<pre><code class="lang-csharp"><span class="hljs-comment">// Process messages in batches to amortize scope creation cost</span>
<span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> batchSize = <span class="hljs-number">50</span>;
<span class="hljs-keyword">using</span> <span class="hljs-keyword">var</span> scope = _scopeFactory.CreateScope();
<span class="hljs-keyword">var</span> dbContext = scope.ServiceProvider.GetRequiredService&lt;MyDbContext&gt;();

<span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> batch <span class="hljs-keyword">in</span> messages.Chunk(batchSize))
{
    <span class="hljs-keyword">await</span> ProcessBatch(batch, dbContext);
    dbContext.ChangeTracker.Clear(); <span class="hljs-comment">// Prevent memory buildup</span>
}
</code></pre>
<h3 id="heading-idbcontextfactory">IDbContextFactory</h3>
<pre><code class="lang-csharp"><span class="hljs-comment">// More control over context lifecycle</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">OptimizedMessageHandler</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IDbContextFactory&lt;MyDbContext&gt; _contextFactory;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">ProcessMessages</span>(<span class="hljs-params">IEnumerable&lt;Message&gt; messages</span>)</span>
    {
        <span class="hljs-keyword">using</span> <span class="hljs-keyword">var</span> context = _contextFactory.CreateDbContext();
        <span class="hljs-comment">// Process multiple messages with same context</span>
    }
}
</code></pre>
<h2 id="heading-this-isnt-really-about-the-code-though">This isn't really about the code though</h2>
<p>The issue isn't the technical implementation—it's the dismissive attitude toward performance considerations. When someone raises a scalability concern, the response should be: <em>"Interesting point. What are you thinking about specifically?"</em> not <em>"Stop nitpicking."</em></p>
<p>This kind of dismissal shuts down important architectural discussions. It also creates a culture where people stop raising valid concerns because they don't want to be labeled as difficult.</p>
<h2 id="heading-heres-how-i-see-it">Here's how I see it</h2>
<p>Look, I get it. Sometimes we do overthink things. Sometimes a simple, documented approach is exactly what you need. But there's a huge difference between:</p>
<ul>
<li><p><strong>Premature optimization</strong>: "Let me write custom assembly code for this string concatenation"</p>
</li>
<li><p><strong>Architectural awareness</strong>: "This pattern might not scale well if our assumptions change"</p>
</li>
</ul>
<p>Raising questions about scalability, especially with historical context, isn't nitpicking—it's good engineering.</p>
<h2 id="heading-so-whats-the-point-of-all-this">So what's the point of all this?</h2>
<p>Next time someone dismisses your performance concerns as "nitpicking," ask yourself:</p>
<ol>
<li><p>Am I worried about style, or system behavior?</p>
</li>
<li><p>Could this decision impact future scalability?</p>
</li>
<li><p>Am I drawing from real experience with similar patterns?</p>
</li>
</ol>
<p>If you're thinking about system behavior, scalability, and drawing from experience, you're not nitpicking. You're doing your job as a senior developer.</p>
<hr />
<p><em>Have you been in similar situations? How do you handle it when architectural concerns get dismissed as nitpicking? I'd love to hear your stories in the comments.</em></p>
]]></content:encoded></item><item><title><![CDATA[How to Choose the Most Appropriate Architecture for Your Software Products]]></title><description><![CDATA[When developing a new project, we often gravitate toward certain technology stacks for various reasons. Throughout my career, I've noticed that one of the most common motivations is simply following trends - similar to what happens in fashion or ente...]]></description><link>https://override.dev/how-to-choose-the-most-appropriate-architecture-for-your-software-products</link><guid isPermaLink="true">https://override.dev/how-to-choose-the-most-appropriate-architecture-for-your-software-products</guid><category><![CDATA[Startups]]></category><category><![CDATA[architecture]]></category><category><![CDATA[System Design]]></category><dc:creator><![CDATA[Rick]]></dc:creator><pubDate>Wed, 30 Apr 2025 02:28:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1745980841259/eaf73238-5c86-4323-b3aa-2d90197b7095.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When developing a new project, we often gravitate toward certain technology stacks for various reasons. Throughout my career, I've noticed that one of the most common motivations is simply following trends - similar to what happens in fashion or entertainment. Developers and companies always want to use what's new and trendy.</p>
<p>In software engineering, this can be dangerous - actually, it <em>is</em> dangerous. Just check X (formerly Twitter) and you'll see hundreds or thousands of developers using the newest framework or tool that comes out every weekend in Web Development. While this drives innovation in certain ecosystems, I want to focus on explaining why this approach is risky when developing software professionally.</p>
<h2 id="heading-the-stability-factor">The Stability Factor</h2>
<p>The primary reason large companies move slowly in adopting new technologies (besides their bureaucracy) is stability. A company won't invest hundreds of thousands or millions of dollars in a library or framework that might disappear months after its launch. So they often opt for older but stable technologies, which creates another problem - their technical debt becomes enormous as time passes.</p>
<h2 id="heading-how-to-solve-this-dilemma">How to Solve This Dilemma?</h2>
<p>Imagine you're a project leader at a company, and they ask you to determine which stack, architecture, and work methodologies to implement for a specific project.</p>
<p>This is where a technical leader must be pragmatic and set aside personal preferences, focusing solely on finding tools that solve the problem in a scalable and maintainable way, regardless of trends.</p>
<p>Software engineers tend to follow trends believing that using the latest X technology makes them smarter than other engineers - many suffer from ego problems. A true software engineer, in my humble opinion, is someone who can set aside this ego and adopt what I call an "Eternal Student" mindset - always seeking continuous improvement, emptying their cup, and staying ready to learn new things and adapt to the changes this profession brings.</p>
<h2 id="heading-focus-on-business-value-not-technical-preferences">Focus on Business Value, Not Technical Preferences</h2>
<p>With this mindset, the questions you should ask when choosing tools and methodologies must be oriented toward solving the problem with minimal effort without sacrificing the maintainability and scalability of the project's architecture.</p>
<p>According to recent trends reported by InfoQ, complex analytical platforms and ML models are no longer considered secondary components as they shift towards becoming core parts of transactional systems. This means your architecture decisions need to account for data as a major force.</p>
<p>Studies have shown that even with all the investments in automation, shifting left, full-stack development, and AI tools, developer productivity continues to plummet year after year. Until teams start proactively managing not just simple source code tech debt but deeper architectural technical debt, we'll never improve developer productivity.</p>
<h2 id="heading-the-microservices-trap">The Microservices Trap</h2>
<p>This is precisely why I'm against Microservices in the vast majority of cases. This architecture adds a layer of complexity that in many (or all) instances creates too much work that's very difficult to justify.</p>
<p>Hence the internet memes: "My 10-service architecture with Kubernetes is ready for my two active users."</p>
<h2 id="heading-a-real-world-example">A Real-World Example</h2>
<p>As described in the case study of a WhatsApp CRM development, one founder realized that talking to real businesses using WhatsApp revealed they were struggling with clunky interfaces, wasting time managing conversations, and missing opportunities because their tools weren't adequate.</p>
<p>The irony is that while developers obsess over architecture decisions and code quality, most users couldn't care less about beautiful microservices or elegant code patterns. They just want something that makes their work easier and helps them close more deals.</p>
<h2 id="heading-making-better-choices">Making Better Choices</h2>
<p>When considering architecture options, remember that your choice depends on project needs. For simpler applications with well-defined requirements and smaller teams, a monolithic approach might be appropriate. For applications requiring scalability, designs like Actor Model or Modular Monolith could be a good option.</p>
<p>The software as a service (SaaS) market was valued at $261.15 billion in 2022 and is predicted to grow at 13.7% annually through 2030. This growth demonstrates the importance of choosing architectures that allow for subscription-based delivery models when appropriate.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>A wise architect is one who can set aside their personal biases and evaluate each project's unique requirements. Sometimes a monolith is the right answer. Sometimes microservices are justified(in rare cases). The key is to constantly educate yourself on emerging patterns while maintaining a pragmatic approach to selection.</p>
<p>The best engineers are those who remain "eternal students," always ready to learn and adapt, making decisions based on what will truly solve business problems rather than what will impress others.</p>
]]></content:encoded></item><item><title><![CDATA[REPR Pattern in .NET for Clean API Architecture]]></title><description><![CDATA[After implementing Domain-Driven Design for a loan management system, I want to share another architectural pattern that complements my DDD approach perfectly: REPR (Request-Endpoint-Response). This pattern has transformed how I structure my API endp...]]></description><link>https://override.dev/repr-pattern-in-net-for-clean-api-architecture</link><guid isPermaLink="true">https://override.dev/repr-pattern-in-net-for-clean-api-architecture</guid><category><![CDATA[fastendpoints]]></category><category><![CDATA[asp.net core]]></category><category><![CDATA[Clean Architecture]]></category><category><![CDATA[API Design]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[C#]]></category><category><![CDATA[fast endpoints]]></category><category><![CDATA[DDD]]></category><dc:creator><![CDATA[Rick]]></dc:creator><pubDate>Mon, 21 Apr 2025 01:20:09 GMT</pubDate><content:encoded><![CDATA[<p>After implementing Domain-Driven Design for a loan management system, I want to share another architectural pattern that complements my DDD approach perfectly: REPR (Request-Endpoint-Response). This pattern has transformed how I structure my API endpoints, making them more maintainable and testable.</p>
<h2 id="heading-the-challenge-with-api-endpoints">The Challenge with API Endpoints</h2>
<p>Traditional API controllers often become bloated with numerous actions, making them difficult to test and maintain. As applications grow, these controllers can accumulate thousands of lines of code, mixing concerns and creating a maintenance nightmare.</p>
<p>Many developers organize APIs by controller, resulting in classes with multiple endpoints that handle different operations on the same resource. While this seems logical at first, it quickly becomes unwieldy as the application grows.</p>
<h2 id="heading-enter-the-repr-pattern">Enter the REPR Pattern</h2>
<p>REPR stands for Request-Endpoint-Response, a pattern that focuses on creating dedicated classes for each API endpoint. Instead of controllers with multiple actions, each endpoint is its own class with a single responsibility.</p>
<p>This approach aligns perfectly with SOLID principles, particularly the Single Responsibility Principle. Let's see how I've implemented this using FastEndpoints, a lightweight library that facilitates the REPR pattern in .NET.</p>
<h2 id="heading-implementation-in-my-loan-management-system">Implementation in My Loan Management System</h2>
<p>Looking at my loan submission endpoint, you can see how clean and focused the implementation is:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">internal</span> class <span class="hljs-title">SubmitLoanEndPoint</span>(<span class="hljs-params">IStorageProvider storageProvider, ILoanPublisher loanPublisher</span>)
    :Endpoint&lt;SubmitLoanRequest, SubmitLoanResponse&gt;</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Configure</span>(<span class="hljs-params"></span>)</span>
    {
        Post(<span class="hljs-string">"/"</span>);
        Group&lt;LoanGroup&gt;(); 
        AllowAnonymous();
        Summary(s =&gt;
        {
            s.Summary = <span class="hljs-string">"Submit a loan application"</span>;
            s.Description = <span class="hljs-string">"Submit a loan application to the system"</span>;
            s.Response&lt;SubmitLoanResponse&gt;();
            s.Response(<span class="hljs-number">400</span>, <span class="hljs-string">"Invalid request"</span>);
            s.Response(<span class="hljs-number">500</span>, <span class="hljs-string">"Internal server error"</span>);
        });
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">HandleAsync</span>(<span class="hljs-params">SubmitLoanRequest req, CancellationToken ct</span>)</span>
    {
        <span class="hljs-comment">//1.- Create the loan entity</span>
        <span class="hljs-keyword">var</span> loanEntity = <span class="hljs-keyword">new</span> LoanEntity
        {
            LoanId = Guid.NewGuid().ToString(),
            LoanAmount = req.LoanAmount,
            LoanTerm = req.LoanTerm,
            LoanPurpose = req.LoanPurpose,
            BankInformation= <span class="hljs-keyword">new</span> BankInformationEntity(req.BankInformation.BankName, 
                req.BankInformation.AccountType, req.BankInformation.AccountNumber),
            PersonalInformation = <span class="hljs-keyword">new</span> PersonalInformationEntity(req.PersonalInformation.FullName, 
                req.PersonalInformation.Email, 
                DateOnly.FromDateTime(req.PersonalInformation.DateOfBirth)),
        };

        <span class="hljs-comment">//2.- Save the loan entity to the storage provider</span>
        <span class="hljs-keyword">var</span> loanCreationResult = <span class="hljs-keyword">await</span> storageProvider.CreateLoanAsync(loanEntity);

        <span class="hljs-comment">//3.- Publish the loan creation event to the message broker</span>
        <span class="hljs-keyword">var</span> loanCreationRequest = <span class="hljs-keyword">new</span> <span class="hljs-keyword">global</span>::Loan.Shared.Contracts.Commands.SubmitLoanRequest(
            loanCreationResult.LoanId,<span class="hljs-comment">// we pass the loan ID from the storage provider</span>
            req.LoanAmount,
            req.LoanTerm,
            req.LoanPurpose,
            <span class="hljs-keyword">new</span>(req.BankInformation.BankName, req.BankInformation.AccountType, 
                req.BankInformation.BankName),
            <span class="hljs-keyword">new</span>(req.PersonalInformation.FullName, req.PersonalInformation.Email, 
                req.PersonalInformation.DateOfBirth)
        );

        <span class="hljs-keyword">await</span> loanPublisher.PublishLoanSubmittedAsync(loanCreationRequest, ct);

        <span class="hljs-comment">//4.- Return the loan ID to the client</span>
        <span class="hljs-keyword">await</span> SendOkAsync(<span class="hljs-keyword">new</span> SubmitLoanResponse(loanCreationResult.LoanId), cancellation: ct);
    }
}
</code></pre>
<h2 id="heading-key-components-of-the-repr-pattern">Key Components of the REPR Pattern</h2>
<h3 id="heading-1-request-objects">1. Request Objects</h3>
<p>Request objects define the incoming data structure. They're immutable records that clearly communicate what data is needed for this specific endpoint:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">internal</span> record <span class="hljs-title">SubmitLoanRequest</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> LoanAmount,
                                 <span class="hljs-keyword">int</span> LoanTerm,
                                 <span class="hljs-keyword">int</span> LoanPurpose,
                                 BankInfo BankInformation,
                                 PersonalInfo PersonalInformation</span>)</span>;
</code></pre>
<p>The supporting data types are also simple and immutable:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">internal</span> record <span class="hljs-title">BankInfo</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> BankName, <span class="hljs-keyword">string</span> AccountType, <span class="hljs-keyword">string</span> AccountNumber</span>)</span>;

<span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">PersonalInfo</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> FullName, <span class="hljs-keyword">string</span> Email, DateTime DateOfBirth</span>)</span>;
</code></pre>
<h3 id="heading-2-endpoint-classes">2. Endpoint Classes</h3>
<p>Each endpoint is a dedicated class that inherits from <code>Endpoint&lt;TRequest, TResponse&gt;</code>. This class has one job: handle a specific API request. The configuration is declarative and self-documented:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Configure</span>(<span class="hljs-params"></span>)</span>
{
    Post(<span class="hljs-string">"/"</span>);
    Group&lt;LoanGroup&gt;(); 
    AllowAnonymous();
    Summary(s =&gt;
    {
        s.Summary = <span class="hljs-string">"Submit a loan application"</span>;
        s.Description = <span class="hljs-string">"Submit a loan application to the system"</span>;
        s.Response&lt;SubmitLoanResponse&gt;();
        s.Response(<span class="hljs-number">400</span>, <span class="hljs-string">"Invalid request"</span>);
        s.Response(<span class="hljs-number">500</span>, <span class="hljs-string">"Internal server error"</span>);
    });
}
</code></pre>
<p>Notice how we're using a <code>LoanGroup</code> to organize our endpoints logically:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">class</span> <span class="hljs-title">LoanGroup</span> : <span class="hljs-title">Group</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">LoanGroup</span>(<span class="hljs-params"></span>)</span>
    {
        Configure(<span class="hljs-keyword">nameof</span>(Loan).ToLower(), _ =&gt;
        {
            <span class="hljs-comment">// Group configuration</span>
        });
    }
}
</code></pre>
<h3 id="heading-3-validation">3. Validation</h3>
<p>Validation is separated into its own class, keeping the endpoint class clean:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">class</span> <span class="hljs-title">SubmitLoanRequestValidator</span> : <span class="hljs-title">AbstractValidator</span>&lt;<span class="hljs-title">SubmitLoanRequest</span>&gt;
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">SubmitLoanRequestValidator</span>(<span class="hljs-params"></span>)</span>
    {
        RuleFor(x =&gt; x.LoanAmount)
            .NotEmpty()
            .WithMessage(<span class="hljs-string">"Loan amount is required"</span>)
            .GreaterThan(<span class="hljs-number">0</span>)
            .WithMessage(<span class="hljs-string">"Loan amount must be greater than 0"</span>);
        RuleFor(x =&gt; x.LoanTerm)
            .NotEmpty()
            .WithMessage(<span class="hljs-string">"Loan term is required"</span>)
            .GreaterThan(<span class="hljs-number">0</span>)
            .WithMessage(<span class="hljs-string">"Loan term must be greater than 0"</span>);
        RuleFor(x =&gt; x.LoanPurpose)
            .NotEmpty()
            .WithMessage(<span class="hljs-string">"Loan purpose is required"</span>);
        RuleFor(x =&gt; x.BankInformation)
            .NotNull()
            .WithMessage(<span class="hljs-string">"Bank information is required"</span>);
        RuleFor(x =&gt; x.PersonalInformation)
            .NotNull()
            .WithMessage(<span class="hljs-string">"Personal information is required"</span>);
    }
}
</code></pre>
<h3 id="heading-4-response-objects">4. Response Objects</h3>
<p>Response objects are also simple records that define the expected output:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">internal</span> record <span class="hljs-title">SubmitLoanResponse</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> LoanId</span>)</span>;
</code></pre>
<h2 id="heading-integration-with-event-driven-architecture">Integration with Event-Driven Architecture</h2>
<p>My implementation doesn't stop at the API level. After processing the request, we publish an event to notify other systems about the loan submission:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">await</span> loanPublisher.PublishLoanSubmittedAsync(loanCreationRequest, ct);
</code></pre>
<p>The message publishing interface is simple:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">ILoanPublisher</span>
{
    <span class="hljs-function">Task <span class="hljs-title">PublishLoanSubmittedAsync</span>(<span class="hljs-params">SubmitLoanRequest command, CancellationToken cancellationToken</span>)</span>;
}
</code></pre>
<p>And the event itself extends a base message class:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">LoanSubmitted</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> LoanId, <span class="hljs-keyword">int</span> LoanStatus</span>) : BaseMessage</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">record</span> <span class="hljs-title">BaseMessage</span>
{
    <span class="hljs-keyword">public</span> Guid MessageId { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = Guid.NewGuid();
    <span class="hljs-keyword">public</span> DateTime Timestamp { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = DateTime.UtcNow;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Version { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-string">"1.0"</span>;
}
</code></pre>
<h2 id="heading-advantages-of-the-repr-pattern">Advantages of the REPR Pattern</h2>
<ol>
<li><p><strong>Focused Responsibility</strong>: Each endpoint handles exactly one API operation, making the code easier to understand and maintain.</p>
</li>
<li><p><strong>Improved Testability</strong>: Testing is simplified because each endpoint is focused on a single task with clear inputs and outputs.</p>
</li>
<li><p><strong>Easier to Extend</strong>: Adding new endpoints doesn't require modifying existing code, adhering to the Open/Closed Principle.</p>
</li>
<li><p><strong>Self-Documented API</strong>: The configuration method clearly defines the endpoint's behavior, making the API easier to understand.</p>
</li>
<li><p><strong>Simplified Dependency Injection</strong>: Dependencies are injected only where needed, reducing unnecessary coupling.</p>
</li>
<li><p><strong>Clean Architecture Support</strong>: This pattern fits perfectly with clean architecture and DDD principles.</p>
</li>
</ol>
<h2 id="heading-potential-improvements">Potential Improvements</h2>
<p>Looking at my <code>SubmitLoanEndPoint</code> implementation, there's a TODO comment about implementing the outbox pattern:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">//<span class="hljs-doctag">TODO:</span> apply outbox pattern</span>
</code></pre>
<p>This would further improve the system's resilience by ensuring that the message publishing is transactionally consistent with the database operations.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The REPR pattern, combined with Domain-Driven Design, has significantly improved the maintainability and scalability of my loan management system. By creating focused, single-responsibility endpoints, the code becomes more readable, testable, and extensible.</p>
<p>If you're struggling with bloated controllers in your API, consider giving the REPR pattern a try. Libraries like FastEndpoints make it easy to implement in .NET applications, and the benefits become increasingly apparent as your application grows.</p>
<p>Remember: good architecture isn't about following patterns blindly but understanding your domain's complexity and choosing patterns that help manage that complexity effectively. In my experience, the combination of DDD for the domain layer and REPR for the API layer creates a robust foundation for complex business applications.</p>
]]></content:encoded></item><item><title><![CDATA[Implementing Event-Domain-Driven Design in a .NET Loan Management System]]></title><description><![CDATA[Domain-Driven Design (DDD) isn't just a set of theoretical patterns—it's a practical approach to developing complex software systems. As part of my work on a loan management system, I've put these principles into practice using the .NET ecosystem. Le...]]></description><link>https://override.dev/implementing-event-domain-driven-design-in-a-net-loan-management-system</link><guid isPermaLink="true">https://override.dev/implementing-event-domain-driven-design-in-a-net-loan-management-system</guid><category><![CDATA[#Domain-Driven-Design]]></category><category><![CDATA[event-driven-architecture]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[C#]]></category><dc:creator><![CDATA[Rick]]></dc:creator><pubDate>Thu, 17 Apr 2025 04:01:10 GMT</pubDate><content:encoded><![CDATA[<p>Domain-Driven Design (DDD) isn't just a set of theoretical patterns—it's a practical approach to developing complex software systems. As part of my work on a loan management system, I've put these principles into practice using the .NET ecosystem. Let me walk you through how I've applied DDD concepts to create a maintainable, scalable solution that accurately reflects the business domain.</p>
<h2 id="heading-ddd-in-action-the-loan-domain-model">DDD in Action: The Loan Domain Model</h2>
<p>Looking at the code I've implemented, you can see how the loan domain takes shape through clearly defined aggregates, entities, value objects, and domain events.</p>
<h3 id="heading-aggregates-and-entities">Aggregates and Entities</h3>
<p>The <code>Loan</code> class serves as our aggregate root, containing all the essential properties and behavior related to a loan:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Loan</span>
{
    <span class="hljs-keyword">public</span> LoanId Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> LoanAmount { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> LoanTerm { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> LoanPurpose { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> BankInformation BankInformation { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> LoanStatus LoanStatus { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; } = LoanStatus.Pending;
    <span class="hljs-keyword">public</span> PersonalInformation PersonalInformation { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> List&lt;IDomainEvent&gt; _domainEvents = [];
    <span class="hljs-keyword">public</span> IReadOnlyCollection&lt;IDomainEvent&gt; DomainEvents =&gt; _domainEvents.AsReadOnly();
}
</code></pre>
<p>Notice the use of private setters and a private constructor. This encapsulation is crucial—it prevents external code from modifying the loan's state directly, preserving domain integrity.</p>
<p>The only way to create a loan is through the <code>Create</code> factory method:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Result&lt;Loan&gt; <span class="hljs-title">Create</span>(<span class="hljs-params">LoanId Id, <span class="hljs-keyword">int</span> LoanAmount, <span class="hljs-keyword">int</span> LoanTerm, 
                                <span class="hljs-keyword">int</span> LoanPurpose, LoanStatus loanStatus, 
                                PersonalInformation personalInformation, 
                                BankInformation bankInformation</span>)</span>
{
    <span class="hljs-keyword">var</span> loan = <span class="hljs-keyword">new</span> Loan(Id, LoanAmount, LoanTerm, LoanPurpose, loanStatus, 
                       personalInformation, bankInformation);

    <span class="hljs-keyword">return</span> loan.Validate();
}
</code></pre>
<p>This pattern ensures that every loan instance is valid upon creation, enforcing invariants at the domain level.</p>
<h3 id="heading-value-objects">Value Objects</h3>
<p>Value objects represent descriptive aspects of the domain with no conceptual identity. In my implementation, <code>LoanId</code> is a perfect example of a value object:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">internal</span> record <span class="hljs-title">LoanId</span>(<span class="hljs-params">Guid Value</span>)</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">implicit</span> <span class="hljs-keyword">operator</span> <span class="hljs-title">Guid</span>(<span class="hljs-params">LoanId loanId</span>)</span> =&gt; loanId.Value;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">implicit</span> <span class="hljs-keyword">operator</span> <span class="hljs-title">LoanId</span>(<span class="hljs-params">Guid <span class="hljs-keyword">value</span></span>)</span> =&gt; <span class="hljs-keyword">new</span>(<span class="hljs-keyword">value</span>);
}
</code></pre>
<p>I've leveraged C# records for value objects since they provide immutability and value-based equality out of the box. The implicit conversion operators add a nice touch, making the code more fluent when working with these types.</p>
<h3 id="heading-rich-domain-model-with-behavior">Rich Domain Model with Behavior</h3>
<p>A key aspect of DDD is that domain objects encapsulate not just data but behavior. My <code>Loan</code> class includes methods that represent business operations:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> Result <span class="hljs-title">Approve</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">if</span> (LoanStatus != LoanStatus.Pending)
    {
        <span class="hljs-keyword">return</span> Result.Invalid(<span class="hljs-keyword">new</span> ValidationError(<span class="hljs-keyword">nameof</span>(LoanStatus), <span class="hljs-keyword">string</span>.Empty, 
                            DomainErrors.Loan.LOAN_STATUS_INVALID, ValidationSeverity.Error));
    }

    LoanStatus = LoanStatus.Approved;
    _domainEvents.Add(<span class="hljs-keyword">new</span> LoanApprovedEvent(Id));

    <span class="hljs-keyword">return</span> Result.Success();
}

<span class="hljs-function"><span class="hljs-keyword">public</span> Result <span class="hljs-title">Cancel</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">if</span> (LoanStatus != LoanStatus.Pending)
    {
        <span class="hljs-keyword">return</span> Result.Invalid(<span class="hljs-keyword">new</span> ValidationError(<span class="hljs-keyword">nameof</span>(LoanStatus), <span class="hljs-keyword">string</span>.Empty, 
                            DomainErrors.Loan.LOAN_STATUS_INVALID, ValidationSeverity.Error));
    }

    LoanStatus = LoanStatus.Canceled;
    _domainEvents.Add(<span class="hljs-keyword">new</span> LoanCanceledEvent(Id));

    <span class="hljs-keyword">return</span> Result.Success();
}
</code></pre>
<p>These methods enforce business rules (a loan can only be approved if it's pending) and raise appropriate domain events, keeping the domain model self-contained and expressive.</p>
<h3 id="heading-domain-events">Domain Events</h3>
<p>Domain events play a crucial role in DDD, allowing different parts of the system to react to changes without tight coupling. Here's how I've implemented domain events:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">abstract</span> class <span class="hljs-title">LoanDomainEvent</span>(<span class="hljs-params">LoanId loanId</span>) : IDomainEvent</span>
{
    <span class="hljs-keyword">public</span> Guid Id { <span class="hljs-keyword">get</span>; } = Guid.NewGuid();
    <span class="hljs-keyword">public</span> DateTime OccurredOn { <span class="hljs-keyword">get</span>; } = DateTime.UtcNow;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> EventType =&gt; GetType().Name;
    <span class="hljs-keyword">public</span> LoanId LoanId { <span class="hljs-keyword">get</span>; } = loanId;
}

<span class="hljs-function"><span class="hljs-keyword">internal</span> class <span class="hljs-title">LoanApprovedEvent</span>(<span class="hljs-params">LoanId loanId</span>) : <span class="hljs-title">LoanDomainEvent</span>(<span class="hljs-params">loanId</span>)</span>
{
}

<span class="hljs-function"><span class="hljs-keyword">internal</span> class <span class="hljs-title">LoanCanceledEvent</span>(<span class="hljs-params">LoanId loanId</span>) : <span class="hljs-title">LoanDomainEvent</span>(<span class="hljs-params">loanId</span>)</span>
{
}
</code></pre>
<p>When significant state changes occur within the domain, the relevant events are raised, allowing other components (like notification services) to act accordingly without introducing direct dependencies.</p>
<h3 id="heading-validation-as-part-of-the-domain">Validation as Part of the Domain</h3>
<p>In DDD, validation belongs within the domain. Each entity and aggregate includes its own validation logic:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> Result <span class="hljs-title">Validate</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">if</span> (Id == <span class="hljs-keyword">default</span>)
    {
        <span class="hljs-keyword">return</span> Result.Invalid(<span class="hljs-keyword">new</span> ValidationError(<span class="hljs-keyword">nameof</span>(Id), <span class="hljs-keyword">string</span>.Empty, 
                            DomainErrors.Loan.LOAN_NOT_FOUND, ValidationSeverity.Error));
    }

    <span class="hljs-keyword">if</span> (LoanAmount &lt;= <span class="hljs-number">0</span>)
    {
        <span class="hljs-keyword">return</span> Result.Invalid(<span class="hljs-keyword">new</span> ValidationError(<span class="hljs-keyword">nameof</span>(LoanAmount), <span class="hljs-keyword">string</span>.Empty, 
                            DomainErrors.Loan.LOAN_AMOUNT_INVALID, ValidationSeverity.Error));
    }

    <span class="hljs-comment">// Additional validations...</span>

    <span class="hljs-keyword">var</span> personalValidationResult = PersonalInformation.Validate();
    <span class="hljs-keyword">if</span> (!personalValidationResult.IsSuccess)
    {
        <span class="hljs-keyword">return</span> personalValidationResult.Map();
    }

    <span class="hljs-keyword">var</span> bankValidationResult = BankInformation.Validate();
    <span class="hljs-keyword">if</span> (!bankValidationResult.IsSuccess)
    {
        <span class="hljs-keyword">return</span> bankValidationResult.Map();
    }

    <span class="hljs-keyword">return</span> Result.Success();
}
</code></pre>
<p>This approach ensures that business rules are encapsulated within their respective domain objects and consistently applied.</p>
<h2 id="heading-the-application-layer-command-handlers">The Application Layer: Command Handlers</h2>
<p>The application layer orchestrates the domain objects to fulfill use cases. Here's how I've implemented a command handler for submitting a loan:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">internal</span> class <span class="hljs-title">SubmitLoanCommandHandler</span>(<span class="hljs-params">ILoanRepository loanRepository</span>) : 
    CommandHandler&lt;SubmitLoanCommand, Result&lt;SubmitLoanCommandResponse&gt;&gt;</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task&lt;Result&lt;SubmitLoanCommandResponse&gt;&gt; ExecuteAsync(
        SubmitLoanCommand command, CancellationToken ct = <span class="hljs-keyword">default</span>)
    {
        <span class="hljs-comment">// 1. Create and validate domain entities</span>
        <span class="hljs-keyword">var</span> personalInformationResult = PersonalInformation.Create(
            command.PersonalInformation.FullName,
            command.PersonalInformation.Email,
            command.PersonalInformation.DateOfBirth
        );

        <span class="hljs-comment">// ... additional validation logic</span>

        <span class="hljs-comment">// 2. Create the aggregate root</span>
        <span class="hljs-keyword">var</span> loanResult = Loan.Create(
            Id: <span class="hljs-keyword">new</span> LoanId(Guid.NewGuid()),
            LoanAmount: command.LoanAmount,
            LoanTerm: command.LoanTerm,
            LoanPurpose: command.LoanPurpose,
            loanStatus: LoanStatus.Pending,
            personalInformation: personalInformation,
            bankInformation: bankInformation);

        <span class="hljs-comment">// ... persistence logic</span>

        <span class="hljs-comment">// 3. Publish domain events</span>
        <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> @event <span class="hljs-keyword">in</span> loan.DomainEvents)
        {
            <span class="hljs-keyword">var</span> eventNotification = <span class="hljs-keyword">new</span> LoanNotification(JsonSerializer.Serialize(@event));
            <span class="hljs-keyword">await</span> eventNotification.PublishAsync(cancellation: ct);
        }

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> SubmitLoanCommandResponse(loan.Id.Value.ToString());
    }
}
</code></pre>
<p>The command handler follows a clear workflow:</p>
<ol>
<li><p>Create and validate domain entities</p>
</li>
<li><p>Create the aggregate root</p>
</li>
<li><p>Persist changes</p>
</li>
<li><p>Publish domain events</p>
</li>
</ol>
<p>This separation ensures that application logic coordinates domain operations without mixing concerns.</p>
<h2 id="heading-domain-events-and-notifications">Domain Events and Notifications</h2>
<p>An important aspect of my implementation is how domain events connect to the notification system:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">class</span> <span class="hljs-title">LoanNotificationHandler</span> : <span class="hljs-title">IEventHandler</span>&lt;<span class="hljs-title">LoanNotification</span>&gt;
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> Task <span class="hljs-title">HandleAsync</span>(<span class="hljs-params">LoanNotification notification, CancellationToken cancellationToken</span>)</span>
    {
        <span class="hljs-comment">// Handle the loan notification here</span>
        <span class="hljs-comment">// For example, send an email or push notification</span>
        <span class="hljs-comment">// Could apply outbox pattern here to send the notification to a queue or service bus</span>

        <span class="hljs-keyword">return</span> Task.CompletedTask;
    }
}
</code></pre>
<p>This decoupling allows the domain to focus on business rules while infrastructure components handle cross-cutting concerns like notifications.</p>
<h2 id="heading-data-transfer-objects-dtos-and-contracts">Data Transfer Objects (DTOs) and Contracts</h2>
<p>To separate the domain model from external contracts, I've created DTOs specifically for API communication:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">PersonalInformationDto</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> FullName, <span class="hljs-keyword">string</span> Email, DateOnly DateOfBirth</span>)</span>;

<span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">BankInformationDto</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> BankName, <span class="hljs-keyword">string</span> AccountType, <span class="hljs-keyword">string</span> AccountNumber</span>)</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">SubmitLoanCommand</span> : <span class="hljs-title">ICommand</span>&lt;<span class="hljs-title">Result</span>&lt;<span class="hljs-title">SubmitLoanCommandResponse</span>&gt;&gt;
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> LoanAmount { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> LoanTerm { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> LoanPurpose { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> required BankInformationDto BankInformation { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> required PersonalInformationDto PersonalInformation { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}
</code></pre>
<p>These DTOs define the contract between the API and clients without exposing the internal domain model.</p>
<h2 id="heading-key-benefits-of-this-ddd-implementation">Key Benefits of this DDD Implementation</h2>
<p>Implementing DDD in this loan management system has provided several advantages:</p>
<ol>
<li><p><strong>Clear boundaries</strong>: Each domain concept has a well-defined responsibility</p>
</li>
<li><p><strong>Encapsulated business rules</strong>: Validation occurs within the domain, not scattered across the application</p>
</li>
<li><p><strong>Rich domain model</strong>: Entities contain both data and behavior, reflecting real-world concepts</p>
</li>
<li><p><strong>Explicit state transitions</strong>: Status changes are controlled by dedicated methods that enforce business rules</p>
</li>
<li><p><strong>Decoupled components</strong>: Domain events allow different parts of the system to communicate without tight coupling</p>
</li>
</ol>
<h2 id="heading-practical-considerations">Practical Considerations</h2>
<p>While implementing DDD, I encountered several practical considerations:</p>
<p><strong>Result Pattern for Error Handling</strong></p>
<p>Instead of throwing exceptions, I've used the Result pattern (from Ardalis.Result) for more explicit error handling:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> Result <span class="hljs-title">Validate</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">string</span>.IsNullOrEmpty(FullName))
    {
        <span class="hljs-keyword">return</span> Result.Invalid(<span class="hljs-keyword">new</span> ValidationError(<span class="hljs-keyword">nameof</span>(FullName), <span class="hljs-keyword">string</span>.Empty, 
                            DomainErrors.PersonalInformation.FULL_NAME_REQUIRED, 
                            ValidationSeverity.Error));
    }

    <span class="hljs-comment">// Additional validations...</span>

    <span class="hljs-keyword">return</span> Result.Success();
}
</code></pre>
<p>This approach makes error paths explicit and provides a consistent way to handle validation failures.</p>
<p><strong>Domain Events for Integration</strong></p>
<p>Domain events provide a clean way to integrate with external systems without polluting the domain model:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// In the domain model</span>
LoanStatus = LoanStatus.Approved;
_domainEvents.Add(<span class="hljs-keyword">new</span> LoanApprovedEvent(Id));

<span class="hljs-comment">// In the application layer</span>
<span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> @event <span class="hljs-keyword">in</span> loan.DomainEvents)
{
    <span class="hljs-keyword">var</span> eventNotification = <span class="hljs-keyword">new</span> LoanNotification(JsonSerializer.Serialize(@event));
    <span class="hljs-keyword">await</span> eventNotification.PublishAsync(cancellation: ct);
}
</code></pre>
<p>This approach keeps the domain model focused on business logic while allowing for integration with external systems.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Domain-Driven Design isn't just an academic exercise—it's a practical approach to building complex systems that reflect real business domains. By implementing DDD in this loan management system, I've created a codebase that is maintainable, testable, and aligned with business terminology.</p>
<p>The result is a system where:</p>
<ul>
<li><p>Business rules are clearly expressed in code</p>
</li>
<li><p>Changes to the domain are localized and don't ripple throughout the system</p>
</li>
<li><p>The code structure reflects the language of the domain experts</p>
</li>
<li><p>Integration with external systems is clean and decoupled</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Implementing Domain-Driven Design in .NET with Loan Management]]></title><description><![CDATA[Recently, I encountered an interesting problem: how to design a loan management system that is maintainable, scalable, and accurately reflects business rules. After years of working with different architectures, I decided on Domain-Driven Design (DDD...]]></description><link>https://override.dev/implementing-domain-driven-design-in-net-with-loan-management</link><guid isPermaLink="true">https://override.dev/implementing-domain-driven-design-in-net-with-loan-management</guid><category><![CDATA[dotnet]]></category><category><![CDATA[DDD]]></category><category><![CDATA[C#]]></category><category><![CDATA[asp.net core]]></category><dc:creator><![CDATA[Rick]]></dc:creator><pubDate>Wed, 16 Apr 2025 04:16:16 GMT</pubDate><content:encoded><![CDATA[<p>Recently, I encountered an interesting problem: how to design a loan management system that is maintainable, scalable, and accurately reflects business rules. After years of working with different architectures, I decided on Domain-Driven Design (DDD), an approach that has proven especially effective for complex domains.</p>
<p>WPF and React, and similar frameworks have been part of my journey as a developer, but now I'm immersed in the world of .NET and clean architecture principles. Many solutions I find on the web are often limited to simplistic examples that hardly reflect the challenges of real enterprise applications.</p>
<p>After more than a decade developing applications with different technologies, I'm surprised to see how many production implementations lack adequate separation of concerns, consisting of just a handful of folders pretending to simulate an architecture.</p>
<p>I've seen projects with hundreds of entities and operations all mixed together, making maintenance a nightmare. That's why I decided to implement a solution using DDD that truly separates responsibilities and maintains domain integrity.</p>
<h2 id="heading-the-problem-to-solve">The Problem to Solve</h2>
<p>How can we design a loan management system that is scalable, maintainable, and accurately reflects the business rules of the financial domain?</p>
<p>A loan seems simple on the surface, but quickly becomes complex when we consider all the necessary validations, state management, and the need to maintain data integrity throughout the entire lifecycle of the loan.</p>
<h2 id="heading-the-ddd-approach">The DDD Approach</h2>
<p>Domain-Driven Design shines precisely in these complex scenarios. It allows us to model the domain in a way that reflects the ubiquitous language of the business and encapsulate logic in appropriate places. Let's see how I've applied several DDD patterns in my implementation.</p>
<h3 id="heading-aggregates-and-entities">Aggregates and Entities</h3>
<p>In DDD, an aggregate is a set of related objects that we treat as a unit. In our case, the loan (<code>Loan</code>) is clearly a root aggregate that contains entities such as <code>PersonalInformation</code> and <code>BankInformation</code>.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Loan</span>
{
    <span class="hljs-keyword">public</span> LoanId Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> LoanAmount { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> LoanTerm { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> LoanPurpose { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> BankInformation BankInformation { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> LoanStatus LoanStatus { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; } = LoanStatus.Pending;
    <span class="hljs-keyword">public</span> PersonalInformation PersonalInformation { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> List&lt;IDomainEvent&gt; _domainEvents = [];
    <span class="hljs-keyword">public</span> IReadOnlyCollection&lt;IDomainEvent&gt; DomainEvents =&gt; _domainEvents.AsReadOnly();

    <span class="hljs-comment">// Private constructor to force the use of the Create method</span>
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">Loan</span>(<span class="hljs-params">LoanId Id, <span class="hljs-keyword">int</span> LoanAmount, <span class="hljs-keyword">int</span> LoanTerm, <span class="hljs-keyword">int</span> LoanPurpose, 
                LoanStatus loanStatus, PersonalInformation personalInformation, 
                BankInformation bankInformation</span>)</span>
    {
        <span class="hljs-keyword">this</span>.Id = Id;
        <span class="hljs-keyword">this</span>.LoanAmount = LoanAmount;
        <span class="hljs-keyword">this</span>.LoanTerm = LoanTerm;
        <span class="hljs-keyword">this</span>.LoanPurpose = LoanPurpose;
        PersonalInformation = personalInformation;
        BankInformation = bankInformation;
        LoanStatus = loanStatus;
    }
}
</code></pre>
<p>Notice how I've designed the <code>Loan</code> class with private setters and a private constructor. This is a fundamental principle: encapsulation. We can only create a loan through the <code>Create</code> method (Factory pattern):</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Result&lt;Loan&gt; <span class="hljs-title">Create</span>(<span class="hljs-params">LoanId Id, <span class="hljs-keyword">int</span> LoanAmount, <span class="hljs-keyword">int</span> LoanTerm, 
                                <span class="hljs-keyword">int</span> LoanPurpose, LoanStatus loanStatus, 
                                PersonalInformation personalInformation, 
                                BankInformation bankInformation</span>)</span>
{
    <span class="hljs-keyword">var</span> loan = <span class="hljs-keyword">new</span> Loan(Id, LoanAmount, LoanTerm, LoanPurpose, loanStatus, 
                      personalInformation, bankInformation);

    <span class="hljs-keyword">return</span> loan.Validate();
}
</code></pre>
<h3 id="heading-value-objects">Value Objects</h3>
<p>Value Objects are another crucial component of DDD. They are immutable and identified by the value of their properties, not by an identity. In my implementation, <code>LoanId</code> is a perfect example:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">internal</span> record <span class="hljs-title">LoanId</span>(<span class="hljs-params">Guid Value</span>)</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">implicit</span> <span class="hljs-keyword">operator</span> <span class="hljs-title">Guid</span>(<span class="hljs-params">LoanId loanId</span>)</span> =&gt; loanId.Value;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">implicit</span> <span class="hljs-keyword">operator</span> <span class="hljs-title">LoanId</span>(<span class="hljs-params">Guid <span class="hljs-keyword">value</span></span>)</span> =&gt; <span class="hljs-keyword">new</span>(<span class="hljs-keyword">value</span>);
}
</code></pre>
<p>I'm using modern C# here with records, which are ideal for Value Objects due to their immutable nature.</p>
<h3 id="heading-domain-rules-and-validations">Domain Rules and Validations</h3>
<p>One of the most important parts of DDD is ensuring that business rules are in the right place: within the domain. I've incorporated validations directly into the entities:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> Result <span class="hljs-title">Validate</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">if</span> (Id == <span class="hljs-keyword">default</span>)
    {
        <span class="hljs-keyword">return</span> Result.Invalid(<span class="hljs-keyword">new</span> ValidationError(<span class="hljs-keyword">nameof</span>(Id), <span class="hljs-keyword">string</span>.Empty, 
                            DomainErrors.Loan.LOAN_NOT_FOUND, ValidationSeverity.Error));
    }

    <span class="hljs-keyword">if</span> (LoanAmount &lt;= <span class="hljs-number">0</span>)
    {
        <span class="hljs-keyword">return</span> Result.Invalid(<span class="hljs-keyword">new</span> ValidationError(<span class="hljs-keyword">nameof</span>(LoanAmount), <span class="hljs-keyword">string</span>.Empty, 
                            DomainErrors.Loan.LOAN_AMOUNT_INVALID, ValidationSeverity.Error));
    }

    <span class="hljs-comment">// More validations...</span>

    <span class="hljs-keyword">var</span> personalValidationResult = PersonalInformation.Validate();
    <span class="hljs-keyword">if</span> (!personalValidationResult.IsSuccess)
    {
        <span class="hljs-keyword">return</span> personalValidationResult.Map();
    }

    <span class="hljs-keyword">var</span> bankValidationResult = BankInformation.Validate();
    <span class="hljs-keyword">if</span> (!bankValidationResult.IsSuccess)
    {
        <span class="hljs-keyword">return</span> bankValidationResult.Map();
    }

    <span class="hljs-keyword">return</span> Result.Success();
}
</code></pre>
<h3 id="heading-domain-events">Domain Events</h3>
<p>Domain events are fundamental to correctly implementing DDD, especially in complex systems. They allow decoupling certain actions and facilitate implementing patterns like CQRS.</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">abstract</span> class <span class="hljs-title">LoanDomainEvent</span>(<span class="hljs-params">LoanId loanId</span>) : IDomainEvent</span>
{
    <span class="hljs-keyword">public</span> Guid Id { <span class="hljs-keyword">get</span>; } = Guid.NewGuid();
    <span class="hljs-keyword">public</span> DateTime OccurredOn { <span class="hljs-keyword">get</span>; } = DateTime.UtcNow;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> EventType =&gt; GetType().Name;
    <span class="hljs-keyword">public</span> LoanId LoanId { <span class="hljs-keyword">get</span>; } = loanId;
}

<span class="hljs-function"><span class="hljs-keyword">internal</span> class <span class="hljs-title">LoanApprovedEvent</span>(<span class="hljs-params">LoanId loanId</span>) : <span class="hljs-title">LoanDomainEvent</span>(<span class="hljs-params">loanId</span>)</span>
{
}

<span class="hljs-function"><span class="hljs-keyword">internal</span> class <span class="hljs-title">LoanCanceledEvent</span>(<span class="hljs-params">LoanId loanId</span>) : <span class="hljs-title">LoanDomainEvent</span>(<span class="hljs-params">loanId</span>)</span>
{
}
</code></pre>
<p>And in the loan entity, we use these events when important changes occur:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> Result <span class="hljs-title">Approve</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">if</span> (LoanStatus != LoanStatus.Pending)
    {
        <span class="hljs-keyword">return</span> Result.Invalid(<span class="hljs-keyword">new</span> ValidationError(<span class="hljs-keyword">nameof</span>(LoanStatus), <span class="hljs-keyword">string</span>.Empty, 
                            DomainErrors.Loan.LOAN_STATUS_INVALID, ValidationSeverity.Error));
    }

    LoanStatus = LoanStatus.Approved;
    _domainEvents.Add(<span class="hljs-keyword">new</span> LoanApprovedEvent(Id));

    <span class="hljs-keyword">return</span> Result.Success();
}
</code></pre>
<h3 id="heading-nested-entities">Nested Entities</h3>
<p>The loan contains entities such as <code>PersonalInformation</code> and <code>BankInformation</code>. These entities also have their own encapsulated validations:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">class</span> <span class="hljs-title">PersonalInformation</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> FullName { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Email { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> DateOnly DateOfBirth { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">PersonalInformation</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> fullName, <span class="hljs-keyword">string</span> email, DateOnly dateOfBirth</span>)</span>
    {
        FullName = fullName;
        Email = email;
        DateOfBirth = dateOfBirth;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Result&lt;PersonalInformation&gt; <span class="hljs-title">Create</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> fullName, <span class="hljs-keyword">string</span> email, DateOnly dateOfBirth</span>)</span>
    {
        <span class="hljs-keyword">var</span> personalInformation = <span class="hljs-keyword">new</span> PersonalInformation(fullName, email, dateOfBirth);
        <span class="hljs-keyword">return</span> personalInformation.Validate();
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> Result <span class="hljs-title">Validate</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">string</span>.IsNullOrEmpty(FullName))
        {
            <span class="hljs-keyword">return</span> Result.Invalid(<span class="hljs-keyword">new</span> ValidationError(<span class="hljs-keyword">nameof</span>(FullName), <span class="hljs-keyword">string</span>.Empty, 
                                DomainErrors.PersonalInformation.FULL_NAME_REQUIRED, 
                                ValidationSeverity.Error));
        }

        <span class="hljs-comment">// More validations...</span>

        <span class="hljs-keyword">return</span> Result.Success();
    }
}
</code></pre>
<h2 id="heading-advantages-of-this-approach">Advantages of this Approach</h2>
<ol>
<li><p><strong>Robust encapsulation</strong>: Entities control their internal states</p>
</li>
<li><p><strong>Protected business rules</strong>: Validation occurs within the domain</p>
</li>
<li><p><strong>Ubiquitous language</strong>: The code reflects business terms</p>
</li>
<li><p><strong>Maintainability</strong>: Changes in the domain are localized</p>
</li>
<li><p><strong>Testability</strong>: It's easy to test entities and their behaviors</p>
</li>
</ol>
<h2 id="heading-important-points-to-highlight">Important Points to Highlight</h2>
<h3 id="heading-1-private-constructors-and-factory-methods">1. Private Constructors and Factory Methods</h3>
<p>Note that all constructors are private. This forces us to use the <code>Create</code> methods that include validation. It's impossible to create an invalid object.</p>
<h3 id="heading-2-error-handling-with-result">2. Error Handling with Result</h3>
<p>Instead of throwing exceptions, I'm using the Result pattern (from the Ardalis.Result library) which offers a more explicit and predictable way of handling errors:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> Result&lt;PersonalInformation&gt; <span class="hljs-title">Create</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> fullName, <span class="hljs-keyword">string</span> email, DateOnly dateOfBirth</span>)</span>
{
    <span class="hljs-keyword">var</span> personalInformation = <span class="hljs-keyword">new</span> PersonalInformation(fullName, email, dateOfBirth);
    <span class="hljs-keyword">return</span> personalInformation.Validate();
}
</code></pre>
<h3 id="heading-3-controlled-state-transitions">3. Controlled State Transitions</h3>
<p>Loan state changes are controlled by specific methods, not by public setters:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> Result <span class="hljs-title">Approve</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">if</span> (LoanStatus != LoanStatus.Pending)
    {
        <span class="hljs-keyword">return</span> Result.Invalid(<span class="hljs-keyword">new</span> ValidationError(<span class="hljs-keyword">nameof</span>(LoanStatus), <span class="hljs-keyword">string</span>.Empty, 
                            DomainErrors.Loan.LOAN_STATUS_INVALID, ValidationSeverity.Error));
    }

    LoanStatus = LoanStatus.Approved;
    _domainEvents.Add(<span class="hljs-keyword">new</span> LoanApprovedEvent(Id));

    <span class="hljs-keyword">return</span> Result.Success();
}
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Implementing DDD is not simply adopting a set of technical patterns; it's a mindset shift. It's about modeling software to reflect the real business domain, not the other way around.</p>
<p>In my experience, although this approach may initially seem more complex and verbose compared to typical CRUD examples found online, it pays enormous dividends when the application grows in complexity, especially for enterprise applications with numerous intertwined business rules.</p>
<p>The real benefits include:</p>
<ul>
<li><p>Clear separation of concerns</p>
</li>
<li><p>Testable business logic independent of infrastructure</p>
</li>
<li><p>Code that reflects the business language</p>
</li>
<li><p>Validations contained in the right place</p>
</li>
<li><p>Easy to extend without breaking domain integrity</p>
</li>
</ul>
<p>As developers, it's easy to fall into the trap of thinking only in terms of technology and frameworks. DDD reminds us that our main goal is to solve business problems, and that code should reflect the domain we're modeling.</p>
<p>The next time you face a complex problem, consider whether DDD might be the answer. It's not a silver bullet, but for domains rich in business rules like financial loans, few architectures offer such a clear and maintainable approach.</p>
]]></content:encoded></item><item><title><![CDATA[Mastering MVVM with TypeScript and React: Part 2 - Parent ViewModels]]></title><description><![CDATA[This article is a continuation of the article MVVM with TypeScript and React.
There are countless topics to cover when discussing MVVM, especially when using TypeScript, because there's essentially no documentation or examples for this approach in th...]]></description><link>https://override.dev/mastering-mvvm-with-typescript-and-react-part-2-parent-viewmodels</link><guid isPermaLink="true">https://override.dev/mastering-mvvm-with-typescript-and-react-part-2-parent-viewmodels</guid><category><![CDATA[React]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[MVVM]]></category><dc:creator><![CDATA[Rick]]></dc:creator><pubDate>Wed, 16 Apr 2025 02:15:13 GMT</pubDate><content:encoded><![CDATA[<p>This article is a continuation of the article <a target="_blank" href="https://rickpons.hashnode.dev/mvvm-with-typescript-and-react-for-net-developers">MVVM with TypeScript and React</a>.</p>
<p>There are countless topics to cover when discussing MVVM, especially when using TypeScript, because there's essentially no documentation or examples for this approach in the TypeScript ecosystem.</p>
<p>In this blog, I'll gradually try to change that. Although this blog is primarily for .NET developers, TypeScript is essential in my stack for frontend development, so I'll also cover many aspects of it as I work on different applications.</p>
<h2 id="heading-problem-to-solve">Problem to Solve</h2>
<p>In the previous post, we built a Stepper where each step has its own View and ViewModel. So far so good, but this approach presents several challenges:</p>
<ul>
<li><p>How can I access the data from each ViewModel to build the request that we'll send to the backend?</p>
</li>
<li><p>How can I determine if any ViewModel is in an invalid state?</p>
</li>
<li><p>How can I add more ViewModels to the Stepper without affecting the existing logic?</p>
</li>
<li><p>How can I change their order?</p>
</li>
<li><p>How can I remove them if necessary?</p>
</li>
</ul>
<p>As you can see, dear reader, there are quite a few scenarios we need to cover if we want our Stepper to be flexible, scalable, and above all, maintainable in the long term.</p>
<h2 id="heading-the-solution-parent-viewmodel">The Solution: Parent ViewModel</h2>
<p>To solve this problem, we'll use a concept called the Parent ViewModel.</p>
<h3 id="heading-what-is-a-parent-viewmodel">What is a Parent ViewModel?</h3>
<p>As the name suggests, it's a ViewModel that acts as an orchestrator. Its job is to read the list of ViewModels that the Stepper will have and dynamically display them in the UI.</p>
<p>The child ViewModels (each section of the Stepper) don't know how they're being used and have no idea that other ViewModels exist. They are completely isolated, following principles of composition. This way, we can build components in a Lego-like fashion, block by block.</p>
<p>To achieve this, we first need to have a list of the ViewModels we want to display in our Stepper.</p>
<p>I've created a registry where we'll indicate the list of ViewModels we want to deploy in our Stepp<a target="_blank" href="https://rickpons.hashnode.dev/mvvm-with-typescript-and-react-for-net-developers">er:</a></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> type { IStepViewModel } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/interfaces/IStepViewModel"</span>;
<span class="hljs-keyword">import</span> { PersonalInformationViewModel } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/viewModels/personalInformationViewModel"</span>;
<span class="hljs-keyword">import</span> { LoanDetailsViewModel } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/viewModels/loanDetailsViewModel"</span>;
<span class="hljs-keyword">import</span> { BankInformationViewModel } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/viewModels/bankInformationViewModel"</span>;
<span class="hljs-keyword">import</span> PersonalInformationView <span class="hljs-keyword">from</span> <span class="hljs-string">"~/views/personalInformationView"</span>;
<span class="hljs-keyword">import</span> LoanDetailsView <span class="hljs-keyword">from</span> <span class="hljs-string">"~/views/loanDetailsView"</span>;
<span class="hljs-keyword">import</span> BankInformationView <span class="hljs-keyword">from</span> <span class="hljs-string">"~/views/bankInformationView"</span>;

type StepRegistry = {
  [key: string]: {
    <span class="hljs-attr">viewModelClass</span>: <span class="hljs-keyword">new</span> () =&gt; IStepViewModel&lt;any&gt;;
    component: React.ComponentType&lt;{ <span class="hljs-attr">viewModel</span>: IStepViewModel&lt;any&gt; }&gt;;
  };
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> stepRegistry: StepRegistry = {
  <span class="hljs-attr">personalInfo</span>: {
    <span class="hljs-attr">viewModelClass</span>: PersonalInformationViewModel,
    <span class="hljs-attr">component</span>: PersonalInformationView,
  },
  <span class="hljs-attr">loanDetails</span>: {
    <span class="hljs-attr">viewModelClass</span>: LoanDetailsViewModel,
    <span class="hljs-attr">component</span>: LoanDetailsView,
  },
  <span class="hljs-attr">bankInfo</span>: {
    <span class="hljs-attr">viewModelClass</span>: BankInformationViewModel,
    <span class="hljs-attr">component</span>: BankInformationView,
  },
};
</code></pre>
<p>This registry serves as a central configuration point for our Stepper. Each entry maps a unique key to an object containing both the ViewModel class and its corresponding View component. This approach provides several advantages:</p>
<ol>
<li><p>It decouples the definition of our steps from their implementation</p>
</li>
<li><p>It makes the order of steps explicit and easily modifiable</p>
</li>
<li><p>It allows for dynamic step management (adding, removing, or reordering steps)</p>
</li>
</ol>
<h2 id="heading-standardizing-viewmodel-structure">Standardizing ViewModel Structure</h2>
<p>To standardize the structure of the ViewModels and make it easy for the Parent ViewModel and UI to access the required information from all ViewModels, I've created new interfaces and extended the functionality of some existing ones.</p>
<h3 id="heading-iviewmodel">IViewModel</h3>
<p>This generic interface has been extended where T is the object we're going to validate. The concept is straightforward:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Observable } <span class="hljs-keyword">from</span> <span class="hljs-string">"rxjs"</span>;
<span class="hljs-keyword">import</span> type { IValidationError } <span class="hljs-keyword">from</span> <span class="hljs-string">"./IValidationError"</span>;

<span class="hljs-keyword">export</span> interface IViewModel&lt;T&gt; {
  <span class="hljs-attr">data$</span>: Observable&lt;T&gt;;
  errors$: Observable&lt;IValidationError[]&gt;;
  updateData(data: Partial&lt;T&gt;): <span class="hljs-keyword">void</span>;
  validate(): <span class="hljs-keyword">void</span>;
}
</code></pre>
<p>The interface uses RxJS observables to expose reactive streams of both the data and validation errors. This reactive approach is central to the MVVM pattern, allowing the View to automatically update when the underlying data or validation state changes. The <code>updateData</code> method allows for partial updates to the model, while <code>validate</code> triggers validation of the current state.</p>
<h3 id="heading-ivalidationerror">IValidationError</h3>
<p>This interface indicates the structure our validation errors will have:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> interface IValidationError {
  <span class="hljs-attr">field</span>: string;
  message: string;
}
</code></pre>
<p>By standardizing our validation errors with a consistent structure, we enable uniform error handling across all ViewModels. The <code>field</code> property identifies which specific field has the error, while the <code>message</code> provides user-friendly information about the validation failure.</p>
<h3 id="heading-istepviewmodel">IStepViewModel</h3>
<p>This interface is a marker that will help the Parent ViewModel identify the different ViewModels that will be deployed in the Stepper:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> type { IViewModel } <span class="hljs-keyword">from</span> <span class="hljs-string">"./IViewModel"</span>;

<span class="hljs-keyword">export</span> interface IStepViewModel&lt;T&gt; <span class="hljs-keyword">extends</span> IViewModel&lt;T&gt; {
}
</code></pre>
<p>While this interface doesn't add additional methods, it serves an important architectural purpose by clearly indicating which ViewModels are designed to work as steps in our Stepper. This kind of type-based classification enhances code readability and maintainability.</p>
<h2 id="heading-parent-viewmodel-implementation">Parent ViewModel Implementation</h2>
<p>The Parent ViewModel reads the ViewModel registry, creates an instance of each ViewModel (we could use dependency injection to construct the ViewModel), and can move steps forward, backward, and get the current ViewModel.</p>
<p>The Parent ViewModel persists the instance of each of the ViewModels to prevent React from creating new instances of them on each render:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { BehaviorSubject, Observable } <span class="hljs-keyword">from</span> <span class="hljs-string">"rxjs"</span>;
<span class="hljs-keyword">import</span> { map } <span class="hljs-keyword">from</span> <span class="hljs-string">"rxjs/operators"</span>;
<span class="hljs-keyword">import</span> { stepRegistry } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/helpers/stepRegistry"</span>;
<span class="hljs-keyword">import</span> type { IStepViewModel } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/interfaces/IStepViewModel"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ParentViewModel</span> </span>{
  private viewModels: <span class="hljs-built_in">Array</span>&lt;IStepViewModel&lt;any&gt;&gt; = [];
  private currentStepSubject = <span class="hljs-keyword">new</span> BehaviorSubject&lt;number&gt;(<span class="hljs-number">0</span>);

  public currentStep$ = <span class="hljs-built_in">this</span>.currentStepSubject.asObservable();
  public currentViewModel$: Observable&lt;IStepViewModel&lt;any&gt;&gt;;

  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-comment">// we could use DI to inject the view models, but for now we'll just use the registry</span>
    <span class="hljs-built_in">this</span>.viewModels = <span class="hljs-built_in">Object</span>.values(stepRegistry).map(<span class="hljs-function">(<span class="hljs-params">entry</span>) =&gt;</span> <span class="hljs-keyword">new</span> entry.viewModelClass());
    <span class="hljs-built_in">this</span>.currentViewModel$ = <span class="hljs-built_in">this</span>.currentStep$.pipe(
      map(<span class="hljs-function">(<span class="hljs-params">step</span>) =&gt;</span> <span class="hljs-built_in">this</span>.viewModels[step])
    );
  }

  nextStep(): <span class="hljs-keyword">void</span> {
    <span class="hljs-keyword">const</span> currentStep = <span class="hljs-built_in">this</span>.currentStepSubject.value;
    <span class="hljs-keyword">if</span> (currentStep &lt; <span class="hljs-built_in">this</span>.viewModels.length - <span class="hljs-number">1</span>) {
      <span class="hljs-built_in">this</span>.currentStepSubject.next(currentStep + <span class="hljs-number">1</span>);
    }
  }

  previousStep(): <span class="hljs-keyword">void</span> {
    <span class="hljs-keyword">const</span> currentStep = <span class="hljs-built_in">this</span>.currentStepSubject.value;
    <span class="hljs-keyword">if</span> (currentStep &gt; <span class="hljs-number">0</span>) {
      <span class="hljs-built_in">this</span>.currentStepSubject.next(currentStep - <span class="hljs-number">1</span>);
    }
  }

  getCurrentStep(): number {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.currentStepSubject.value;
  }

  getViewModel(step: number): IStepViewModel&lt;any&gt; | <span class="hljs-literal">undefined</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.viewModels[step];
  }
}
</code></pre>
<p>The Parent ViewModel uses RxJS's <code>BehaviorSubject</code> to maintain the current step index and expose it as an observable. This reactive approach allows components to subscribe to step changes and automatically update when the step changes.</p>
<p>Another key implementation detail is the <code>currentViewModel$</code> observable, which provides a stream of the current ViewModel based on the current step. This makes it easy for the View to always have access to the appropriate ViewModel without having to manually determine which one is active.</p>
<h2 id="heading-parent-view-implementation">Parent View Implementation</h2>
<p>The Parent View persists the instance of Parent ViewModel using <code>useRef</code>. Thanks to this, we'll have only one instance of this ViewModel and, in turn, one instance of each ViewModel in the Stepper, thus preventing React from creating a new instance on each render.</p>
<p>This component subscribes to the <code>currentStep$</code> property at the component's initialization:</p>
<pre><code class="lang-javascript">useEffect(<span class="hljs-function">() =&gt;</span> {
  subscription = parentViewModel.currentStep$.subscribe(<span class="hljs-function">(<span class="hljs-params">step</span>) =&gt;</span> {
    setCurrentStep(step);
  });

  <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (subscription) {
      subscription.unsubscribe();
    }
  };
}, []);
</code></pre>
<p>Here's where the magic happens:</p>
<p>As the user moves through the Stepper, we obtain the ViewModel that corresponds to each step:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> currentEntry = <span class="hljs-built_in">Object</span>.values(stepRegistry)[currentStep];
<span class="hljs-keyword">const</span> CurrentComponent = currentEntry.component;
<span class="hljs-keyword">const</span> currentViewModel = parentViewModel.getViewModel(currentStep);
</code></pre>
<p>Finally, we render the Component:</p>
<pre><code class="lang-javascript">&lt;CurrentComponent viewModel={currentViewModel} /&gt;
</code></pre>
<h2 id="heading-benefits-of-this-approach">Benefits of This Approach</h2>
<p>With this technique, to add, remove, or change ViewModels, all I have to do is go to the registry and modify the ViewModels that will be displayed. This provides exceptional flexibility without having to touch the core implementation of the Stepper component.</p>
<p>I could even extend the registry to also be a ViewModel and dynamically add ViewModels using dependency injection or some other strategy.</p>
<p>The separation of concerns here is particularly strong:</p>
<ol>
<li><p>Each ViewModel handles only its own data and validation logic</p>
</li>
<li><p>The Parent ViewModel orchestrates the flow between steps without knowing the details of each step</p>
</li>
<li><p>The Views focus solely on rendering the UI based on the ViewModel state</p>
</li>
<li><p>The registry serves as a centralized configuration point</p>
</li>
</ol>
<p>This approach also makes testing much easier, as each component can be tested in isolation.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The Parent ViewModel pattern offers a powerful solution for complex multi-step interfaces in React applications. By leveraging TypeScript's type system and the principles of the MVVM pattern, we can create highly modular, maintainable, and extensible components.</p>
<p>This approach particularly shines in enterprise applications where forms may need to evolve over time, with steps being added, removed, or reordered. The registry-based configuration makes these changes trivial, without requiring changes to the core implementation.</p>
]]></content:encoded></item><item><title><![CDATA[MVVM with Typescript and React for .NET Developers]]></title><description><![CDATA[A long time ago, around 2008, was when I fully immersed myself in desktop software development with WPF and a framework that died long ago, MEF, along with PRISM, which has continued to evolve.
Windows Presentation Foundation, without fear of being w...]]></description><link>https://override.dev/mvvm-with-typescript-and-react-for-net-developers</link><guid isPermaLink="true">https://override.dev/mvvm-with-typescript-and-react-for-net-developers</guid><category><![CDATA[MVVM]]></category><category><![CDATA[React]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[dotnet]]></category><dc:creator><![CDATA[Rick]]></dc:creator><pubDate>Tue, 15 Apr 2025 08:19:53 GMT</pubDate><content:encoded><![CDATA[<p>A long time ago, around 2008, was when I fully immersed myself in desktop software development with WPF and a framework that died long ago, MEF, along with PRISM, which has continued to evolve.</p>
<p>Windows Presentation Foundation, without fear of being wrong, is the father of MVVM, so to speak, as this pattern and the concept of data binding were introduced with this framework.</p>
<p>I was truly amazed by this UI framework when I had the opportunity to attend a Telerik conference where they showcased their UI components with WPF.</p>
<p>WPF was (and still is) very powerful, and what caught my attention was how the project in the presentation was structured, where the UI was completely empty (without codebehind). I was already familiar with MVVM, but it wasn't until that moment when I decided to deepen my knowledge of this pattern.</p>
<p>After many years making XAML apps for WPF, Silverlight, UWP with MVVM, times change, and I ventured into the world of TypeScript with React. Surprisingly, I realized that very few or almost no one, at least publicly, uses MVVM with this UI framework.</p>
<p>All the examples I found (and still find) are projects with a basic structure that honestly leaves much to be desired, as their level of separation of concerns is limited to the use of folders.</p>
<p>After developing desktop apps for more than a decade, I realized that in the web world, adopting these practices was relatively rare for web developers. Additionally, at the JavaScript/TypeScript level, I saw terrible practices (I still see them) when watching YouTubers and example code.</p>
<p>Although I understand they are examples, I would be quite concerned if these practices are carried over to enterprise-level applications (I fear that is the case).</p>
<p>That's why I decided to create a repository that uses a template for my personal projects as a base:</p>
<p><a target="_blank" href="https://github.com/SrRickGrimes/SaaS-Template-V2">SrRickGrimes/SaaS-Template-V2</a></p>
<p>This repository only contains the skeleton of the projects I develop and I don't intend to touch on it in this article.</p>
<p>As I mentioned before, using this repository as a base, I've created a simple demo where I show patterns used in enterprise-level applications. You might say that some applied patterns are too much for the scenario, but the objective is to show how they are applied. It will be up to you whether to apply them depending on your requirements.</p>
<h2 id="heading-lets-begin-by-explaining-the-problem-to-solve">Let's Begin by Explaining the Problem to Solve</h2>
<p>The best scenario I found is a banking loan app, where typically the fields to fill out are endless. I've seen applications with up to 1,000 properties divided among 60 or 70 forms.</p>
<p>The question is: how could we develop an app that is scalable, maintainable, and easy to understand?</p>
<p>This is where MVVM shines by itself, as it was created for these complex scenarios where we require a clear separation of responsibilities. By applying some advanced techniques, we can have a clean solution, and although it may grow over time, the technical debt will be manageable.</p>
<h2 id="heading-the-requirements">The Requirements</h2>
<p>The requirements are basic. We need to create a Stepper where this stepper can have n forms. These forms can change order or no longer be required as time passes, or we can also add more.</p>
<p>If we think about it in React style, I can already imagine using React Context with some other State framework, etc., which could make sense. But the key here is that the business logic and everything we write in our forms should be agnostic of React. This principle is very useful, especially in a JavaScript ecosystem where a new framework comes out every weekend. Today it's React, next month it's Vue, and the month after that, it's Svelte or some other.</p>
<p>The JavaScript ecosystem is very unstable, so it's extremely important that enterprise applications and their business rules are decoupled from the UI of the moment.</p>
<h2 id="heading-what-is-mvvm">What is MVVM?</h2>
<p>MVVM stands for Model - View - ViewModel</p>
<h3 id="heading-model">Model</h3>
<p>In simple words, the Model is our class with properties to encapsulate the data of our app, generally POCOs. These models are typically used with services, repositories, etc.</p>
<h3 id="heading-viewmodel">ViewModel</h3>
<p>The ViewModel implements properties and commands with which the view can connect through data binding, and it notifies the view of any state change that occurs through notification events.</p>
<p>The commands provide functionality that the UI can use to determine the functionality that will be displayed in the UI.</p>
<h3 id="heading-view">View</h3>
<p>The view is responsible for defining the structure, layout, and appearance of what the user sees on the screen. It does not contain business logic; however, in some cases, it could contain code-behind specific to the UI framework used, such as animations.</p>
<p>There is extensive documentation and many examples for .NET with WPF/UWP and other XAML-based frameworks.</p>
<p>To my surprise, the examples I found in TypeScript are very few or too basic, or in my humble opinion, they don't follow the principles of MVVM because they add UI functionalities within the ViewModels, breaking several principles.</p>
<h2 id="heading-the-loan-manager-wizard-components">The Loan Manager Wizard Components</h2>
<p>The Loan Manager Wizard consists of several sections:</p>
<ol>
<li><p><strong>Personal Information</strong>: Client's personal information</p>
</li>
<li><p><strong>Bank Information</strong>: Bank information</p>
</li>
<li><p><strong>Loan Details</strong>: Loan details</p>
</li>
</ol>
<h2 id="heading-bankinformation-model">BankInformation Model</h2>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { BehaviorSubject } <span class="hljs-keyword">from</span> <span class="hljs-string">"rxjs"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BankInformation</span> </span>{
  private bankNameSubject = <span class="hljs-keyword">new</span> BehaviorSubject&lt;string&gt;(<span class="hljs-string">""</span>);
  private accountTypeSubject = <span class="hljs-keyword">new</span> BehaviorSubject&lt;string&gt;(<span class="hljs-string">""</span>);
  private accountNumberSubject = <span class="hljs-keyword">new</span> BehaviorSubject&lt;string&gt;(<span class="hljs-string">""</span>);

  public bankName$ = <span class="hljs-built_in">this</span>.bankNameSubject.asObservable();
  public accountType$ = <span class="hljs-built_in">this</span>.accountTypeSubject.asObservable();
  public accountNumber$ = <span class="hljs-built_in">this</span>.accountNumberSubject.asObservable();

  <span class="hljs-keyword">constructor</span>(data?: { bankName?: string; accountType?: string; accountNumber?: string }) {
    <span class="hljs-keyword">if</span> (data?.bankName) <span class="hljs-built_in">this</span>.bankNameSubject.next(data.bankName);
    <span class="hljs-keyword">if</span> (data?.accountType) <span class="hljs-built_in">this</span>.accountTypeSubject.next(data.accountType);
    <span class="hljs-keyword">if</span> (data?.accountNumber) <span class="hljs-built_in">this</span>.accountNumberSubject.next(data.accountNumber);
  }

  setBankName(bankName: string): <span class="hljs-keyword">void</span> {
    <span class="hljs-built_in">this</span>.bankNameSubject.next(bankName);
  }

  setAccountType(accountType: string): <span class="hljs-keyword">void</span> {
    <span class="hljs-built_in">this</span>.accountTypeSubject.next(accountType);
  }

  setAccountNumber(accountNumber: string): <span class="hljs-keyword">void</span> {
    <span class="hljs-built_in">this</span>.accountNumberSubject.next(accountNumber);
  }

  <span class="hljs-keyword">get</span> <span class="hljs-title">bankName</span>(): <span class="hljs-title">string</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.bankNameSubject.value;
  }

  <span class="hljs-keyword">get</span> <span class="hljs-title">accountType</span>(): <span class="hljs-title">string</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.accountTypeSubject.value;
  }

  <span class="hljs-keyword">get</span> <span class="hljs-title">accountNumber</span>(): <span class="hljs-title">string</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.accountNumberSubject.value;
  }
}
</code></pre>
<h2 id="heading-loandetails-model">LoanDetails Model</h2>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { BehaviorSubject } <span class="hljs-keyword">from</span> <span class="hljs-string">"rxjs"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LoanDetails</span> </span>{
  private loanAmountSubject = <span class="hljs-keyword">new</span> BehaviorSubject&lt;number&gt;(<span class="hljs-number">0</span>);
  private loanTermSubject = <span class="hljs-keyword">new</span> BehaviorSubject&lt;number&gt;(<span class="hljs-number">0</span>);
  private loanPurposeSubject = <span class="hljs-keyword">new</span> BehaviorSubject&lt;number&gt;(<span class="hljs-number">0</span>);

  public loanAmount$ = <span class="hljs-built_in">this</span>.loanAmountSubject.asObservable();
  public loanTerm$ = <span class="hljs-built_in">this</span>.loanTermSubject.asObservable();
  public loanPurpose$ = <span class="hljs-built_in">this</span>.loanPurposeSubject.asObservable();

  <span class="hljs-keyword">constructor</span>(data?: { loanAmount?: number; loanTerm?: number; loanPurpose?: number }) {
    <span class="hljs-keyword">if</span> (data?.loanAmount) <span class="hljs-built_in">this</span>.loanAmountSubject.next(data.loanAmount);
    <span class="hljs-keyword">if</span> (data?.loanTerm) <span class="hljs-built_in">this</span>.loanTermSubject.next(data.loanTerm);
    <span class="hljs-keyword">if</span> (data?.loanPurpose) <span class="hljs-built_in">this</span>.loanPurposeSubject.next(data.loanPurpose);
  }

  setLoanAmount(loanAmount: number): <span class="hljs-keyword">void</span> {
    <span class="hljs-built_in">this</span>.loanAmountSubject.next(loanAmount);
  }

  setLoanTerm(loanTerm: number): <span class="hljs-keyword">void</span> {
    <span class="hljs-built_in">this</span>.loanTermSubject.next(loanTerm);
  }

  setLoanPurpose(loanPurpose: number): <span class="hljs-keyword">void</span> {
    <span class="hljs-built_in">this</span>.loanPurposeSubject.next(loanPurpose);
  }

  <span class="hljs-keyword">get</span> <span class="hljs-title">loanAmount</span>(): <span class="hljs-title">number</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.loanAmountSubject.value;
  }

  <span class="hljs-keyword">get</span> <span class="hljs-title">loanTerm</span>(): <span class="hljs-title">number</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.loanTermSubject.value;
  }

  <span class="hljs-keyword">get</span> <span class="hljs-title">loanPurpose</span>(): <span class="hljs-title">number</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.loanPurposeSubject.value;
  }
}
</code></pre>
<h2 id="heading-personalinformation-model">PersonalInformation Model</h2>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { BehaviorSubject } <span class="hljs-keyword">from</span> <span class="hljs-string">"rxjs"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PersonalInformation</span> </span>{
  private fullNameSubject = <span class="hljs-keyword">new</span> BehaviorSubject&lt;string&gt;(<span class="hljs-string">''</span>);
  private dateOfBirthSubject = <span class="hljs-keyword">new</span> BehaviorSubject&lt;string&gt;(<span class="hljs-string">''</span>);
  private emailSubject = <span class="hljs-keyword">new</span> BehaviorSubject&lt;string&gt;(<span class="hljs-string">''</span>);

  public fullName$ = <span class="hljs-built_in">this</span>.fullNameSubject.asObservable();
  public dateOfBirth$ = <span class="hljs-built_in">this</span>.dateOfBirthSubject.asObservable();
  public email$ = <span class="hljs-built_in">this</span>.emailSubject.asObservable();

  <span class="hljs-keyword">constructor</span>(data?: { fullName?: string; dateOfBirth?: string; email?: string }) {
    <span class="hljs-keyword">if</span> (data?.fullName) <span class="hljs-built_in">this</span>.fullNameSubject.next(data.fullName);
    <span class="hljs-keyword">if</span> (data?.dateOfBirth) <span class="hljs-built_in">this</span>.dateOfBirthSubject.next(data.dateOfBirth);
    <span class="hljs-keyword">if</span> (data?.email) <span class="hljs-built_in">this</span>.emailSubject.next(data.email);
  }

  <span class="hljs-keyword">get</span> <span class="hljs-title">fullName</span>(): <span class="hljs-title">string</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.fullNameSubject.value;
  }

  <span class="hljs-keyword">get</span> <span class="hljs-title">dateOfBirth</span>(): <span class="hljs-title">string</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.dateOfBirthSubject.value;
  }

  <span class="hljs-keyword">get</span> <span class="hljs-title">email</span>(): <span class="hljs-title">string</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.emailSubject.value;
  }

  setFullName(fullName: string): <span class="hljs-keyword">void</span> {
    <span class="hljs-built_in">this</span>.fullNameSubject.next(fullName);
  }

  setDateOfBirth(dateOfBirth: string): <span class="hljs-keyword">void</span> {
    <span class="hljs-built_in">this</span>.dateOfBirthSubject.next(dateOfBirth);
  }

  setEmail(email: string): <span class="hljs-keyword">void</span> {
    <span class="hljs-built_in">this</span>.emailSubject.next(email);
  }
}
</code></pre>
<p>If you review the models, dear reader, you will have noticed that I'm using RxJs. This library is in charge of helping us with the notifications that our model needs to make to know that its state has changed.</p>
<p>In .NET with WPF, we have the INotifyPropertyChanged interface, and WPF and XAML-based apps (also Blazor) include Binding out of the box.</p>
<p>For our manager, we have to do it manually, that's why I'm using RxJs.</p>
<p>The code for the models shouldn't be complex to understand as we only have some observables for each property with its getter and setter.</p>
<h2 id="heading-writing-the-viewmodel">Writing the ViewModel</h2>
<p>In this case, the ViewModels of each section of the Stepper have a main function, which is to validate that the model is always in a Valid state.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { BehaviorSubject, combineLatest, Observable } <span class="hljs-keyword">from</span> <span class="hljs-string">"rxjs"</span>;
<span class="hljs-keyword">import</span> { map, debounceTime } <span class="hljs-keyword">from</span> <span class="hljs-string">"rxjs/operators"</span>;
<span class="hljs-keyword">import</span> type { IStepViewModel } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/interfaces/IStepViewModel"</span>;
<span class="hljs-keyword">import</span> type { IValidationError } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/interfaces/IValidationError"</span>;
<span class="hljs-keyword">import</span> { BankInformation } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/models/bankInformation"</span>;
<span class="hljs-keyword">import</span> { AccountNumberFormatByTypeSpecification } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/specifications/BankInformationSpecifications/AccountNumberFormatByTypeSpecification"</span>;
<span class="hljs-keyword">import</span> { AccountNumberSpecification } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/specifications/BankInformationSpecifications/AccountNumberSpecification"</span>;
<span class="hljs-keyword">import</span> { AccountTypeSpecification } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/specifications/BankInformationSpecifications/AccountTypeSpecification"</span>;
<span class="hljs-keyword">import</span> { BankNameSpecification } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/specifications/BankInformationSpecifications/BankNameSpecification"</span>;
<span class="hljs-keyword">import</span> type { ISpecification } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/specifications/ISpecification"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BankInformationViewModel</span> <span class="hljs-title">implements</span> <span class="hljs-title">IStepViewModel</span>&lt;<span class="hljs-title">BankInformation</span>&gt; </span>{
  private model: BankInformation;
  private _errors: BehaviorSubject&lt;IValidationError[]&gt;;
  private formSpecification: ISpecification&lt;BankInformation&gt;;

  public data$: Observable&lt;BankInformation&gt;;
  public errors$: Observable&lt;IValidationError[]&gt;;
  public isValid$: Observable&lt;boolean&gt;;

  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">this</span>.model = <span class="hljs-keyword">new</span> BankInformation();
    <span class="hljs-built_in">this</span>._errors = <span class="hljs-keyword">new</span> BehaviorSubject&lt;IValidationError[]&gt;([]);

    <span class="hljs-comment">// Create individual specifications</span>
    <span class="hljs-keyword">const</span> bankNameSpec = <span class="hljs-keyword">new</span> BankNameSpecification();
    <span class="hljs-keyword">const</span> accountTypeSpec = <span class="hljs-keyword">new</span> AccountTypeSpecification();
    <span class="hljs-keyword">const</span> accountNumberSpec = <span class="hljs-keyword">new</span> AccountNumberSpecification();
    <span class="hljs-keyword">const</span> accountNumberFormatSpec = <span class="hljs-keyword">new</span> AccountNumberFormatByTypeSpecification();

    <span class="hljs-comment">// Combine specifications for the whole form</span>
    <span class="hljs-built_in">this</span>.formSpecification = bankNameSpec
      .and(accountTypeSpec)
      .and(accountNumberSpec)
      .and(accountNumberFormatSpec);

    <span class="hljs-comment">// Set up data observable</span>
    <span class="hljs-built_in">this</span>.data$ = combineLatest([
      <span class="hljs-built_in">this</span>.model.bankName$,
      <span class="hljs-built_in">this</span>.model.accountType$,
      <span class="hljs-built_in">this</span>.model.accountNumber$,
    ]).pipe(
      map(<span class="hljs-function">(<span class="hljs-params">[bankName, accountType, accountNumber]</span>) =&gt;</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> BankInformation({ bankName, accountType, accountNumber });
      })
    );

    <span class="hljs-comment">// Set up errors observable</span>
    <span class="hljs-built_in">this</span>.errors$ = <span class="hljs-built_in">this</span>._errors.asObservable();

    <span class="hljs-comment">// Set up validity observable</span>
    <span class="hljs-built_in">this</span>.isValid$ = <span class="hljs-built_in">this</span>.errors$.pipe(
      map(<span class="hljs-function"><span class="hljs-params">errors</span> =&gt;</span> errors.length === <span class="hljs-number">0</span>)
    );

    <span class="hljs-comment">// Set up automatic validation when data changes</span>
    <span class="hljs-built_in">this</span>.data$.pipe(
      debounceTime(<span class="hljs-number">300</span>) <span class="hljs-comment">// Wait 300ms after the last change before validating</span>
    ).subscribe(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">this</span>.validate();
    });

    <span class="hljs-comment">// Run initial validation</span>
    <span class="hljs-built_in">this</span>.validate();
  }

  updateData(data: Partial&lt;BankInformation&gt;): <span class="hljs-keyword">void</span> {
    <span class="hljs-keyword">if</span> (data.bankName !== <span class="hljs-literal">undefined</span>) <span class="hljs-built_in">this</span>.model.setBankName(data.bankName);
    <span class="hljs-keyword">if</span> (data.accountType !== <span class="hljs-literal">undefined</span>) <span class="hljs-built_in">this</span>.model.setAccountType(data.accountType);
    <span class="hljs-keyword">if</span> (data.accountNumber !== <span class="hljs-literal">undefined</span>) <span class="hljs-built_in">this</span>.model.setAccountNumber(data.accountNumber);
  }

  updateBankName(bankName: string): <span class="hljs-keyword">void</span> {
    <span class="hljs-built_in">this</span>.model.setBankName(bankName);
  }

  updateAccountType(accountType: string): <span class="hljs-keyword">void</span> {
    <span class="hljs-built_in">this</span>.model.setAccountType(accountType);
  }

  updateAccountNumber(accountNumber: string): <span class="hljs-keyword">void</span> {
    <span class="hljs-built_in">this</span>.model.setAccountNumber(accountNumber);
  }

  validate(): <span class="hljs-keyword">void</span> {
    <span class="hljs-comment">// Use the check method from the combined specification</span>
    <span class="hljs-keyword">const</span> validationResult = <span class="hljs-built_in">this</span>.formSpecification.check(<span class="hljs-built_in">this</span>.model);
    <span class="hljs-comment">// Map validation errors to the expected format</span>
    <span class="hljs-keyword">const</span> errors: IValidationError[] = validationResult.errors.map(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> ({
      <span class="hljs-attr">field</span>: error.field,
      <span class="hljs-attr">message</span>: error.message
    }));
    <span class="hljs-comment">// Update the errors subject</span>
    <span class="hljs-built_in">this</span>._errors.next(errors);
  }
}
</code></pre>
<p>As you will see, the ViewModel offers 3 key properties:</p>
<ol>
<li><p><strong>Data</strong>: The model used by the ViewModel and where we can access the information created by the user.</p>
</li>
<li><p><strong>Errors</strong>: The list of validation errors produced by the ViewModel, and these validations will be shown in the UI.</p>
</li>
<li><p><strong>IsValid</strong>: If the ViewModel is in a valid state.</p>
</li>
</ol>
<p>If you look more closely, I have implemented the specification pattern to validate each form.</p>
<p>I don't intend to go into details about what the specification pattern is and what it's used for.</p>
<p>But thanks to this, we can encapsulate the validation logic in different classes, removing that responsibility from the ViewModel (following SOLID principles).</p>
<p>Here is a specification example:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { ValidationResult } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/helpers/validationResult"</span>;
<span class="hljs-keyword">import</span> type { BankInformation } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/models/bankInformation"</span>;
<span class="hljs-keyword">import</span> { Specification } <span class="hljs-keyword">from</span> <span class="hljs-string">"../Specification"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BankNameSpecification</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Specification</span>&lt;<span class="hljs-title">BankInformation</span>&gt; </span>{
  <span class="hljs-keyword">constructor</span>(private minLength: number = 2) {
    <span class="hljs-built_in">super</span>();
  }

  isSatisfiedBy(candidate: BankInformation): boolean {
    <span class="hljs-keyword">return</span> candidate.bankName.length &gt;= <span class="hljs-built_in">this</span>.minLength;
  }

  check(candidate: BankInformation): ValidationResult {
    <span class="hljs-keyword">if</span> (!candidate.bankName || candidate.bankName.trim().length === <span class="hljs-number">0</span>) {
      <span class="hljs-keyword">return</span> ValidationResult.invalid(<span class="hljs-string">'bankName'</span>, <span class="hljs-string">'Bank name is required'</span>);
    }

    <span class="hljs-keyword">if</span> (candidate.bankName.length &lt; <span class="hljs-built_in">this</span>.minLength) {
      <span class="hljs-keyword">return</span> ValidationResult.invalid(<span class="hljs-string">'bankName'</span>, <span class="hljs-string">`Bank name must be at least <span class="hljs-subst">${<span class="hljs-built_in">this</span>.minLength}</span> characters`</span>);
    }

    <span class="hljs-keyword">return</span> ValidationResult.valid();
  }
}
</code></pre>
<p>As you can see, the implementation is very simple and easy to understand, without using third-party libraries or dependencies, purely applying MVVM and following the principle that our implementation should be agnostic of the UI. That's the main reason I've opted for specifications.</p>
<p>Now, using Fluent Specifications design (Fluent API Design Pattern), we can combine the specifications to validate the entire form:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">this</span>.formSpecification = bankNameSpec
  .and(accountTypeSpec)
  .and(accountNumberSpec)
  .and(accountNumberFormatSpec);
</code></pre>
<p>Finally, in our Validate method, we call our formSpecification, and it will validate the entire form, and we only update the errors collection with the errors produced:</p>
<pre><code class="lang-javascript">validate(): <span class="hljs-keyword">void</span> {
  <span class="hljs-comment">// Use the check method from the combined specification</span>
  <span class="hljs-keyword">const</span> validationResult = <span class="hljs-built_in">this</span>.formSpecification.check(<span class="hljs-built_in">this</span>.model);
  <span class="hljs-comment">// Map validation errors to the expected format</span>
  <span class="hljs-keyword">const</span> errors: IValidationError[] = validationResult.errors.map(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> ({
    <span class="hljs-attr">field</span>: error.field,
    <span class="hljs-attr">message</span>: error.message
  }));
  <span class="hljs-comment">// Update the errors subject</span>
  <span class="hljs-built_in">this</span>._errors.next(errors);
}
</code></pre>
<h2 id="heading-connecting-the-view-with-the-viewmodel">Connecting the View with the ViewModel</h2>
<p>This React component is only subscribing to the notifications produced by Data from our ViewModel. Using React's useState, every time a change is produced, we update the bankInfo object, and this propagates the changes in the form components using normal React:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useEffect, useState, type FC } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> type { IStepViewModel } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/interfaces/IStepViewModel"</span>;
<span class="hljs-keyword">import</span> type { IValidationError } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/interfaces/IValidationError"</span>;
<span class="hljs-keyword">import</span> { BankInformation } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/models/bankInformation"</span>;

interface BankInformationViewProps {
  <span class="hljs-attr">viewModel</span>: IStepViewModel&lt;BankInformation&gt;;
}

<span class="hljs-keyword">const</span> BankInformationView: FC&lt;BankInformationViewProps&gt; = <span class="hljs-function">(<span class="hljs-params">{ viewModel }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [bankInfo, setBankInfo] = useState&lt;BankInformation&gt;(<span class="hljs-keyword">new</span> BankInformation());
  <span class="hljs-keyword">const</span> [errors, setErrors] = useState&lt;IValidationError[]&gt;([]);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// Subscribe to data changes</span>
    <span class="hljs-keyword">const</span> dataSubscription = viewModel.data$.subscribe(<span class="hljs-function">(<span class="hljs-params">info</span>) =&gt;</span> {
      setBankInfo(info);
    });
    <span class="hljs-comment">// Subscribe to validation errors</span>
    <span class="hljs-keyword">const</span> errorsSubscription = viewModel.errors$.subscribe(<span class="hljs-function">(<span class="hljs-params">validationErrors</span>) =&gt;</span> {
      setErrors(validationErrors);
    });

    <span class="hljs-comment">// Force an initial validation</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> viewModel.validate === <span class="hljs-string">'function'</span>) {
      viewModel.validate();
    }

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      dataSubscription.unsubscribe();
      errorsSubscription.unsubscribe();
    };
  }, [viewModel]);

  <span class="hljs-comment">// Helper function to get error for a specific field</span>
  <span class="hljs-keyword">const</span> getFieldError = (fieldName: string): string | <span class="hljs-function"><span class="hljs-params">null</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> error = errors.find(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> e.field === fieldName);
    <span class="hljs-keyword">return</span> error ? error.message : <span class="hljs-literal">null</span>;
  };

  <span class="hljs-keyword">const</span> handleBankNameChange = <span class="hljs-function">(<span class="hljs-params">e: React.ChangeEvent&lt;HTMLInputElement&gt;</span>) =&gt;</span> {
    viewModel.updateData({ <span class="hljs-attr">bankName</span>: e.target.value });
  };

  <span class="hljs-keyword">const</span> handleAccountTypeChange = <span class="hljs-function">(<span class="hljs-params">e: React.ChangeEvent&lt;HTMLSelectElement&gt;</span>) =&gt;</span> {
    viewModel.updateData({ <span class="hljs-attr">accountType</span>: e.target.value });
  };

  <span class="hljs-keyword">const</span> handleAccountNumberChange = <span class="hljs-function">(<span class="hljs-params">e: React.ChangeEvent&lt;HTMLInputElement&gt;</span>) =&gt;</span> {
    viewModel.updateData({ <span class="hljs-attr">accountNumber</span>: e.target.value });
  };

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"space-y-4"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-xl font-semibold"</span>&gt;</span>Bank Information<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      {/* Bank Name Field */}
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"form-control"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"label"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"label-text"</span>&gt;</span>Bank Name<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
          <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span>
          <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Bank Name"</span>
          <span class="hljs-attr">value</span>=<span class="hljs-string">{bankInfo.bankName}</span>
          <span class="hljs-attr">onChange</span>=<span class="hljs-string">{handleBankNameChange}</span>
          <span class="hljs-attr">className</span>=<span class="hljs-string">{</span>`<span class="hljs-attr">input</span> <span class="hljs-attr">input-bordered</span> <span class="hljs-attr">w-full</span> ${<span class="hljs-attr">getFieldError</span>('<span class="hljs-attr">bankName</span>') ? '<span class="hljs-attr">border-red-500</span>' <span class="hljs-attr">:</span> ''}`}
        /&gt;</span>
        {getFieldError('bankName') &amp;&amp; (
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-red-500 text-sm mt-1"</span>&gt;</span>{getFieldError('bankName')}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        )}
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      {/* Account Type Field */}
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"form-control"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"label"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"label-text"</span>&gt;</span>Account Type<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">select</span>
          <span class="hljs-attr">value</span>=<span class="hljs-string">{bankInfo.accountType}</span>
          <span class="hljs-attr">onChange</span>=<span class="hljs-string">{handleAccountTypeChange}</span>
          <span class="hljs-attr">className</span>=<span class="hljs-string">{</span>`<span class="hljs-attr">select</span> <span class="hljs-attr">select-bordered</span> <span class="hljs-attr">w-full</span> ${<span class="hljs-attr">getFieldError</span>('<span class="hljs-attr">accountType</span>') ? '<span class="hljs-attr">border-red-500</span>' <span class="hljs-attr">:</span> ''}`}
        &gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">""</span>&gt;</span>Select account type<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"checking"</span>&gt;</span>Checking<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"savings"</span>&gt;</span>Savings<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"credit"</span>&gt;</span>Credit<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">select</span>&gt;</span>
        {getFieldError('accountType') &amp;&amp; (
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-red-500 text-sm mt-1"</span>&gt;</span>{getFieldError('accountType')}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        )}
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      {/* Account Number Field */}
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"form-control"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"label"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"label-text"</span>&gt;</span>Account Number<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
          <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span>
          <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Account Number"</span>
          <span class="hljs-attr">value</span>=<span class="hljs-string">{bankInfo.accountNumber}</span>
          <span class="hljs-attr">onChange</span>=<span class="hljs-string">{handleAccountNumberChange}</span>
          <span class="hljs-attr">className</span>=<span class="hljs-string">{</span>`<span class="hljs-attr">input</span> <span class="hljs-attr">input-bordered</span> <span class="hljs-attr">w-full</span> ${<span class="hljs-attr">getFieldError</span>('<span class="hljs-attr">accountNumber</span>') ? '<span class="hljs-attr">border-red-500</span>' <span class="hljs-attr">:</span> ''}`}
        /&gt;</span>
        {getFieldError('accountNumber') &amp;&amp; (
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-red-500 text-sm mt-1"</span>&gt;</span>{getFieldError('accountNumber')}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        )}
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-gray-500 text-xs mt-1"</span>&gt;</span>
          {bankInfo.accountType === 'checking' &amp;&amp; 'Checking accounts must start with 4 or 5'}
          {bankInfo.accountType === 'savings' &amp;&amp; 'Savings accounts must start with 1 or 2'}
          {bankInfo.accountType === 'credit' &amp;&amp; 'Credit accounts must start with 9'}
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> BankInformationView;
</code></pre>
<p>As you can see, MVVM is a very powerful pattern, and highly scalable and maintainable apps can be developed as long as the principles are followed correctly.</p>
<p>In the next article, I will cover other aspects of the same repository that are not mentioned in this article.</p>
<p>Here is the repository where you can find the implementation: <a target="_blank" href="https://github.com/SrRickGrimes/SaaS-Template-V2">SrRickGrimes/SaaS-Template-V2</a></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The MVVM pattern, originally popularized in the desktop application world with WPF, can be successfully applied to modern web development with React. By keeping our business logic agnostic from the UI framework and using advanced patterns like Specification for validation, we can build scalable applications that are easier to maintain over time. The key benefits include:</p>
<ol>
<li><p>Clear separation of concerns</p>
</li>
<li><p>Testable business logic independent of UI</p>
</li>
<li><p>Reactive state management with RxJs</p>
</li>
<li><p>Composable validation using the Specification pattern</p>
</li>
<li><p>UI framework independence for core business rules</p>
</li>
</ol>
<p>While this approach might seem more complex initially compared to typical React examples found online, it pays dividends as the application grows in complexity, especially for enterprise applications with numerous forms and complex validation rules.</p>
]]></content:encoded></item><item><title><![CDATA[WhatsApp CRM Starting with Research]]></title><description><![CDATA[When I decided to create a CRM for WhatsApp, I chose to investigate the best development approach first.
Rather than just sitting down to write code and imagining this CRM—which is the most comfortable thing a software engineer can do, locking themse...]]></description><link>https://override.dev/whatsapp-crm-starting-with-research</link><guid isPermaLink="true">https://override.dev/whatsapp-crm-starting-with-research</guid><category><![CDATA[whatsapp]]></category><category><![CDATA[Startups]]></category><category><![CDATA[crm]]></category><category><![CDATA[Entrepreneurship]]></category><dc:creator><![CDATA[Rick]]></dc:creator><pubDate>Sat, 12 Apr 2025 13:53:47 GMT</pubDate><content:encoded><![CDATA[<p>When I decided to create a CRM for WhatsApp, I chose to investigate the best development approach first.</p>
<p>Rather than just sitting down to write code and imagining this CRM—which is the most comfortable thing a software engineer can do, locking themselves in their bubble and spending months or years building something nobody will use or pay for—I took a different path.</p>
<p>I decided to learn even more about startups and modern techniques in this field, as my first interaction with entrepreneurship was back in 2010 when I attended an event for entrepreneurs.</p>
<p>Back then, the proposal was to create an MVP (Minimum Viable Product) and test it with customers interactively. While it sounds good in theory, based on my experience, I realized this might no longer apply.</p>
<p>Developing applications isn't as difficult as it used to be because technology keeps advancing—things like artificial intelligence and now these new "vibe coders"... who claim they can make apps (I'll talk about this topic in another post and explain why I think now more than ever we need engineers who know how to develop software).</p>
<p>So I decided to explore the topic further and found Rob Walling's channel [MicroConf](https://youtube.com/@microconf?si=9hAF-1phA-macfVv).</p>
<p>I watched all the videos from this content creator, and honestly, it changed my thinking about startups and the ideas I had about them since 2010.</p>
<h3 id="heading-key-insights"><strong>Key Insights</strong></h3>
<p>Some phrases that stuck with me after watching his videos:</p>
<p><strong><em>"There are problems not worth solving."</em></strong></p>
<p><strong><em>"Instead of telling me your idea, tell me the problem you want to solve."</em></strong></p>
<p>After analyzing and studying entrepreneurship again, I realized several things.</p>
<p>Code and its quality, although important, ultimately don't matter because customers only focus on whether it solves their problem, is stable, and provides a good user experience.</p>
<p>This last point short-circuited my way of thinking because, having been a software architect for many years, I always focused on the technical aspects of the product but had never focused on what the customer wants from their perspective and is willing to pay for.</p>
<h3 id="heading-getting-my-hands-dirty-with-research">Getting My Hands Dirty with Research</h3>
<p>So I took on the task of contacting different companies and startups that use WhatsApp as their main point of access to customers and businesses. I spent time taking notes, even from many who were already using solutions like LeadSales and Kommo.</p>
<p>The list of problems was significant, and within this list, common issues emerged. These are where I concentrated to start planning the main features that together would solve my potential customers' problems.</p>
<h3 id="heading-real-problems-i-identified">Real Problems I Identified</h3>
<p>Here are some of the problems I managed to identify:</p>
<ul>
<li><p>Many existing applications don't offer a UX designed with the user in mind, requiring people to take courses to understand how they work.</p>
</li>
<li><p>Lack of preview for incoming messages, which means users have to open the chat to see them.</p>
</li>
<li><p>Very basic chat organization model; even though LeadSales offers funnels, the features are very basic.</p>
</li>
<li><p>Lack of metrics that actually serve the business; it's not valuable for many to just indicate how many messages have been received.</p>
</li>
<li><p>The bots offered by some platforms are very basic.</p>
</li>
</ul>
<p>And I could continue with many more problems that were mentioned to me.</p>
<h3 id="heading-whats-next">What's Next?</h3>
<p>In the next article, I'll broadly explain what features the CRM will provide in its first version and how this has been validated interactively with a feedback loop.</p>
<h3 id="heading-my-takeaway">My Takeaway</h3>
<p>Look, I've been in this game a while, and the biggest shift in my thinking has been moving from "what cool tech can I build?" to "what problems can I actually solve?"</p>
<p>Talking to real businesses using WhatsApp has been eye-opening. These folks are struggling with clunky interfaces, spending way too much time managing conversations, and missing opportunities because their tools just aren't cutting it.</p>
<p>The irony is that while I was obsessing over architecture decisions and code quality (which I still love, don't get me wrong), most users couldn't care less about my beautiful microservices or elegant code patterns. They just want something that makes their work easier and helps them close more deals.</p>
<p>This research phase has completely transformed how I'm approaching this CRM. Instead of building what I think is cool, I'm building what I know solves real headaches for real businesses. And honestly? That's way more satisfying than showing off some fancy tech stack that nobody appreciates.</p>
<p>Stay tuned for the next post where I'll break down exactly what this WhatsApp CRM will do and how each feature directly addresses the pain points I discovered during my conversations with potential users.</p>
]]></content:encoded></item><item><title><![CDATA[WhatsApp CRM ( the backend)]]></title><description><![CDATA[Building a CRM for Whatsapp choosing the backend
When I decided to create a WhatsApp CRM, I began researching existing apps from a technical perspective to understand their challenges and how difficult WhatsApp integration would be.
Fortunately, I fo...]]></description><link>https://override.dev/whatsapp-crm-the-backend</link><guid isPermaLink="true">https://override.dev/whatsapp-crm-the-backend</guid><category><![CDATA[whatsapp]]></category><category><![CDATA[Microsoft Orleans]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[Startups]]></category><category><![CDATA[crm]]></category><dc:creator><![CDATA[Rick]]></dc:creator><pubDate>Fri, 11 Apr 2025 07:48:59 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-building-a-crm-for-whatsapp-choosing-the-backend">Building a CRM for Whatsapp choosing the backend</h1>
<p>When I decided to create a WhatsApp CRM, I began researching existing apps from a technical perspective to understand their challenges and how difficult WhatsApp integration would be.</p>
<p>Fortunately, I found a video from the entrepreneurs behind LeadSales platform where they explained the technical challenges they've faced (I'm not sure if they still have these issues).</p>
<p>What caught my attention most was their scalability challenge. In their video, they mentioned handling 70 million messages per month—a considerable volume—and were experiencing scalability problems.</p>
<p>This is where I thought: they're already established with many users, and any entrepreneur would tell you to build an MVP to test the market first, or create a prototype.</p>
<p>However, in this case, they had already done that research for me, validating the market. According to various entrepreneurship books I've read, you either find a "blue ocean" (extremely rare these days) or enter to compete. I chose the latter, only verifying the market wasn't as saturated as others like note-taking apps, personal finance apps, video games, etc.</p>
<p>The first challenge they presented was scaling. Many engineers' natural response is microservices—the trend for the past 10 years. But as I've said before and will continue saying, 99% of cases don't require microservices. In practice, I see many disadvantages which I won't detail here (perhaps in another article).</p>
<p>Ten years ago, I had the opportunity to meet the development team behind Microsoft Orleans, a framework powering Microsoft services like Office 365, Xbox Live, and games like Halo and Gears of War.</p>
<p>For these reasons, I chose a more pragmatic and simple approach to solve the problem.</p>
<p>A modular architecture (Modular Monolith) with Microsoft Orleans is the perfect solution for this scenario.</p>
<h2 id="heading-lets-talk-tech">Let's Talk Tech</h2>
<p>Look, when I mention Microsoft Orleans, I'm talking about this cool actor-based framework that makes distributed systems way less painful. Instead of building dozens of separate services that need to talk to each other through complex APIs and message queues, Orleans gives you these things called "virtual actors" that just handle their business automatically across your servers. It's like getting all the scaling benefits without the massive headache.</p>
<p>And this Modular Monolith approach? It's basically having your cake and eating it too. You deploy one application (simple!) but organize it internally into clean, separated modules (organized!). I've seen too many teams jump straight to microservices without realizing what they're signing up for.</p>
<h2 id="heading-the-microservices-trap">The Microservices Trap</h2>
<p>I can't tell you how many projects I've seen where teams went full microservices way too early. The data backs this up too. I was reading that something like 60% of microservices projects fail to deliver what they promised. That's insane!</p>
<p>Most devs don't talk about the hidden costs – you end up with separate databases, complicated service discovery, multiple deployment pipelines, inconsistent monitoring... it's a mess unless you really need it.</p>
<p>At my previous company, we spent almost half our time just managing the infrastructure for our "elegant" microservices architecture. We could've built twice as many features with a simpler approach.</p>
<p>That's why I'm betting on Orleans for this WhatsApp CRM. It's how Xbox Live handles millions of concurrent gamers. If it works for them, it'll definitely handle my messaging volume without forcing me to become a Kubernetes expert overnight.</p>
<p>Sometimes the boring solution is the right one. Build a solid, modular system first. You can always break things apart later when you actually have the scaling problems that microservices solve – which, let's be honest, most of us won't have for a long time.</p>
]]></content:encoded></item><item><title><![CDATA[Crafting a Custom WhatsApp CRM My Journey from Concept to Creation[PART 1]]]></title><description><![CDATA[For over 16 years, I've been developing software across a wide spectrum of technologies. Throughout my career, I've built numerous applications spanning frontend and backend development—from enterprise banking systems to insurance platforms. My techn...]]></description><link>https://override.dev/crafting-a-custom-whatsapp-crm-my-journey-from-concept-to-creationpart-1</link><guid isPermaLink="true">https://override.dev/crafting-a-custom-whatsapp-crm-my-journey-from-concept-to-creationpart-1</guid><category><![CDATA[whatsapp]]></category><category><![CDATA[crm]]></category><category><![CDATA[Startups]]></category><category><![CDATA[Entrepreneurship]]></category><category><![CDATA[engineering]]></category><category><![CDATA[ideas]]></category><dc:creator><![CDATA[Rick]]></dc:creator><pubDate>Fri, 11 Apr 2025 05:19:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1744348570874/b9096a32-bc0f-4e44-a47d-29451ba571ff.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>For over 16 years, I've been developing software across a wide spectrum of technologies. Throughout my career, I've built numerous applications spanning frontend and backend development—from enterprise banking systems to insurance platforms. My technical journey has taken me from Windows Forms to React with Azure and <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core, implementing solutions both on-premises and in the cloud.</p>
<p>But I reached a point in my professional life where I wanted to create something with my own signature—a product that would be entirely mine.</p>
<p>I spent considerable time contemplating what would be worth developing. During a period when personal circumstances required me to use WhatsApp extensively, I found myself overwhelmed with messages from many different contacts. It became practically impossible to keep track of these conversations, and I often forgot which messages needed replies or lost track of ongoing discussions.</p>
<p>I began searching for solutions and discovered applications like LeadSales, Kommo, and others. I tried them, but none quite fit my needs—they were either too simplistic or overly complex, sometimes requiring training courses just to understand how to use them.</p>
<p>That's when I identified a niche where my software expertise could create something to solve my own problem while potentially helping others (particularly businesses) facing similar challenges.</p>
<p>This is how the idea for a WhatsApp CRM was born, with plans to eventually support other messaging platforms as well.</p>
<p>I started developing this platform in June, and now, almost a year into the project, I can say it has matured enough to start talking about it. Personally, I've loved working on it, and it has presented a technical challenge worth sharing.</p>
<p>In this series of articles, I'll be sharing with you, dear reader, the progress of this project—what it's like to start developing a SaaS product from scratch.</p>
<p>What I've particularly enjoyed about this niche is that while competition exists, it's not overwhelming. The complexity involved in creating a CRM of this nature is high, which is exactly the challenge I was looking for.</p>
<p>When you look at a market saturated with many apps, it's often because the problem isn't particularly difficult to solve—hence the abundance of options. Note-taking apps are a clear example; there are hundreds, if not thousands of them. I wanted to invest my time in something challenging but potentially rewarding in the long run—both technically and personally, and hopefully financially as well.</p>
]]></content:encoded></item><item><title><![CDATA[Step-by-Step Guide to Building Custom .NET Templates]]></title><description><![CDATA[Introduction
The .NET SDK provides a powerful templating engine that allows developers to create reusable templates for projects, files, and solution structures. This guide covers how to create, package, and distribute custom templates using the dotn...]]></description><link>https://override.dev/step-by-step-guide-to-building-custom-net-templates</link><guid isPermaLink="true">https://override.dev/step-by-step-guide-to-building-custom-net-templates</guid><category><![CDATA[.NET]]></category><category><![CDATA[template]]></category><category><![CDATA[C#]]></category><category><![CDATA[visual studio]]></category><dc:creator><![CDATA[Rick]]></dc:creator><pubDate>Fri, 11 Apr 2025 04:56:44 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>The .NET SDK provides a powerful templating engine that allows developers to create reusable templates for projects, files, and solution structures. This guide covers how to create, package, and distribute custom templates using the <code>dotnet new</code> command-line interface.</p>
<h2 id="heading-understanding-net-templates">Understanding .NET Templates</h2>
<p>Templates in .NET are packages that contain source files and a configuration that determines how those files should be transformed when instantiated. They provide a way to standardize project structures, apply consistent patterns, and save time when creating new applications or components.</p>
<h3 id="heading-key-concepts">Key Concepts</h3>
<ul>
<li><p><strong>Template</strong>: A set of files and folders that can be transformed into a working project or component</p>
</li>
<li><p><strong>Template Package</strong>: A NuGet package containing one or more templates</p>
</li>
<li><p><strong>Template Engine</strong>: The system that processes templates and generates output</p>
</li>
</ul>
<h2 id="heading-template-structure">Template Structure</h2>
<p>A basic template consists of:</p>
<ol>
<li><p><strong>Source Files and Folders</strong>: The content that will be used to generate new projects</p>
</li>
<li><p><strong>Configuration Files</strong>: JSON files that control how the template behaves</p>
</li>
</ol>
<h3 id="heading-the-templateconfig-directory">The .template.config Directory</h3>
<p>Every template must have a <code>.template.config</code> directory at its root containing at least a <code>template.json</code> file. This file provides metadata and configuration for the template.</p>
<h3 id="heading-templatejson">template.json</h3>
<p>The <code>template.json</code> file defines how the template works. Here's a breakdown of the essential fields:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"$schema"</span>: <span class="hljs-string">"http://json.schemastore.org/template"</span>,
  <span class="hljs-attr">"author"</span>: <span class="hljs-string">"Your Name"</span>,
  <span class="hljs-attr">"classifications"</span>: [<span class="hljs-string">"Category1"</span>, <span class="hljs-string">"Category2"</span>],
  <span class="hljs-attr">"identity"</span>: <span class="hljs-string">"Your.Template.Identity"</span>,
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"User-friendly Template Name"</span>,
  <span class="hljs-attr">"shortName"</span>: <span class="hljs-string">"template-shortname"</span>,
  <span class="hljs-attr">"sourceName"</span>: <span class="hljs-string">"TemplateSourceName"</span>,
  <span class="hljs-attr">"preferNameDirectory"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"tags"</span>: {
    <span class="hljs-attr">"language"</span>: <span class="hljs-string">"C#"</span>,
    <span class="hljs-attr">"type"</span>: <span class="hljs-string">"project"</span>
  }
}
</code></pre>
<ul>
<li><p><strong>$schema</strong>: Reference to the JSON schema</p>
</li>
<li><p><strong>author</strong>: Creator of the template</p>
</li>
<li><p><strong>classifications</strong>: Categories for organizing templates</p>
</li>
<li><p><strong>identity</strong>: Unique identifier for the template</p>
</li>
<li><p><strong>name</strong>: Display name for the template</p>
</li>
<li><p><strong>shortName</strong>: Command-line alias for the template</p>
</li>
<li><p><strong>sourceName</strong>: The name in source files to be replaced with the user's specified name</p>
</li>
<li><p><strong>preferNameDirectory</strong>: Whether to create a directory for the output</p>
</li>
<li><p><strong>tags</strong>: Metadata for filtering templates</p>
</li>
</ul>
<h2 id="heading-advanced-template-configuration">Advanced Template Configuration</h2>
<h3 id="heading-parameters-and-symbols">Parameters and Symbols</h3>
<p>Templates can define parameters that users can specify when creating a new instance:</p>
<pre><code class="lang-json"><span class="hljs-string">"symbols"</span>: {
  <span class="hljs-attr">"ParameterName"</span>: {
    <span class="hljs-attr">"type"</span>: <span class="hljs-string">"parameter"</span>,
    <span class="hljs-attr">"datatype"</span>: <span class="hljs-string">"bool"</span>,
    <span class="hljs-attr">"defaultValue"</span>: <span class="hljs-string">"false"</span>,
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Parameter description"</span>
  }
}
</code></pre>
<h3 id="heading-conditional-content">Conditional Content</h3>
<p>You can include or exclude files based on parameter values:</p>
<pre><code class="lang-json"><span class="hljs-string">"sources"</span>: [
  {
    <span class="hljs-attr">"modifiers"</span>: [
      {
        <span class="hljs-attr">"condition"</span>: <span class="hljs-string">"(!ParameterName)"</span>,
        <span class="hljs-attr">"exclude"</span>: [<span class="hljs-string">"ExcludedFolder/**/*"</span>]
      }
    ]
  }
]
</code></pre>
<h3 id="heading-post-creation-actions">Post-Creation Actions</h3>
<p>You can define actions to be executed after the template is instantiated:</p>
<pre><code class="lang-json"><span class="hljs-string">"postActions"</span>: [
  {
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Additional setup steps"</span>,
    <span class="hljs-attr">"manualInstructions"</span>: [
      {
        <span class="hljs-attr">"text"</span>: <span class="hljs-string">"Instructions for the user after template creation"</span>
      }
    ],
    <span class="hljs-attr">"actionId"</span>: <span class="hljs-string">"CB9A6CF3-4F5C-4860-B9D2-03A574959774"</span>,
    <span class="hljs-attr">"continueOnError"</span>: <span class="hljs-literal">true</span>
  }
]
</code></pre>
<h2 id="heading-packaging-and-distribution">Packaging and Distribution</h2>
<h3 id="heading-creating-a-nuget-package">Creating a NuGet Package</h3>
<p>To distribute your template, you need to create a NuGet package:</p>
<ol>
<li>Create a <code>.csproj</code> file to define the package:</li>
</ol>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Project</span> <span class="hljs-attr">Sdk</span>=<span class="hljs-string">"Microsoft.NET.Sdk"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageType</span>&gt;</span>Template<span class="hljs-tag">&lt;/<span class="hljs-name">PackageType</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageVersion</span>&gt;</span>1.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">PackageVersion</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageId</span>&gt;</span>Your.Template.Package<span class="hljs-tag">&lt;/<span class="hljs-name">PackageId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Title</span>&gt;</span>Your Template Package<span class="hljs-tag">&lt;/<span class="hljs-name">Title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Authors</span>&gt;</span>Your Name<span class="hljs-tag">&lt;/<span class="hljs-name">Authors</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Description</span>&gt;</span>Your template description<span class="hljs-tag">&lt;/<span class="hljs-name">Description</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageTags</span>&gt;</span>dotnet-new;templates;yourKeywords<span class="hljs-tag">&lt;/<span class="hljs-name">PackageTags</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">TargetFramework</span>&gt;</span>netstandard2.0<span class="hljs-tag">&lt;/<span class="hljs-name">TargetFramework</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">IncludeContentInPack</span>&gt;</span>true<span class="hljs-tag">&lt;/<span class="hljs-name">IncludeContentInPack</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">IncludeBuildOutput</span>&gt;</span>false<span class="hljs-tag">&lt;/<span class="hljs-name">IncludeBuildOutput</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ContentTargetFolders</span>&gt;</span>content<span class="hljs-tag">&lt;/<span class="hljs-name">ContentTargetFolders</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">NoWarn</span>&gt;</span>$(NoWarn);NU5128<span class="hljs-tag">&lt;/<span class="hljs-name">NoWarn</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">NoDefaultExcludes</span>&gt;</span>true<span class="hljs-tag">&lt;/<span class="hljs-name">NoDefaultExcludes</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Content</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"content/**/*"</span> <span class="hljs-attr">Exclude</span>=<span class="hljs-string">"content/**/bin/**;content/**/obj/**"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Compile</span> <span class="hljs-attr">Remove</span>=<span class="hljs-string">"**/*"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Project</span>&gt;</span>
</code></pre>
<ol start="2">
<li>Organize your files:</li>
</ol>
<pre><code class="lang-plaintext">YourTemplatePackage/
├── YourTemplatePackage.csproj
└── content/
    ├── .template.config/
    │   └── template.json
    └── YourTemplateFiles/
</code></pre>
<ol start="3">
<li>Build the package:</li>
</ol>
<pre><code class="lang-bash">dotnet pack -c Release
</code></pre>
<h3 id="heading-installing-templates">Installing Templates</h3>
<p>Templates can be installed from various sources:</p>
<ol>
<li>From a NuGet package:</li>
</ol>
<pre><code class="lang-bash">dotnet new install Your.Template.Package
</code></pre>
<ol start="2">
<li>From a local .nupkg file:</li>
</ol>
<pre><code class="lang-bash">dotnet new install path/to/Your.Template.Package.1.0.0.nupkg
</code></pre>
<ol start="3">
<li>From a file system directory:</li>
</ol>
<pre><code class="lang-bash">dotnet new install path/to/template/directory
</code></pre>
<h3 id="heading-using-templates">Using Templates</h3>
<p>Once installed, you can use your template with:</p>
<pre><code class="lang-bash">dotnet new template-shortname -n ProjectName [--parameter1 value1]
</code></pre>
<h2 id="heading-template-localization">Template Localization</h2>
<p>Templates can be localized to display in different languages. Create a <code>localize</code> folder in the <code>.template.config</code> directory with JSON files named according to the language code:</p>
<pre><code class="lang-plaintext">.template.config/
├── template.json
└── localize/
    ├── templatestrings.en-US.json
    └── templatestrings.es-ES.json
</code></pre>
<p>Each localization file contains key-value pairs matching the fields in <code>template.json</code> that should be translated.</p>
<h2 id="heading-visual-studio-integration">Visual Studio Integration</h2>
<p>To make your template appear in Visual Studio's "New Project" dialog:</p>
<ol>
<li>Add Visual Studio metadata to the <code>template.json</code>:</li>
</ol>
<pre><code class="lang-json"><span class="hljs-string">"tags"</span>: {
  <span class="hljs-attr">"language"</span>: <span class="hljs-string">"C#"</span>,
  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"project"</span>,
  <span class="hljs-attr">"vs-workload"</span>: <span class="hljs-string">"dotnetcore"</span>
}
</code></pre>
<ol start="2">
<li>For complete integration, consider creating a Visual Studio Extension (VSIX) package.</li>
</ol>
<h2 id="heading-best-practices">Best Practices</h2>
<ol>
<li><p><strong>Clear naming</strong>: Choose descriptive, specific names for your templates</p>
</li>
<li><p><strong>Good documentation</strong>: Include clear descriptions and examples</p>
</li>
<li><p><strong>Minimal dependencies</strong>: Avoid unnecessary dependencies</p>
</li>
<li><p><strong>Thoughtful defaults</strong>: Provide sensible default values for parameters</p>
</li>
<li><p><strong>Testing</strong>: Test your templates in various environments before distribution</p>
</li>
<li><p><strong>Versioning</strong>: Follow semantic versioning for your template packages</p>
</li>
<li><p><strong>Organization</strong>: Keep templates focused and organized by functionality</p>
</li>
</ol>
<h2 id="heading-troubleshooting">Troubleshooting</h2>
<h3 id="heading-common-issues">Common Issues</h3>
<ol>
<li><p><strong>Template Not Found</strong>: Ensure the template is installed correctly</p>
</li>
<li><p><strong>File Not Included</strong>: Check your include/exclude patterns</p>
</li>
<li><p><strong>Parameter Not Working</strong>: Verify parameter configuration in <code>template.json</code></p>
</li>
<li><p><strong>Template Not Visible in VS</strong>: Check Visual Studio integration settings</p>
</li>
</ol>
<h3 id="heading-debugging">Debugging</h3>
<p>For detailed logs when using templates:</p>
<pre><code class="lang-bash">dotnet new template-shortname -n ProjectName --verbosity diagnostic
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Custom .NET templates are a powerful way to standardize code structures, enforce architectural patterns, and improve productivity. By following this guide, you can create, package, and distribute templates that will help your team or the wider .NET community build better software more efficiently.</p>
<h2 id="heading-resources">Resources</h2>
<ul>
<li><p><a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/core/tools/custom-templates">Official Microsoft Documentation</a></p>
</li>
<li><p><a target="_blank" href="http://json.schemastore.org/template">Template Schema Reference</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/dotnet/templating">dotnet/templating GitHub Repository</a></p>
</li>
</ul>
]]></content:encoded></item></channel></rss>