<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged design-patterns</title>
<link>https://coderlegion.com/tag/design-patterns</link>
<description>Powered by Question2Answer</description>
<item>
<title>Priority-Based Job Processing System in C#</title>
<link>https://coderlegion.com/18967/priority-based-job-processing-system-in-c</link>
<description>&lt;p&gt;This tutorial explains how to build a &lt;strong&gt;multithreaded, priority-based job processing system&lt;/strong&gt; using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PriorityQueue&amp;lt;TElement,TPriority&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SemaphoreSlim&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Producer / Consumer pattern&lt;/li&gt;
&lt;li&gt;Retry mechanism&lt;/li&gt;
&lt;li&gt;Dead Letter Queue (DLQ)&lt;/li&gt;
&lt;li&gt;Multiple workers&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;1-what-is-priorityqueue-in-c-&quot;&gt;1. What is PriorityQueue in C#?&lt;/h2&gt;
&lt;h3 id=&quot;definition&quot;&gt;Definition&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;PriorityQueue&amp;lt;TElement,TPriority&amp;gt;&lt;/code&gt; is a built-in .NET collection that:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Stores elements so that the item with the &lt;strong&gt;highest priority is dequeued first&lt;/strong&gt;, not the one inserted first.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Official docs:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.priorityqueue-2?view=net-10.0&quot;&gt;https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.priorityqueue-2?view=net-10.0&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-how-it-works-in-this-system-&quot;&gt;⚙️ How it works (in this system)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;private readonly PriorityQueue&amp;lt;Job, int&amp;gt; _queue = new();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Jobs are added like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;_queue.Enqueue(job, job.Priority);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each entry has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Element&lt;/strong&gt; → &lt;code&gt;Job&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Priority&lt;/strong&gt; → &lt;code&gt;int&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;priority-rules-in-net&quot;&gt;Priority rules in .NET&lt;/h3&gt;
&lt;p&gt; Lower number = higher priority&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Priority &lt;/th&gt;&lt;th&gt;Meaning          &lt;/th&gt;&lt;th&gt;Execution Order&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1        &lt;/td&gt;&lt;td&gt;Highest priority &lt;/td&gt;&lt;td&gt;First&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2        &lt;/td&gt;&lt;td&gt;Medium priority  &lt;/td&gt;&lt;td&gt;Next&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5        &lt;/td&gt;&lt;td&gt;Low priority     &lt;/td&gt;&lt;td&gt;Last&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;simple-example&quot;&gt;Simple example&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var queue = new PriorityQueue&amp;lt;string, int&amp;gt;();

queue.Enqueue(&quot;Job A&quot;, 5);
queue.Enqueue(&quot;Job B&quot;, 1);
queue.Enqueue(&quot;Job C&quot;, 3);

Console.WriteLine(queue.Dequeue()); // Job B
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-important-limitation&quot;&gt;⚠️ Important limitation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;❌ PriorityQueue is NOT thread-safe&lt;/li&gt;
&lt;li&gt;✔ This system fixes that using &lt;code&gt;lock&lt;/code&gt; + &lt;code&gt;SemaphoreSlim&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;2-job-model&quot;&gt;2. Job Model&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public record Job(
    string Name,
    int Priority,
    int RetryCount = 0,
    int MaxRetries = 3);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;why-this-works-well&quot;&gt;Why this works well&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Immutable (&lt;code&gt;record&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Safe for multithreading&lt;/li&gt;
&lt;li&gt;Supports retry tracking&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;3-blocking-priority-queue-core-engine-&quot;&gt;3. Blocking Priority Queue (Core Engine)&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class BlockingPriorityQueue
{
    private readonly PriorityQueue&amp;lt;Job, int&amp;gt; _queue = new();
    private readonly object _lock = new();
    private readonly SemaphoreSlim _signal = new(0);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-these-3-components-&quot;&gt;Why these 3 components?&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Component     &lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;PriorityQueue &lt;/td&gt;&lt;td&gt;Stores jobs by priority&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;lock          &lt;/td&gt;&lt;td&gt;Thread safety&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;SemaphoreSlim &lt;/td&gt;&lt;td&gt;Async waiting / signaling&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-enqueue-producer-side-&quot;&gt;➕ Enqueue (Producer side)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public void Enqueue(Job job)
{
    lock (_lock)
    {
        _queue.Enqueue(job, job.Priority);
        _signal.Release();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;flow&quot;&gt;Flow&lt;/h4&gt;
&lt;p&gt;Producer → lock → enqueue → signal workers&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-dequeue-consumer-side-&quot;&gt;➖ Dequeue (Consumer side)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public async Task&amp;lt;Job&amp;gt; DequeueAsync(CancellationToken cancellationToken = default)
{
    await _signal.WaitAsync(cancellationToken);

    lock (_lock)
    {
        return _queue.Dequeue();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;flow-1&quot;&gt;Flow&lt;/h4&gt;
&lt;p&gt;Worker waits → signal → lock → dequeue highest priority job&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;4-dead-letter-queue-dlq-&quot;&gt;4. Dead Letter Queue (DLQ)&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class DeadLetterQueue
{
    private readonly List&amp;lt;Job&amp;gt; _failedJobs = new();

    public void Add(Job job, Exception ex)
    {
        Console.WriteLine($&quot;DLQ: Job {job.Name} failed permanently: {ex.Message}&quot;);
        _failedJobs.Add(job);
    }

    public IReadOnlyList&amp;lt;Job&amp;gt; GetAll() =&amp;gt; _failedJobs;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;purpose&quot;&gt;Purpose&lt;/h4&gt;
&lt;p&gt;DLQ stores jobs that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;exceeded retry limit&lt;/li&gt;
&lt;li&gt;failed permanently&lt;/li&gt;
&lt;li&gt;require manual inspection&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;5-job-processor-worker-system-&quot;&gt;5. Job Processor (Worker System)&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class JobProcessor(BlockingPriorityQueue jobQueue, DeadLetterQueue deadLetterQueue)
{
    private readonly BlockingPriorityQueue _jobQueue = jobQueue;
    private readonly DeadLetterQueue _deadLetterQueue = deadLetterQueue;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;main-processing-loop&quot;&gt;Main processing loop&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public async Task ProcessJobsAsync(CancellationToken cancellationToken = default)
{
    while (!cancellationToken.IsCancellationRequested)
    {
        try
        {
            var job = await _jobQueue.DequeueAsync(cancellationToken);

            await ProcessJob(job, cancellationToken);
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine(&quot;Job processing cancelled.&quot;);
            break;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;job-execution-logic&quot;&gt;Job execution logic&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;private async Task ProcessJob(Job job, CancellationToken ct)
{
    try
    {
        Console.WriteLine($&quot;Processing {job.Name}&quot;);

        await Task.Delay(500, ct);

        if (Random.Shared.Next(0, 3) == 0)
            throw new Exception(&quot;Random failure&quot;);

        Console.WriteLine($&quot;✅ Success: {job.Name}&quot;);
    }
    catch (Exception ex)
    {
        await HandleFailure(job, ex);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;6-retry-logic&quot;&gt;6. Retry Logic&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;private async Task HandleFailure(Job job, Exception ex)
{
    if (job.RetryCount &amp;lt; job.MaxRetries)
    {
        var retryJob = job with
        {
            RetryCount = job.RetryCount + 1
        };

        Console.WriteLine($&quot;Retrying {job.Name} ({retryJob.RetryCount})&quot;);

        await Task.Delay(1000);

        _jobQueue.Enqueue(retryJob);
    }
    else
    {
        _deadLetterQueue.Add(job, ex);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;behavior-flow&quot;&gt;Behavior flow&lt;/h3&gt;
&lt;p&gt;Failure → retry → requeue → success OR DLQ&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;7-job-producer&quot;&gt;7. Job Producer&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class JobProducer(BlockingPriorityQueue jobQueue, DeadLetterQueue deadLetterQueue)
{
    private readonly BlockingPriorityQueue _jobQueue = jobQueue;
    private readonly DeadLetterQueue _deadLetterQueue = deadLetterQueue;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;publish-single-job&quot;&gt;Publish single job&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public void PublishJob(string name, int priority)
{
    var job = new Job(name, priority);

    Console.WriteLine($&quot;Publishing {job.Name} with priority {job.Priority}&quot;);

    _jobQueue.Enqueue(job);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;publish-multiple-jobs&quot;&gt;Publish multiple jobs&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public void PublishJobs(IEnumerable&amp;lt;Job&amp;gt; jobs)
{
    foreach (var job in jobs)
    {
        Console.WriteLine($&quot;Publishing {job.Name} with priority {job.Priority}&quot;);
        _jobQueue.Enqueue(job);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;manual-dlq-injection&quot;&gt;Manual DLQ injection&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public void PublishDeadLetterJob(string name, int priority)
{
    var job = new Job(name, priority);

    Console.WriteLine($&quot;Publishing {job.Name} to DLQ&quot;);

    _deadLetterQueue.Add(job, new Exception(&quot;Manually added&quot;));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;8-system-execution&quot;&gt;8. System Execution&lt;/h2&gt;
&lt;h3 id=&quot;setup&quot;&gt;Setup&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var queue = new BlockingPriorityQueue();
var dlq = new DeadLetterQueue();
var jobProducer = new JobProducer(queue, dlq);
var cts = new CancellationTokenSource();
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;start-workers&quot;&gt;Start workers&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var job1 = new JobProcessor(queue, dlq);
var job2 = new JobProcessor(queue, dlq);

var workerTasks = new[]
{
    Task.Run(() =&amp;gt; job1.ProcessJobsAsync(cts.Token)),
    Task.Run(() =&amp;gt; job2.ProcessJobsAsync(cts.Token))
};
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;publish-jobs&quot;&gt;Publish jobs&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;for (int i = 0; i &amp;lt; 10; i++)
{
    var priority = Random.Shared.Next(1, 5);

    Console.WriteLine($&quot;\nPublishing Job - {i} with priority {priority}&quot;);

    jobProducer.PublishJob($&quot;Job - {i}&quot;, priority);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;shutdown-system&quot;&gt;Shutdown system&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;await Task.Delay(3000);

cts.Cancel();

await Task.WhenAll(workerTasks);

Console.WriteLine(&quot;System stopped cleanly.&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;9-runtime-behavior&quot;&gt;9. Runtime behavior&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Jobs are published with random priority&lt;/li&gt;
&lt;li&gt;Stored in PriorityQueue&lt;/li&gt;
&lt;li&gt;Workers wait asynchronously&lt;/li&gt;
&lt;li&gt;Highest priority job executes first&lt;/li&gt;
&lt;li&gt;Random failures occur&lt;/li&gt;
&lt;li&gt;Failed jobs retry if allowed&lt;/li&gt;
&lt;li&gt;Permanent failures go to DLQ&lt;/li&gt;
&lt;li&gt;System shuts down gracefully&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&quot;10-key-concepts-learned&quot;&gt;10. Key Concepts Learned&lt;/h2&gt;
&lt;h3 id=&quot;-priorityqueue&quot;&gt;✔ PriorityQueue&lt;/h3&gt;
&lt;p&gt;Built-in heap structure for priority-based scheduling&lt;/p&gt;
&lt;h3 id=&quot;-thread-safety&quot;&gt;✔ Thread Safety&lt;/h3&gt;
&lt;p&gt;Handled using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;lock&lt;/li&gt;
&lt;li&gt;SemaphoreSlim&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;-producer-consumer&quot;&gt;✔ Producer / Consumer&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Producer → JobProducer&lt;/li&gt;
&lt;li&gt;Consumer → JobProcessor&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;-retry-system&quot;&gt;✔ Retry System&lt;/h3&gt;
&lt;p&gt;Automatic retry with counter tracking&lt;/p&gt;
&lt;h3 id=&quot;-dead-letter-queue&quot;&gt;✔ Dead Letter Queue&lt;/h3&gt;
&lt;p&gt;Stores failed jobs permanently&lt;/p&gt;
&lt;h3 id=&quot;-multithreading&quot;&gt;✔ Multithreading&lt;/h3&gt;
&lt;p&gt;Multiple workers process jobs concurrently&lt;/p&gt;
&lt;h3 id=&quot;-graceful-shutdown&quot;&gt;✔ Graceful Shutdown&lt;/h3&gt;
&lt;p&gt;Uses CancellationTokenSource&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Real-world style system that includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Priority scheduling&lt;/li&gt;
&lt;li&gt;Concurrent workers&lt;/li&gt;
&lt;li&gt;Async processing&lt;/li&gt;
&lt;li&gt;Retry logic&lt;/li&gt;
&lt;li&gt;Failure isolation (DLQ)&lt;/li&gt;
&lt;li&gt;Thread-safe queue coordination&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Full Implementation &lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public record Job(string Name,int Priority,int RetryCount = 0, int MaxRetries = 3);
/// &amp;lt;summary&amp;gt;
/// 
/// &amp;lt;/summary&amp;gt;
public class BlockingPriorityQueue
{
    /// &amp;lt;summary&amp;gt;
    /// 
    /// &amp;lt;/summary&amp;gt;
    private readonly PriorityQueue&amp;lt;Job, int&amp;gt; _queue = new();
    /// &amp;lt;summary&amp;gt;
    /// 
    /// &amp;lt;/summary&amp;gt;
    private readonly object _lock = new();
    /// &amp;lt;summary&amp;gt;
    /// 
    /// &amp;lt;/summary&amp;gt;
    private readonly SemaphoreSlim _signal = new(0);

    public void Enqueue(Job job)
    {
        lock (_lock)
        {
            _queue.Enqueue(job, job.Priority);

            _signal.Release();
        }
    }

    public async Task&amp;lt;Job&amp;gt; DequeueAsync(CancellationToken cancellationToken = default) { 
    
        await _signal.WaitAsync(cancellationToken);
       
        lock (_lock)
        {
            return _queue.Dequeue();
        }
    }
}


public class DeadLetterQueue
{
    private readonly List&amp;lt;Job&amp;gt; _failedJobs = new();

    public void Add(Job job, Exception ex)
    {
        Console.WriteLine($&quot;DLQ: Job {job.Name} failed permanently: {ex.Message}&quot;);
        _failedJobs.Add(job);
    }

    public IReadOnlyList&amp;lt;Job&amp;gt; GetAll() =&amp;gt; _failedJobs;
}


public class JobProcessor(BlockingPriorityQueue jobQueue, DeadLetterQueue deadLetterQueue)
{
    private readonly BlockingPriorityQueue _jobQueue = jobQueue;

    private readonly DeadLetterQueue _deadLetterQueue = deadLetterQueue;

    public async Task ProcessJobsAsync(CancellationToken cancellationToken = default)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            try
            {
               var job = await _jobQueue.DequeueAsync(cancellationToken);
                if (job != null) 
                { 
                    await ProcessJob(job, cancellationToken);
                }
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine(&quot;Job processing cancelled.&quot;);
                break;
            }
        }
    }

    private async Task ProcessJob(Job job, CancellationToken ct)
    {

        try
        {
            Console.WriteLine($&quot;Processing {job.Name}&quot;);

            // Simulate work
            // In a real implementation, this would be where the actual job logic goes
            // For demonstration, we just delay for a short time
            // http Call .. database operation .. etc
            await Task.Delay(500, ct);

            // Simulate random failure
            if (Random.Shared.Next(0, 3) == 0)
                throw new Exception(&quot;Random failure&quot;);

            Console.WriteLine($&quot;✅ Success: {job.Name}&quot;);
        }
        catch (Exception ex)
        {
            await HandleFailure(job, ex);
        }
    }

    private async Task HandleFailure(Job job, Exception ex)
    {
        if (job.RetryCount &amp;lt; job.MaxRetries)
        {
            var retryJob = job with { RetryCount = job.RetryCount + 1 };

            Console.WriteLine($&quot;Retrying {job.Name} ({retryJob.RetryCount})&quot;);

            await Task.Delay(1000);

            _jobQueue.Enqueue(retryJob);
        }
        else
        {
            _deadLetterQueue.Add(job, ex);
        }
    }


    public class JobProducer(BlockingPriorityQueue jobQueue, DeadLetterQueue deadLetterQueue)
    {
        /// &amp;lt;summary&amp;gt;
        /// Thread-safe priority queue for managing jobs.   
        /// &amp;lt;/summary&amp;gt;
        /// &amp;lt;remarks&amp;gt;Enables prioritized job processing in concurrent scenarios.&amp;lt;/remarks&amp;gt;
        private readonly BlockingPriorityQueue _jobQueue = jobQueue;
        /// &amp;lt;summary&amp;gt;
        /// 
        /// &amp;lt;/summary&amp;gt;
        private readonly DeadLetterQueue _deadLetterQueue = deadLetterQueue;
        /// &amp;lt;summary&amp;gt;
        /// Publishes a collection of jobs to the job queue.
        /// &amp;lt;/summary&amp;gt;
        /// &amp;lt;param name=&quot;jobs&quot;&amp;gt;The jobs to be published.&amp;lt;/param&amp;gt;
        public void PublishJobs(IEnumerable&amp;lt;Job&amp;gt; jobs)
        {
            foreach (var job in jobs)
            {
                Console.WriteLine($&quot;Publishing {job.Name} with priority {job.Priority}&quot;);

                _jobQueue.Enqueue(job);
            }
        }

        public void PublishJob(string name, int priority)
        {
            var job = new Job(name, priority);

            Console.WriteLine($&quot;Publishing {job.Name} with priority {job.Priority}&quot;);

            _jobQueue.Enqueue(job);
        }

        public void PublishDeadLetterJob(string name, int priority)
        {
            var job = new Job(name, priority);
            Console.WriteLine($&quot;Publishing {job.Name} to DLQ with priority {job.Priority}&quot;);
            _deadLetterQueue.Add(job, new Exception(&quot;Manually added to DLQ&quot;));
        }
    }
}

var queue = new BlockingPriorityQueue();
var dlq = new DeadLetterQueue();
var jobProducer = new JobProducer(queue, dlq);
var cts = new CancellationTokenSource();

try
{
    var job1 = new JobProcessor(queue, dlq);
    var job2 = new JobProcessor(queue, dlq);

    var workerTasks = new[]
    {
        Task.Run(() =&amp;gt; job1.ProcessJobsAsync(cts.Token)),
        Task.Run(() =&amp;gt; job2.ProcessJobsAsync(cts.Token))
    };

    // Publish jobs
    for (int i = 0; i &amp;lt; 10; i++)
    {
        var priority = Random.Shared.Next(1, 5);

        Console.BackgroundColor = ConsoleColor.Green;
        Console.WriteLine(Environment.NewLine + $&quot;Publishing Job - {i} with priority {priority}&quot;);
        Console.WriteLine($&quot;Publishing Job - {i} with priority {priority}&quot;);
        Console.ResetColor();   
        jobProducer.PublishJob($&quot;Job - {i}&quot;, priority);
    }

    await Task.Delay(3000);

    cts.Cancel();

    await Task.WhenAll(workerTasks);

    Console.WriteLine(&quot;System stopped cleanly.&quot;);
}
catch (OperationCanceledException)
{
    Console.WriteLine(&quot;Processing cancelled.&quot;);
}
catch (Exception ex)
{
    Console.WriteLine($&quot;Error: {ex.Message}&quot;);
}

Console.ReadKey();

&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18967/priority-based-job-processing-system-in-c</guid>
<pubDate>Tue, 26 May 2026 08:51:21 +0000</pubDate>
</item>
<item>
<title>Abstract Factory Pattern Tutorial</title>
<link>https://coderlegion.com/17410/abstract-factory-pattern-tutorial</link>
<description>&lt;h2 id=&quot;abstract-factory-pattern-in-c-step-by-step-tutorial-&quot;&gt;Abstract Factory Pattern in C# – Step-by-Step Tutorial.&lt;/h2&gt;
&lt;p&gt;In this tutorial, I’ll explain how I implemented the &lt;strong&gt;Abstract Factory Pattern&lt;/strong&gt; in C# using my project:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/AbstractFactoryDemo&quot;&gt;https://github.com/stevsharp/AbstractFactoryDemo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This project demonstrates how to support multiple database providers like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SQL Server&lt;/li&gt;
&lt;li&gt;MySQL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each database needs related objects such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Database Connection&lt;/li&gt;
&lt;li&gt;Database Command&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of hardcoding these objects directly, we use the &lt;strong&gt;Abstract Factory Pattern&lt;/strong&gt; to make the code cleaner and scalable.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-1-the-problem&quot;&gt;Step 1: The Problem&lt;/h2&gt;
&lt;p&gt;Imagine your application supports multiple databases.&lt;/p&gt;
&lt;p&gt;Without using Abstract Factory, your code may look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;if(databaseType == &quot;SQL&quot;)
{
    var connection = new SqlConnection();
    var command = new SqlCommand();
}
else
{
    var connection = new MySqlConnection();
    var command = new MySqlCommand();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;why-this-is-bad-&quot;&gt;Why this is bad:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Too many &lt;code&gt;if/else&lt;/code&gt; statements&lt;/li&gt;
&lt;li&gt;Hard to maintain&lt;/li&gt;
&lt;li&gt;Difficult to scale&lt;/li&gt;
&lt;li&gt;Tight coupling with concrete classes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We need a better approach.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-2-create-abstract-products&quot;&gt;Step 2: Create Abstract Products&lt;/h2&gt;
&lt;p&gt;These interfaces define common behavior for all database implementations.&lt;/p&gt;
&lt;h3 id=&quot;idbconnection-cs&quot;&gt;IDbConnection.cs&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public interface IDbConnection
{
    void Connect();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;idbcommand-cs&quot;&gt;IDbCommand.cs&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public interface IDbCommand
{
    void Execute();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now every database provider must implement these interfaces.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-3-create-sql-server-products&quot;&gt;Step 3: Create SQL Server Products&lt;/h2&gt;
&lt;h3 id=&quot;sqlconnection-cs&quot;&gt;SqlConnection.cs&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class SqlConnection : IDbConnection
{
    public void Connect()
    {
        Console.WriteLine(&quot;Connecting to SQL Server...&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;sqlcommand-cs&quot;&gt;SqlCommand.cs&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class SqlCommand : IDbCommand
{
    public void Execute()
    {
        Console.WriteLine(&quot;Executing SQL command...&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These classes handle SQL Server operations.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-4-create-mysql-products&quot;&gt;Step 4: Create MySQL Products&lt;/h2&gt;
&lt;h3 id=&quot;mysqlconnection-cs&quot;&gt;MySqlConnection.cs&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class MySqlConnection : IDbConnection
{
    public void Connect()
    {
        Console.WriteLine(&quot;Connecting to MySQL...&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;mysqlcommand-cs&quot;&gt;MySqlCommand.cs&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class MySqlCommand : IDbCommand
{
    public void Execute()
    {
        Console.WriteLine(&quot;Executing MySQL command...&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These classes handle MySQL operations.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-5-create-the-abstract-factory&quot;&gt;Step 5: Create the Abstract Factory&lt;/h2&gt;
&lt;p&gt;Now we create a factory interface.&lt;/p&gt;
&lt;h3 id=&quot;idatabasefactory-cs&quot;&gt;IDatabaseFactory.cs&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public interface IDatabaseFactory
{
    IDbConnection CreateConnection();
    IDbCommand CreateCommand();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This ensures every factory creates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Connection objects&lt;/li&gt;
&lt;li&gt;Command objects&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-6-create-sql-server-factory&quot;&gt;Step 6: Create SQL Server Factory&lt;/h2&gt;
&lt;h3 id=&quot;sqlserverfactory-cs&quot;&gt;SqlServerFactory.cs&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class SqlServerFactory : IDatabaseFactory
{
    public IDbConnection CreateConnection()
    {
        return new SqlConnection();
    }

    public IDbCommand CreateCommand()
    {
        return new SqlCommand();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This factory creates SQL-related objects.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-7-create-mysql-factory&quot;&gt;Step 7: Create MySQL Factory&lt;/h2&gt;
&lt;h3 id=&quot;mysqlfactory-cs&quot;&gt;MySqlFactory.cs&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class MySqlFactory : IDatabaseFactory
{
    public IDbConnection CreateConnection()
    {
        return new MySqlConnection();
    }

    public IDbCommand CreateCommand()
    {
        return new MySqlCommand();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This factory creates MySQL-related objects.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-8-use-the-factory-in-client-code&quot;&gt;Step 8: Use the Factory in Client Code&lt;/h2&gt;
&lt;p&gt;Now the client works with the factory instead of concrete classes.&lt;/p&gt;
&lt;h3 id=&quot;program-cs&quot;&gt;Program.cs&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;class Program
{
    static void Main()
    {
        IDatabaseFactory factory = new MySqlFactory();

        var connection = factory.CreateConnection();
        var command = factory.CreateCommand();

        connection.Connect();
        command.Execute();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;output&quot;&gt;Output&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Connecting to MySQL...
Executing MySQL command...
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-9-why-this-pattern-is-useful&quot;&gt;Step 9: Why This Pattern Is Useful&lt;/h2&gt;
&lt;p&gt;Let’s say your application needs to support PostgreSQL in the future.&lt;/p&gt;
&lt;p&gt;You would simply add:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PostgresConnection&lt;/li&gt;
&lt;li&gt;PostgresCommand&lt;/li&gt;
&lt;li&gt;PostgresFactory&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And your client code stays exactly the same.&lt;/p&gt;
&lt;p&gt;That makes your code:&lt;/p&gt;
&lt;p&gt;✅ Flexible&lt;br&gt;
✅ Maintainable&lt;br&gt;
✅ Scalable&lt;br&gt;
✅ Easy to test&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;visual-flow&quot;&gt;Visual Flow&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Client
   ↓
IDatabaseFactory
   ↓
--------------------------------
↓                              ↓
SqlServerFactory         MySqlFactory
↓                              ↓
SqlConnection            MySqlConnection
SqlCommand               MySqlCommand
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;real-interview-explanation&quot;&gt;Real Interview Explanation&lt;/h2&gt;
&lt;p&gt;Here’s how you can explain this in an interview:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;In this project, I used the Abstract Factory Pattern to create families of related database objects. Each database provider creates its own connection and command objects. This reduces tight coupling and makes the application easier to extend when adding new databases.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;when-should-you-use-abstract-factory-&quot;&gt;When Should You Use Abstract Factory?&lt;/h2&gt;
&lt;p&gt;Use Abstract Factory when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You need to create families of related objects&lt;/li&gt;
&lt;li&gt;You want loose coupling&lt;/li&gt;
&lt;li&gt;Your system supports multiple implementations&lt;/li&gt;
&lt;li&gt;You want easier scalability&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Database providers&lt;/li&gt;
&lt;li&gt;UI themes&lt;/li&gt;
&lt;li&gt;Payment gateways&lt;/li&gt;
&lt;li&gt;Cloud providers&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;Abstract Factory is a great design pattern when your application needs flexibility.&lt;/p&gt;
&lt;p&gt;Instead of hardcoding object creation logic everywhere, you centralize it through factories and keep your code clean.&lt;/p&gt;
&lt;p&gt;If you want to see the full implementation, check out the repository:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/AbstractFactoryDemo&quot;&gt;https://github.com/stevsharp/AbstractFactoryDemo&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;reference&quot;&gt;Reference&lt;/h4&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://refactoring.guru/design-patterns/abstract-factory&quot;&gt;https://refactoring.guru/design-patterns/abstract-factory&lt;/a&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/17410/abstract-factory-pattern-tutorial</guid>
<pubDate>Mon, 11 May 2026 08:15:04 +0000</pubDate>
</item>
<item>
<title># The Rule Pattern in C#, and Why It Works So Well with the Result Pattern</title>
<link>https://coderlegion.com/14143/the-rule-pattern-in-c-and-why-it-works-so-well-with-the-result-pattern</link>
<description>&lt;h2 id=&quot;the-rule-pattern-in-c-and-why-it-works-so-well-with-the-result-pattern&quot;&gt;The Rule Pattern in C#, and Why It Works So Well with the Result Pattern&lt;/h2&gt;
&lt;p&gt;Business rules rarely stay in one place.&lt;/p&gt;
&lt;p&gt;A small validation starts in a command handler. Another check appears in a service. A third gets added somewhere in the domain. A few weeks later, the real business policy is scattered across the application, and nobody can see it clearly anymore.&lt;/p&gt;
&lt;p&gt;That is exactly where the Rule pattern becomes useful.&lt;/p&gt;
&lt;p&gt;At its core, the idea is simple. Instead of expressing business decisions as anonymous conditionals spread throughout the system, you give each rule a name and wrap it in its own object. Martin Fowler describes this style through the Specification pattern, where the statement of what satisfies a candidate is separated from the candidate itself, and those specifications can be combined into richer logic. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://martinfowler.com/apsupp/spec.pdf?utm_source=chatgpt.com&quot; title=&quot;Specifications&quot;&gt;martinfowler.com&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;In practical C# applications, especially the ones using CQRS or domain-driven design, that gives you something powerful: business rules that are explicit, reusable, and easy to reason about. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://martinfowler.com/apsupp/spec.pdf?utm_source=chatgpt.com&quot; title=&quot;Specifications&quot;&gt;martinfowler.com&lt;/a&gt;)&lt;/p&gt;
&lt;h3 id=&quot;what-the-rule-pattern-really-solves&quot;&gt;What the Rule pattern really solves&lt;/h3&gt;
&lt;p&gt;The biggest problem is not that business checks exist. The problem is that they often exist in the wrong shape.&lt;/p&gt;
&lt;p&gt;When rules are buried inside handlers, services, or controller actions, they are hard to spot, hard to test in isolation, and very easy to duplicate. The code still works, but the business language disappears.&lt;/p&gt;
&lt;p&gt;The Rule pattern fixes that by turning each business condition into a named concept. Instead of asking developers to interpret a chain of low-level checks, the code starts speaking in terms the business would actually recognize. That is very much in line with DDD’s emphasis on modeling around the real language of the domain. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/ddd-oriented-microservice?utm_source=chatgpt.com&quot; title=&quot;Designing a DDD-oriented microservice - .NET&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;In other words, the pattern is not just about validation. It is about clarity.&lt;/p&gt;
&lt;h3 id=&quot;why-it-is-often-called-the-specification-pattern&quot;&gt;Why it is often called the Specification pattern&lt;/h3&gt;
&lt;p&gt;If the shape feels familiar, that is because it usually is. What many teams call a Rule pattern is very close to what Fowler documented as Specification. The core idea is to represent business criteria as composable objects rather than raw conditionals, and then combine them with logical operations like AND, OR, and NOT. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://martinfowler.com/apsupp/spec.pdf?utm_source=chatgpt.com&quot; title=&quot;Specifications&quot;&gt;martinfowler.com&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;That is why this pattern tends to feel so natural in real systems. Business rules are rarely isolated. More often, they are layered:&lt;/p&gt;
&lt;p&gt;A customer must be verified.&lt;br&gt;
An order must reach a minimum amount.&lt;br&gt;
A credit limit must not be exceeded.&lt;/p&gt;
&lt;p&gt;On their own, each rule is simple. The real value appears when they can be combined into a meaningful policy.&lt;/p&gt;
&lt;h3 id=&quot;why-this-pattern-becomes-more-valuable-as-the-system-grows&quot;&gt;Why this pattern becomes more valuable as the system grows&lt;/h3&gt;
&lt;p&gt;At first, a few inline checks seem harmless. But as the application grows, the cost of hidden business logic grows with it.&lt;/p&gt;
&lt;p&gt;Rules get copied into multiple workflows.&lt;br&gt;
One endpoint enforces them, another forgets.&lt;br&gt;
A handler applies three checks, a background job applies only two.&lt;br&gt;
Tests start focusing on branches instead of policies.&lt;/p&gt;
&lt;p&gt;When rules become standalone concepts, those problems start to shrink. You can test a rule directly. You can reuse it in multiple flows. You can combine it with other rules. Most importantly, you can read the application flow and understand what it is trying to protect.&lt;/p&gt;
&lt;p&gt;That is one of the reasons this pattern fits so well in systems that care about maintainability.&lt;/p&gt;
&lt;h3 id=&quot;where-the-result-pattern-fits&quot;&gt;Where the Result pattern fits&lt;/h3&gt;
&lt;p&gt;The Rule pattern answers one question:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is this action allowed according to the business?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The Result pattern answers another:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What happened when we tried to perform it?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That separation is the reason the two patterns fit together so well.&lt;/p&gt;
&lt;p&gt;The rule evaluates business truth.&lt;br&gt;
The result communicates application outcome.&lt;/p&gt;
&lt;p&gt;Vladimir Khorikov’s writing on Result-based error handling makes this distinction especially useful for expected failures. Instead of throwing exceptions for normal business rejections, you can return an explicit failure outcome and keep the flow predictable. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://enterprisecraftsmanship.com/posts/error-handling-exception-or-result/?utm_source=chatgpt.com&quot; title=&quot;Error handling: Exception or Result?&quot;&gt;Enterprise Craftsmanship&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;That gives you a very clean mental model:&lt;/p&gt;
&lt;p&gt;The domain decides.&lt;br&gt;
The application reports.&lt;/p&gt;
&lt;h3 id=&quot;why-this-pairing-feels-so-clean-in-practice&quot;&gt;Why this pairing feels so clean in practice&lt;/h3&gt;
&lt;p&gt;A good rule object should not care about HTTP, API responses, logging, UI wording, or transport concerns. Its responsibility is much smaller. It answers whether a business condition holds.&lt;/p&gt;
&lt;p&gt;The application layer then takes that answer and decides what to do next. If the rule fails, the application can return a failure result. If it passes, the use case continues.&lt;/p&gt;
&lt;p&gt;This separation lines up well with Microsoft’s DDD guidance, which treats validations as invariants and emphasizes that aggregates are responsible for enforcing consistency across state changes. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/domain-model-layer-validations?utm_source=chatgpt.com&quot; title=&quot;Designing validations in the domain model layer - .NET&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;That is why the combination works so well. The rule keeps business logic clean. The result keeps flow control clean.&lt;/p&gt;
&lt;h3 id=&quot;a-simple-order-example-without-the-code&quot;&gt;A simple order example, without the code&lt;/h3&gt;
&lt;p&gt;Imagine an order placement use case with three policies:&lt;/p&gt;
&lt;p&gt;The order must meet a minimum amount.&lt;br&gt;
The customer must be verified.&lt;br&gt;
The order must stay within the customer’s credit limit.&lt;/p&gt;
&lt;p&gt;Without a rule-based approach, those checks often end up inline inside the command handler. That works, but the handler slowly becomes the place where business policy lives.&lt;/p&gt;
&lt;p&gt;With a rule-based approach, those policies become named rules. The handler simply coordinates the workflow: load data, build the order, evaluate the rule set, return a success or failure result, and persist when everything is valid.&lt;/p&gt;
&lt;p&gt;That is a much healthier separation of concerns.&lt;/p&gt;
&lt;p&gt;The handler stops being the place where business logic is invented, and becomes the place where business logic is orchestrated.&lt;/p&gt;
&lt;h3 id=&quot;the-biggest-benefit-is-not-flexibility-it-is-readability&quot;&gt;The biggest benefit is not flexibility, it is readability&lt;/h3&gt;
&lt;p&gt;People often talk about this pattern in terms of reuse or composability, and those are real benefits.&lt;/p&gt;
&lt;p&gt;But the biggest benefit is readability.&lt;/p&gt;
&lt;p&gt;When rules are named well, the code starts reading like a business conversation instead of a technical negotiation. That matters more than people think. Clear code reduces mistakes, speeds up maintenance, and makes onboarding easier for everyone who touches the system later.&lt;/p&gt;
&lt;p&gt;A long chain of conditionals tells you how the system behaves.&lt;/p&gt;
&lt;p&gt;A well-named rule tells you why.&lt;/p&gt;
&lt;h3 id=&quot;one-important-design-note&quot;&gt;One important design note&lt;/h3&gt;
&lt;p&gt;There is one trap worth mentioning.&lt;/p&gt;
&lt;p&gt;Just because a rule is expressed cleanly does not automatically mean it belongs in the application layer.&lt;/p&gt;
&lt;p&gt;Some rules are just use-case checks. Those can live comfortably near the workflow.&lt;/p&gt;
&lt;p&gt;But some rules are true invariants of the domain. Those should usually be protected by the aggregate itself. Microsoft’s guidance is very clear on this point: aggregates are responsible for enforcing invariants across state changes. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/domain-model-layer-validations?utm_source=chatgpt.com&quot; title=&quot;Designing validations in the domain model layer - .NET&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;So the right question is not only, “Can I model this as a rule?”&lt;/p&gt;
&lt;p&gt;The better question is, “Who is responsible for protecting this rule?”&lt;/p&gt;
&lt;p&gt;If the answer is “the domain itself,” then the aggregate should not rely on the handler to remember it.&lt;/p&gt;
&lt;h3 id=&quot;where-this-pattern-can-go-wrong&quot;&gt;Where this pattern can go wrong&lt;/h3&gt;
&lt;p&gt;Like most good patterns, this one becomes dangerous when overused.&lt;/p&gt;
&lt;p&gt;Not every &lt;code&gt;if&lt;/code&gt; statement needs to become a rule object.&lt;br&gt;
Not every workflow needs an entire rules engine.&lt;br&gt;
Not every tiny validation deserves its own abstraction.&lt;/p&gt;
&lt;p&gt;If the business logic is trivial and local, introducing rule classes may create more ceremony than value.&lt;/p&gt;
&lt;p&gt;The pattern shines when the rules are meaningful, reusable, and important enough to deserve a name. If the condition is just a one-off technical check, a plain conditional may still be the better choice.&lt;/p&gt;
&lt;p&gt;The goal is not abstraction for its own sake. The goal is to make business intent easier to see.&lt;/p&gt;
&lt;h3 id=&quot;why-the-result-pattern-keeps-the-design-honest&quot;&gt;Why the Result pattern keeps the design honest&lt;/h3&gt;
&lt;p&gt;The Result pattern adds a second layer of clarity.&lt;/p&gt;
&lt;p&gt;A failure is no longer hidden behind a thrown exception for something the system fully expected. Instead, the caller gets an explicit outcome. That makes business rejections feel normal rather than exceptional, which is often the right model for application workflows. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://enterprisecraftsmanship.com/posts/error-handling-exception-or-result/?utm_source=chatgpt.com&quot; title=&quot;Error handling: Exception or Result?&quot;&gt;Enterprise Craftsmanship&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;When paired with rules, the flow becomes very natural:&lt;/p&gt;
&lt;p&gt;Evaluate the business policy.&lt;br&gt;
If it fails, return a failure result.&lt;br&gt;
If it passes, continue the operation.&lt;/p&gt;
&lt;p&gt;That is simple, predictable, and easy to test.&lt;/p&gt;
&lt;h3 id=&quot;final-thought&quot;&gt;Final thought&lt;/h3&gt;
&lt;p&gt;The Rule pattern is one of those ideas that looks modest at first, but pays off more and more as the codebase grows.&lt;/p&gt;
&lt;p&gt;It helps you name business policies.&lt;br&gt;
It helps you reuse them.&lt;br&gt;
It helps you combine them.&lt;br&gt;
And when paired with the Result pattern, it gives your application a very clean way to both enforce rules and communicate outcomes. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://martinfowler.com/apsupp/spec.pdf?utm_source=chatgpt.com&quot; title=&quot;Specifications&quot;&gt;martinfowler.com&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;If your handlers are starting to fill up with business conditionals, this pattern is often a strong sign that the code wants a better language.&lt;/p&gt;
&lt;p&gt;And that, more than anything else, is what good domain design is really about.&lt;/p&gt;
&lt;p&gt;Source code :&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/RulePatternApplication&quot;&gt;https://github.com/stevsharp/RulePatternApplication&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;
&lt;p&gt;Martin Fowler, &lt;em&gt;Specifications&lt;/em&gt;. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://martinfowler.com/apsupp/spec.pdf?utm_source=chatgpt.com&quot; title=&quot;Specifications&quot;&gt;martinfowler.com&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Microsoft Learn, &lt;em&gt;Designing validations in the domain model layer&lt;/em&gt;. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/domain-model-layer-validations?utm_source=chatgpt.com&quot; title=&quot;Designing validations in the domain model layer - .NET&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Microsoft Learn, &lt;em&gt;Designing a DDD-oriented microservice&lt;/em&gt;. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/ddd-oriented-microservice?utm_source=chatgpt.com&quot; title=&quot;Designing a DDD-oriented microservice - .NET&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Vladimir Khorikov, &lt;em&gt;Error handling: Exception or Result?&lt;/em&gt; (&lt;a rel=&quot;nofollow&quot; href=&quot;https://enterprisecraftsmanship.com/posts/error-handling-exception-or-result/?utm_source=chatgpt.com&quot; title=&quot;Error handling: Exception or Result?&quot;&gt;Enterprise Craftsmanship&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Vladimir Khorikov, &lt;em&gt;Advanced error handling techniques&lt;/em&gt;. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://enterprisecraftsmanship.com/posts/advanced-error-handling-techniques/?utm_source=chatgpt.com&quot; title=&quot;Advanced error handling techniques&quot;&gt;Enterprise Craftsmanship&lt;/a&gt;)&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14143/the-rule-pattern-in-c-and-why-it-works-so-well-with-the-result-pattern</guid>
<pubDate>Tue, 07 Apr 2026 10:01:22 +0000</pubDate>
</item>
<item>
<title>std::visit is awesome, here’s why</title>
<link>https://coderlegion.com/11855/std-visit-is-awesome-heres-why</link>
<description>&lt;p&gt;One of the very few features of the C++ standard library that I unapologetically like, is the &lt;code&gt;std::visit&lt;/code&gt;. It reduces the amount of boilerplate code, kills multiple birds with a single stone, and doesn’t sacrifice performance while doing it. So let’s have a look at how to use it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;visitor-pattern&quot;&gt;Visitor pattern&lt;/h2&gt;
&lt;p&gt;The Visitor pattern is a more complex OOP design pattern, but once you understand it, it becomes a great tool for breaking up dependencies and removing obtuse conditional statements with RTTI.&lt;/p&gt;
&lt;p&gt;It allows you to separate data structures from algorithms operating on those data structures, making it easier to add new algorithms or alter the structures without having to redeploy the other half.&lt;/p&gt;
&lt;p&gt;It is popular in compilers, where your source code is represented by an abstract syntax tree (AST), and every single optimization or analysis, like constant propagation, expression folding, or precondition analysis is implemented as its own class (so-called Visitor), separately from the AST declaration.&lt;/p&gt;
&lt;p&gt;If you’d like to know how to implement Visitor from the ground up, look no further than &lt;a rel=&quot;nofollow&quot; href=&quot;https://refactoring.guru/design-patterns/visitor&quot;&gt;superb refactoring.guru website&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;without-inheritance&quot;&gt;Without inheritance&lt;/h3&gt;
&lt;p&gt;Unlike the standard object-oriented implementation of the Visitor pattern, &lt;code&gt;std::visit&lt;/code&gt; doesn’t require you to use inheritance. Due to the underlying use of &lt;code&gt;std::variant&lt;/code&gt; and dark standard library magic, &lt;code&gt;std::visit&lt;/code&gt; doesn’t use double dispatch and thus you don’t need any accept method on your data structures:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;variant&amp;gt;
#include &amp;lt;vector&amp;gt;

struct NodeA {};
struct NodeB {};
struct NodeC {};

using Nodes = std::variant&amp;lt;NodeA, NodeB, NodeC&amp;gt;;

struct Visitor {
    void operator() (const NodeA&amp;amp;) { std::cout &amp;lt;&amp;lt; &quot;NodeA&quot; &amp;lt;&amp;lt; std::endl; }
    void operator() (const NodeB&amp;amp;) { std::cout &amp;lt;&amp;lt; &quot;NodeB&quot; &amp;lt;&amp;lt; std::endl; }
    void operator() (const NodeC&amp;amp;) { std::cout &amp;lt;&amp;lt; &quot;NodeC&quot; &amp;lt;&amp;lt; std::endl; }
};

int main() {
    std::vector&amp;lt;Nodes&amp;gt; nodes = {
        NodeA{},
        NodeB{},
        NodeA{},
        NodeC{}
    };

    for (auto&amp;amp;&amp;amp; node : nodes) std::visit(Visitor{}, node);

    // Prints:
    // NodeA
    // NodeB
    // NodeA
    // NodeC
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, all you need is a callable struct with overloads for required types. Note that you need to overload on all types that belong to &lt;code&gt;Nodes&lt;/code&gt; or you’ll get a compiler error (like with pattern matching in other languages).&lt;/p&gt;
&lt;p&gt;The error itself is a classic &lt;code&gt;std&lt;/code&gt; nonsense, so look out for this message:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;no type named 'type' in 'std::invoke_result&amp;lt;Visitor, TypeThatIsMissing&amp;amp;&amp;gt;'
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;with-inheritance&quot;&gt;With inheritance&lt;/h3&gt;
&lt;p&gt;However, &lt;code&gt;std::visit&lt;/code&gt; allows using inheritance. That is even darker magic because you can, but don’t have to, add overloads for only some of the inherited types. The algorithm needs to be able to match the type on at least one overload, and the base class also applies:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;variant&amp;gt;
#include &amp;lt;vector&amp;gt;

struct NodeBase {};
struct NodeA : public NodeBase{};
struct NodeB : public NodeBase{};

using Nodes = std::variant&amp;lt;NodeA, NodeB&amp;gt;;

struct Visitor {
    void operator() (const NodeA&amp;amp;) { std::cout &amp;lt;&amp;lt; &quot;NodeA&quot; &amp;lt;&amp;lt; std::endl; }
    void operator() (const NodeBase&amp;amp;) { std::cout &amp;lt;&amp;lt; &quot;NodeBase&quot; &amp;lt;&amp;lt; std::endl; }
};

int main()
{
    std::vector&amp;lt;Nodes&amp;gt; nodes = {
        NodeA{},
        NodeB{},
    };

    for (auto&amp;amp;&amp;amp; node : nodes) std::visit(Visitor{}, node);

    // Prints:
    // NodeA
    // NodeBase
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The order of overloads doesn’t matter here. Specialization is preferred over the base class.&lt;/p&gt;
&lt;h3 id=&quot;faster-than-manual-dispatch&quot;&gt;Faster than manual dispatch&lt;/h3&gt;
&lt;p&gt;While &lt;code&gt;std::visit&lt;/code&gt; surely has a nice interface (a rare sight in the standard library) and it requires less boilerplate than implementing double dispatch manually, how’s the performance? I do have an unexpected answer - it is faster or at least as fast as the other solutions!&lt;/p&gt;
&lt;p&gt;I highly recommend watching Klaus Ingleberger’s talk about the Visitor pattern implementations where he also shows comparison times on &lt;a rel=&quot;nofollow&quot; href=&quot;https://youtu.be/PEcy1vYHb8A?t=2048&quot;&gt;GCC and Clang&lt;/a&gt; (timestamped to the part with the performance comparison). My own benchmarks on MSVC way back showed a clear win for &lt;code&gt;std::visit&lt;/code&gt; over other methods.&lt;/p&gt;
&lt;h2 id=&quot;functional-programming&quot;&gt;Functional programming&lt;/h2&gt;
&lt;p&gt;C++ committee often comments on how they like their proposals to kill multiple birds with one stone and in the notion of that mindset, you can use &lt;code&gt;std::visit&lt;/code&gt; functionally as well.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;variant&amp;gt;
#include &amp;lt;vector&amp;gt;

struct NodeA {};
struct NodeB {};

using Nodes = std::variant&amp;lt;NodeA, NodeB&amp;gt;;

template&amp;lt;class... Ts&amp;gt;
struct overloaded : Ts... { using Ts::operator()...; };

// Some compilers might require this explicit deduction guide
template&amp;lt;class... Ts&amp;gt;
overloaded(Ts...) -&amp;gt; overloaded&amp;lt;Ts...&amp;gt;;

int main()
{
    std::vector&amp;lt;Nodes&amp;gt; nodes = {
        NodeA{},
        NodeB{},
    };

    for (auto&amp;amp;&amp;amp; node : nodes) {
        std::visit(overloaded{
            [] (const NodeA&amp;amp;) { std::cout &amp;lt;&amp;lt; &quot;NodeA&quot; &amp;lt;&amp;lt; std::endl; },
            [] (const NodeB&amp;amp;) { std::cout &amp;lt;&amp;lt; &quot;NodeB&quot; &amp;lt;&amp;lt; std::endl; }
        }, node);
    }

    // Prints:
    // NodeA
    // NodeB
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of creating one dedicated visitor struct, you can use a helper template class to create an object out of a set of lambdas. This way, you can define your visiting code in the same place as you call it. Some people use this trick to emulate match statements from other languages, others might like it simply because it is functional programming.&lt;/p&gt;
&lt;h2 id=&quot;disadvantages&quot;&gt;Disadvantages&lt;/h2&gt;
&lt;p&gt;As noted by Klaus in his talk, every approach has its advantages and disadvantages. Because this approach is driven by the &lt;code&gt;std::variant&lt;/code&gt;, you must keep in mind its shortcomings:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Types need to be fully defined before they can be used in the variant. This is because their storage size needs to be known.&lt;/li&gt;
&lt;li&gt;Using types with vastly different sizes means that your variant will inherently waste a lot of space.&lt;/li&gt;
&lt;li&gt;Adding a new type might mean updating all existing visitors (not if you use inheritance) and updating the variant (which you don’t have to do with the classic OO approach).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Do note that you can address the first two problems by using wrapper types like smart pointers or employing the Pimpl pattern.&lt;/p&gt;
&lt;h2 id=&quot;summary-1&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;As I said in the beginning, I like &lt;code&gt;std::visit&lt;/code&gt; very much. Implementing the Visitor pattern takes some getting used to and with this feature, you greatly reduce the amount of boilerplate, improve the readability of the code, and don’t sacrifice performance.&lt;/p&gt;
&lt;p&gt;Functional invocation is just a cherry on top for very localized usage where brand new class would just add noise to the code base.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/11855/std-visit-is-awesome-heres-why</guid>
<pubDate>Tue, 17 Feb 2026 22:55:13 +0000</pubDate>
</item>
<item>
<title>Learning the Single Responsibility Principle, One Test at a Time</title>
<link>https://coderlegion.com/5022/learning-the-single-responsibility-principle-one-test-at-a-time</link>
<description>&lt;h4 id=&quot;the-problem-we-re-solving&quot;&gt;The Problem We're Solving&lt;/h4&gt;
&lt;p&gt;Ever come across a &lt;code&gt;utils.ts&lt;/code&gt; file that's more of a &lt;code&gt;everything-but-the-kitchen-sink.ts&lt;/code&gt; file? We've all been there. It starts innocently enough with a small helper function, but over time, it becomes a dumping ground. Let's imagine a function called &lt;code&gt;processUserData&lt;/code&gt; that handles everything for user registration. It validates the user's data, saves it to the database, and then sends a welcome email.&lt;/p&gt;
&lt;p&gt;While this might seem efficient, it's a nightmare to test. How do you write a test for this one function? You'd have to mock the database to prevent it from saving actual data and mock the email service to stop it from sending real emails. It’s like trying to test if a car's horn works, but you're forced to test the engine, the transmission, and the radio at the same time. This complexity is a huge red flag.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-tdd-mindset-write-a-failing-test-first&quot;&gt;The TDD Mindset: Write a Failing Test First&lt;/h4&gt;
&lt;p&gt;This is where Test-Driven Development (TDD) comes in. The core idea is simple: before you write any code, you write a test for it. The test should fail initially because the code it's testing doesn't exist yet. This forces you to think about what you want your function to do in a very specific way.&lt;/p&gt;
&lt;p&gt;Let's try to write a test for our &lt;code&gt;processUserData&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// processUserData.test.ts

import { processUserData } from './utils';
import { saveUserToDatabase } from './database';
import { sendWelcomeEmail } from './emailService';

// Mocking the external functions so they don't actually run
jest.mock('./database');
jest.mock('./emailService');

describe('processUserData', () =&amp;gt; {
  it('should process user data, save to the database, and send a welcome email', () =&amp;gt; {
    const userData = { name: 'Alice', email: '*Emails are not allowed*' };

    // This is the part that gets messy
    const result = processUserData(userData);
    expect(result).toBe(true);
    expect(saveUserToDatabase).toHaveBeenCalledWith(userData);
    expect(sendWelcomeEmail).toHaveBeenCalledWith(userData.email);
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This test is already complicated. It's trying to verify three different things at once: that the function returns a value, that it calls a database function, and that it calls an email function. The test itself is a signal that our function is doing too much. The test is screaming at us to break the function apart.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-aha-moment-my-own-real-world-example&quot;&gt;The &quot;Aha!&quot; Moment: My Own Real-World Example&lt;/h4&gt;
&lt;p&gt;Let me share my own experience with this. I was working on a project with a massive &lt;code&gt;utils.ts&lt;/code&gt; file. I wanted to test just one simple helper function within it, but I discovered it was impossible to do so in isolation. The function I wanted to test was using other functions from the same file, and because they were all in one big file, I couldn't mock them.&lt;/p&gt;
&lt;p&gt;I couldn't write a clean, simple test. The test kept getting bloated and complicated, and I found myself in a testing dead end. This frustration was my &quot;aha!&quot; moment. I realized that my inability to mock the other functions was a clear sign that the design was flawed. The functions were too tightly coupled and doing more than they should. The test was telling me: &quot;You need to separate these things.&quot;&lt;/p&gt;
&lt;p&gt;The solution was to split the file. I moved each function into its own file or into a file with a very specific, related group of functions. By doing this, I made the unrelated functions easy to mock, and suddenly, my tests were simple, clear, and isolated. &lt;strong&gt;I had applied the Single Responsibility Principle without even knowing its name.&lt;/strong&gt; I didn't do it to follow a rule; I did it to make my life easier and make testing possible.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;principles-as-solutions-not-starting-points&quot;&gt;Principles as Solutions, Not Starting Points&lt;/h4&gt;
&lt;p&gt;This is a key point I want you to remember. Software principles and patterns are not rules we blindly follow just because they exist. They were created to solve problems developers faced. They are solutions that emerged from real-world pain.&lt;/p&gt;
&lt;p&gt;You don't need to memorize a list of principles before you start coding. Instead, it's good to be aware of them. When you run into a problem—like a test that's too difficult to write, or a function that's getting too long—you might realize that the problem is a classic one. By searching for a solution, you'll likely find one of these principles, and it will be easy to understand because you've already experienced the pain it's designed to fix.&lt;/p&gt;
&lt;p&gt;So, don't worry about being an expert on every rule. Instead, focus on two simple goals: &lt;strong&gt;make your code easy to understand and easy to test.&lt;/strong&gt; Those two goals will naturally guide you toward good design principles, just like they did for me.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;refactor-and-separate-responsibilities&quot;&gt;Refactor and Separate Responsibilities&lt;/h4&gt;
&lt;p&gt;The revelation is this: a difficult-to-write test is a sign that your code's design is flawed. The solution is to separate the concerns. Let's break down our massive function into three smaller, more focused functions.&lt;/p&gt;
&lt;p&gt;Here is what our new, refactored &lt;code&gt;utils.ts&lt;/code&gt; file looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// new utils.ts

export const isValidUserData = (data: any): boolean =&amp;gt; {
  if (!data || !data.email) {
    return false;
  }
  // A simple email validation check
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email);
};

export const saveUserToDatabase = async (data: any): Promise&amp;lt;boolean&amp;gt; =&amp;gt; {
  console.log(`Saving user ${data.email} to the database...`);
  // Pretend this function interacts with a real database
  return true;
};

export const sendWelcomeEmail = async (email: string): Promise&amp;lt;boolean&amp;gt; =&amp;gt; {
  console.log(`Sending welcome email to ${email}...`);
  // Pretend this function sends a real email
  return true;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, look at how easy it is to test each of these functions individually.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// isValidUserData.test.ts
import { isValidUserData } from './utils';

describe('isValidUserData', () =&amp;gt; {
  it('should return true for a valid email', () =&amp;gt; {
    expect(isValidUserData({ email: '*Emails are not allowed*' })).toBe(true);
  });

  it('should return false for an invalid email', () =&amp;gt; {
    expect(isValidUserData({ email: 'invalid-email' })).toBe(false);
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is so much simpler! We don't need any complex mocking. The tests are focused and clear, and they only care about one specific piece of functionality. By making our code easy to test, we have naturally applied a key programming principle: &lt;strong&gt;each function should do one thing and do it well&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-orchestrator-pattern&quot;&gt;The Orchestrator Pattern&lt;/h4&gt;
&lt;p&gt;Now that we have our small, focused functions, we can create a new function that coordinates them. We'll call this the &quot;orchestrator&quot; function. It doesn't do any of the heavy lifting itself; it simply calls the other functions in the correct order.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// orchestrator.ts

import { isValidUserData, saveUserToDatabase, sendWelcomeEmail } from './utils';

export const processUserData = async (data: any): Promise&amp;lt;boolean&amp;gt; =&amp;gt; {
  if (!isValidUserData(data)) {
    console.log('Invalid user data provided.');
    return false;
  }

  await saveUserToDatabase(data);
  await sendWelcomeEmail(data.email);

  return true;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is much cleaner. Our &lt;code&gt;processUserData&lt;/code&gt; function is now a manager that delegates tasks to specialists. It asks &lt;code&gt;isValidUserData&lt;/code&gt; to validate, tells &lt;code&gt;saveUserToDatabase&lt;/code&gt; to save, and instructs &lt;code&gt;sendWelcomeEmail&lt;/code&gt; to send the email. This pattern makes the overall process much easier to understand, debug, and maintain.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;finding-the-balance-when-to-stop-and-why&quot;&gt;Finding the Balance: When to Stop and Why&lt;/h4&gt;
&lt;p&gt;A word of caution: this doesn't mean you should break down every single line of code into its own function. Over-engineering can lead to a confusing project structure with hundreds of tiny files. The true measure of a good design is its &lt;strong&gt;ease of testing&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Golden Rule:&lt;/strong&gt; If a function or component is still easy to test and read, it likely doesn't need to be broken down further.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The &quot;Why&quot;:&lt;/strong&gt; The goal isn't to create more files; it's to create code that is more maintainable, readable, and flexible. When you need to fix a bug or add a new feature, you should only have to make changes in one place. If you can change the email format without touching the database saving logic, you've found a good balance.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;summary-2&quot;&gt;Summary&lt;/h4&gt;
&lt;p&gt;Congratulations! You've just applied a core software design principle. You started with a messy function that was difficult to test, used a failing test to signal a design flaw, and then refactored the code into smaller, more manageable pieces.&lt;/p&gt;
&lt;p&gt;You've discovered that TDD is more than just a testing technique; it's a powerful guide that naturally pushes you toward better, more focused designs. By focusing on writing easy-to-test code, you will find that your projects become easier to maintain, understand, and grow. Keep this mindset, and you'll be building more robust and flexible applications in no time.&lt;/p&gt;
</description>
<category>Java</category>
<guid isPermaLink="true">https://coderlegion.com/5022/learning-the-single-responsibility-principle-one-test-at-a-time</guid>
<pubDate>Tue, 26 Aug 2025 13:57:56 +0000</pubDate>
</item>
<item>
<title>Data-Driven Design: Leveraging Lessons from Game Development in Everyday Software</title>
<link>https://coderlegion.com/3459/data-driven-design-leveraging-lessons-from-game-development-in-everyday-software</link>
<description>&lt;h3 id=&quot;overview&quot;&gt;Overview&lt;/h3&gt;
&lt;p&gt;Modern software often needs to adapt quickly - whether that means processing new data sets, adjusting to user preferences, or deploying new features safely without downtime. To achieve such flexibility, software engineers increasingly adopt a methodology known as &lt;strong&gt;Data-Driven Design (DDD)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Originally popularized by game development, Data-Driven Design emerged prominently in the 1990s as large studios confronted a challenging problem: the need to iterate rapidly on complex and interactive content. Game developers realized it was costly and slow to rebuild and redeploy an entire game every time designers wanted to tweak gameplay mechanics, adjust character behaviors, or revise in-game dialogues.&lt;/p&gt;
&lt;p&gt;Jason Gregory's influential book &lt;em&gt;Game Engine Architecture&lt;/em&gt; highlighted how AAA games effectively tackled this challenge by externalizing game logic into structured &lt;strong&gt;data files&lt;/strong&gt;. Instead of embedding behaviors directly in C++ code, developers loaded data such as AI rules, game levels, item descriptions, and story dialogues from easily editable files like YAML, JSON, or custom formats. This dramatically accelerated iteration, empowering non-programmers - artists, designers, and writers - to directly experiment and refine experiences without requiring code recompilation or redeployment.&lt;/p&gt;
&lt;p&gt;Although originally rooted in game development, Data-Driven Design has proven invaluable across software domains, ranging from web development, data analytics, and DevOps automation to no-code and low-code platforms. The fundamental principle remains the same: separate &lt;strong&gt;generic engines&lt;/strong&gt; from &lt;strong&gt;domain-specific data&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-core-idea-behind-data-driven-design&quot;&gt;The Core Idea Behind Data-Driven Design&lt;/h3&gt;
&lt;p&gt;Before we dive deeper, let's provide a clear working definition that illustrates why Data-Driven Design is relevant to developers and system administrators today:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Data-Driven Design&lt;/strong&gt; means that the software's behavior is governed by external data rather than hard-coded logic. The code provides generic mechanisms for processing that data, but the specifics of “what to do” or “how to behave” live in data files (such as YAML, JSON, or databases) that can be changed independently from the source code itself.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To clarify further, here's a quick comparison:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;strong&gt;Pattern&lt;/strong&gt;       &lt;/th&gt;&lt;th&gt;&lt;strong&gt;What drives behavior?&lt;/strong&gt;                                        &lt;/th&gt;&lt;th&gt;&lt;strong&gt;Practical Example&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Hard-coded&lt;/strong&gt;    &lt;/td&gt;&lt;td&gt;Embedded conditional statements in code                          &lt;/td&gt;&lt;td&gt;&lt;code&gt;if user.is_premium: enable_feature()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Config-driven&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Simple flags or settings in config files                         &lt;/td&gt;&lt;td&gt;&lt;code&gt;max_connections = 10&lt;/code&gt; in &lt;code&gt;config.ini&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Data-driven&lt;/strong&gt;   &lt;/td&gt;&lt;td&gt;Entire behaviors defined in structured data (YAML, JSON, SQLite) &lt;/td&gt;&lt;td&gt;YAML defining a workflow or SQLite storing business rules&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Below are some common misconceptions and anti-patterns to help clarity things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&quot;It's just a config file.&quot;&lt;/strong&gt; – If removing the file breaks the program, it's &lt;em&gt;not&lt;/em&gt; mere config; it's &lt;em&gt;content&lt;/em&gt; that &lt;em&gt;defines&lt;/em&gt; runtime behaviour.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&quot;DDD = No code.&quot;&lt;/strong&gt; – Wrong: the goal is to keep the &lt;em&gt;engine&lt;/em&gt; generic and thin, but domain logic still has to live &lt;em&gt;somewhere&lt;/em&gt; (often in a domain-specific language interpreted by the engine).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Premature complexity&lt;/strong&gt; –  Don't invent a custom DSL when a handful of YAML documents plus a small interpreter class will do.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Debugging blind&lt;/strong&gt; –  Always log &quot;which data row caused this action?&quot; so you can trace bugs quickly.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;high-level-architecture-overview&quot;&gt;High-Level Architecture Overview&lt;/h3&gt;
&lt;p&gt;A typical Data-Driven Design architecture clearly separates:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Authoring Layer&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Human-readable formats like YAML or JSON.&lt;/li&gt;
&lt;li&gt;Editable directly by users or via automated processes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Validation &amp;amp; Build Layer&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Schema definitions or migrations that ensure data consistency.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Runtime Loader &amp;amp; Hot Reload&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reads and validates data at runtime.&lt;/li&gt;
&lt;li&gt;Supports hot-reload for rapid iterations.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Generic Runtime System&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Executes logic based purely on loaded data.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This creates a robust pipeline where data edits alone trigger different application behaviors.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Data Files (YAML/SQLite)
    │ load &amp;amp; validate
    ▼
Generic Runtime Engine
    │ executes behaviors based on data
    ▼
Dynamic Behavior in Application
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;choosing-your-data-container&quot;&gt;Choosing Your Data Container&lt;/h3&gt;
&lt;p&gt;Here's a quick guide to choosing your data format based on needs:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;strong&gt;Format&lt;/strong&gt;         &lt;/th&gt;&lt;th&gt;&lt;strong&gt;Advantages&lt;/strong&gt;                               &lt;/th&gt;&lt;th&gt;&lt;strong&gt;Typical Use Case&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;YAML/JSON/TOML&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Human-readable, simple to version control    &lt;/td&gt;&lt;td&gt;Small-to-medium complexity workflows, configs&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;SQLite&lt;/strong&gt;         &lt;/td&gt;&lt;td&gt;Relational queries, consistency, single file &lt;/td&gt;&lt;td&gt;Complex rule sets, relational data, analytics&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Binary Formats&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Performance-critical loading                 &lt;/td&gt;&lt;td&gt;Embedded systems, high-performance scenarios&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Typically, you start with YAML or JSON, then upgrade to SQLite as complexity grows.&lt;/p&gt;
&lt;h3 id=&quot;practical-example-data-driven-workflows-with-divooka&quot;&gt;Practical Example: Data-Driven Workflows with Divooka&lt;/h3&gt;
&lt;p&gt;Divooka is the visual programming language that naturally embodies Data-Driven Design principles through its composable dataflow nodes. Let's illustrate this clearly with a straightforward example:&lt;/p&gt;
&lt;h4 id=&quot;scenario-&quot;&gt;Scenario:&lt;/h4&gt;
&lt;p&gt;A data analyst wants to build an automated workflow to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Load a CSV file containing customer orders.&lt;/li&gt;
&lt;li&gt;Filter orders exceeding a certain threshold.&lt;/li&gt;
&lt;li&gt;Send a summary via a web API.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In Divooka, the process is streamlined as follows:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Load Data with Path and File Nodes&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Path&lt;/strong&gt; node specifies file location (e.g., &lt;code&gt;/data/orders.csv&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Load from CSV&lt;/strong&gt; file read node directly loads CSV data into a structured &lt;strong&gt;DataGrid&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;[Path Node] &quot;/data/orders.csv&quot;
    │
    └─► [Load from CSV Node]
           │
           └─► DataGrid
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Equivalent Python-like pseudocode:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;orders_df = pd.read_csv(&quot;/data/orders.csv&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. Filtering Data&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Filter&lt;/strong&gt; node takes the DataGrid and filters based on condition (e.g., &lt;code&gt;order_total &amp;gt; 100&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;DataGrid
    │
    └─► [Filter Node] condition: order_total &amp;gt; 100
           │
           └─► Filtered DataGrid
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Equivalent pseudocode:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;high_value_orders = orders_df[orders_df[&quot;order_total&quot;] &amp;gt; 100]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. Sending Data via HTTP API&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;String&lt;/strong&gt; node specifies the webhook URI.&lt;/li&gt;
&lt;li&gt;HTTP &lt;strong&gt;Send Request&lt;/strong&gt; node sends the filtered data automatically serialized as JSON.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;[String Node] &quot;https://hooks.example.com/orders&quot;
    │
    └─► [Send Request Node] method: POST, body: Filtered DataGrid
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Equivalent pseudocode:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import requests
payload = high_value_orders.to_dict(orient='records')
requests.post(&quot;https://hooks.example.com/orders&quot;, json=payload)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;why-divooka-is-naturally-data-driven-&quot;&gt;Why Divooka Is Naturally Data-Driven:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;High-level abstraction nodes&lt;/strong&gt; (Path, String, File I/O, Web Request) handle data transparently.&lt;/li&gt;
&lt;li&gt;Nodes clearly define dependencies and flow, automatically adjusting behavior when input data changes.&lt;/li&gt;
&lt;li&gt;Entire workflows can be modified without touching underlying engine code or restarting the runtime environment.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;best-practices-and-pitfalls&quot;&gt;Best Practices and Pitfalls&lt;/h3&gt;
&lt;p&gt;When embracing Data-Driven Design, keep the following in mind:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;strong&gt;Practice&lt;/strong&gt;                 &lt;/th&gt;&lt;th&gt;&lt;strong&gt;Recommendation&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Schema Validation&lt;/strong&gt;        &lt;/td&gt;&lt;td&gt;Always enforce schemas (e.g., JSON Schema or SQLite migrations) to prevent errors&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Hot Reloading&lt;/strong&gt;            &lt;/td&gt;&lt;td&gt;Clearly separate parsing from data activation steps&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Security Considerations&lt;/strong&gt;  &lt;/td&gt;&lt;td&gt;Never trust external data implicitly; always validate or sanitize it before use&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Performance Optimization&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Cache loaded data where appropriate to avoid redundant parsing&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;try-it-yourself-starter-projects&quot;&gt;Try It Yourself: Starter Projects&lt;/h3&gt;
&lt;p&gt;To practice Data-Driven Design, consider these beginner-friendly projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Feature Flag Dashboard&lt;/strong&gt;: YAML-based flags control feature visibility.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Email Routing Automation&lt;/strong&gt;: SQLite stores routing rules to sort incoming emails.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Procedural Content Generator&lt;/strong&gt;: YAML files configure parameters for generated data outputs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each project reinforces that your engine stays generic and data explicitly drives all behaviors.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Data-Driven Design originated as a powerful solution to rapid iteration in the game development world, enabling large teams to build and adjust complex software efficiently. Today, its principles transcend industries, benefiting system administrators, data analysts, web developers, and more.&lt;/p&gt;
&lt;p&gt;In tools like Divooka, Data-Driven Design emerges naturally - dataflow-based nodes inherently separate behavior from implementation, ensuring flexibility and robustness. By clearly distinguishing between generic program logic and externally-driven data, you create maintainable, adaptable, and resilient software.&lt;/p&gt;
&lt;h3 id=&quot;references-1&quot;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Wikipedia on &lt;a rel=&quot;nofollow&quot; href=&quot;https://en.wikipedia.org/wiki/Data-driven_programming&quot;&gt;Data-driven programming&lt;/a&gt; - not to be confused with &lt;a rel=&quot;nofollow&quot; href=&quot;https://en.wikipedia.org/wiki/Data-oriented_design&quot;&gt;Data-oriented design&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Jason Gregory's book on &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.gameenginebook.com/toc.html&quot;&gt;Game Engine Architecture (table of contents)&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Richard Fabian's book on &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.dataorienteddesign.com/dodbook/node2.html&quot;&gt;Data-Oriented Design (online version)&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Knowledge base on &lt;a rel=&quot;nofollow&quot; href=&quot;https://wiki.methodox.io/KnowledgeBase/SQL&quot;&gt;SQL&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://wiki.methodox.io/KnowledgeBase/SQLite&quot;&gt;SQLite&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Divooka</category>
<guid isPermaLink="true">https://coderlegion.com/3459/data-driven-design-leveraging-lessons-from-game-development-in-everyday-software</guid>
<pubDate>Tue, 24 Jun 2025 22:18:04 +0000</pubDate>
</item>
<item>
<title>5 tips to know before building a design system</title>
<link>https://coderlegion.com/3433/5-tips-to-know-before-building-a-design-system</link>
<description>&lt;p&gt;Creating a design system is a big step toward scaling design and development efficiently. Here are &lt;strong&gt;5 key tips&lt;/strong&gt; to know before you start building one:&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;1-define-the-purpose-and-scope-first&quot;&gt;&lt;strong&gt;1. Define the Purpose and Scope First&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Before jumping into components or tokens, &lt;strong&gt;clarify why you're building a design system&lt;/strong&gt;. Are you solving consistency issues? Speeding up dev cycles? Supporting multiple products?&lt;br&gt;
&lt;strong&gt;Define:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Primary users (designers, devs, PMs)&lt;/li&gt;
&lt;li&gt;Supported platforms (web, mobile, etc.)&lt;/li&gt;
&lt;li&gt;Short-term and long-term goals&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;2-build-a-strong-foundation-with-design-tokens&quot;&gt;&lt;strong&gt;2. Build a Strong Foundation with Design Tokens&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Design tokens (colors, typography, spacing, etc.) are the &lt;strong&gt;core of your system's consistency&lt;/strong&gt;. Start by establishing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A naming convention (e.g., &lt;code&gt;color-primary&lt;/code&gt;, &lt;code&gt;spacing-sm&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Token hierarchy (global vs. component-specific)&lt;/li&gt;
&lt;li&gt;How tokens will be consumed by code (via CSS variables, JSON, etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;3-start-small-and-scale&quot;&gt;&lt;strong&gt;3. Start Small and Scale&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Don’t try to build everything at once. Focus on &lt;strong&gt;core components&lt;/strong&gt; first—buttons, inputs, modals—then expand.&lt;br&gt;
Use real product needs to drive what gets added. Avoid &quot;just in case&quot; components.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;4-collaborate-early-with-developers&quot;&gt;&lt;strong&gt;4. Collaborate Early with Developers&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;A successful design system is &lt;strong&gt;not just a design asset&lt;/strong&gt;—it's a &lt;strong&gt;living product used by teams&lt;/strong&gt;. Involve engineers from the start to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Align on tech stack (e.g., React, Vue, Web Components)&lt;/li&gt;
&lt;li&gt;Ensure components are accessible, performant, and maintainable&lt;/li&gt;
&lt;li&gt;Avoid a “design-only” system no one uses&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;5-document-everything-clearly&quot;&gt;&lt;strong&gt;5. Document Everything Clearly&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Adoption depends on &lt;strong&gt;clear, centralized documentation&lt;/strong&gt;. Include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Guidelines (how and when to use components)&lt;/li&gt;
&lt;li&gt;Code snippets or package install instructions&lt;/li&gt;
&lt;li&gt;Do/Don’t examples&lt;/li&gt;
&lt;li&gt;Contribution and update processes&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;If you are using Angular, you can keep project Structure like this. &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;src/
├── app/
│   ├── core/        # Singleton services, guards, interceptors
│   ├── shared/      # Shared components, pipes, directives
│   ├── features/    # Feature modules (lazy-loaded)
│   └── design-system/  # Your component library (if building one)
├── assets/          # Images, icons, styles
├── environments/    # Env-specific settings
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;you-can-create-a-component-library-of-your-design-system-&quot;&gt;You can create a component library of your design system:&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;ng generate library ui-kit
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;for-documenting-ui-components-you-can-use-storybook-&quot;&gt;For documenting UI components you can use Storybook:&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npx sb init --type angular
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Web Dev</category>
<guid isPermaLink="true">https://coderlegion.com/3433/5-tips-to-know-before-building-a-design-system</guid>
<pubDate>Mon, 23 Jun 2025 18:44:44 +0000</pubDate>
</item>
<item>
<title>TechTalk 20250611: Factory Pattern - Separating Creation from Use</title>
<link>https://coderlegion.com/3322/techtalk-20250611-factory-pattern-separating-creation-from-use</link>
<description>&lt;p&gt;At its core, the &lt;strong&gt;Factory Pattern&lt;/strong&gt; centralizes object creation into dedicated methods or classes, instead of scattering &lt;code&gt;new&lt;/code&gt; calls or complex constructors throughout your codebase. This separation yields several benefits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clarity of Intent&lt;/strong&gt;&lt;br&gt;
A method named &lt;code&gt;CreateFromCsv()&lt;/code&gt; or &lt;code&gt;ConfigureNumberDisplay()&lt;/code&gt; immediately tells the reader &lt;em&gt;what&lt;/em&gt; kind of object will emerge, whereas a bare constructor &lt;code&gt;new TextDisplayConfiguration(…)&lt;/code&gt; may require you to look up parameter lists and defaults.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Flexible Return Types&lt;/strong&gt;&lt;br&gt;
Static factory methods can return any subtype or cached instance they choose. A constructor must always return a fresh instance of its own class.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simplified Overloads&lt;/strong&gt;&lt;br&gt;
Instead of dozens of overloaded constructors to cover different initialization scenarios, you provide a handful of well-named static methods (e.g. &lt;code&gt;WithTitle()&lt;/code&gt;, &lt;code&gt;WithSubtitle()&lt;/code&gt;), each encapsulating its own logic and defaults.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Validation and Caching&lt;/strong&gt;&lt;br&gt;
Factories can centralize validation (&lt;code&gt;throw&lt;/code&gt; early on bad arguments), perform expensive setup only once (returning a shared singleton), or even pool and reuse objects.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;static-factory-methods-vs-constructors&quot;&gt;Static Factory Methods vs Constructors&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Aspect                          &lt;/th&gt;&lt;th&gt;Constructors                 &lt;/th&gt;&lt;th&gt;Static Factory Methods&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Naming &amp;amp; Discoverability&lt;/strong&gt;    &lt;/td&gt;&lt;td&gt;No name beyond class name    &lt;/td&gt;&lt;td&gt;Method names describe purpose&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Return Flexibility&lt;/strong&gt;          &lt;/td&gt;&lt;td&gt;Fixed to class it belongs to &lt;/td&gt;&lt;td&gt;Can return subtypes or cached refs&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Overload Explosion&lt;/strong&gt;          &lt;/td&gt;&lt;td&gt;Many overloads → unreadable  &lt;/td&gt;&lt;td&gt;Distinct methods → self-documented&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Default Configuration Logic&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Scattered in callers         &lt;/td&gt;&lt;td&gt;Encapsulated in one place&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Conditional Instantiation&lt;/strong&gt;   &lt;/td&gt;&lt;td&gt;Hard to vary at runtime      &lt;/td&gt;&lt;td&gt;Easy to branch inside factory&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;divooka-s-use-case-organizing-tools-by-category&quot;&gt;Divooka's Use Case: Organizing Tools by Category&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mz2j271vz39xe3ochc4m.png&quot; alt=&quot;Functions Tray in Divooka - Functions Organized as Categories under Toolboxes&quot;&gt;&lt;/p&gt;
&lt;p&gt;In &lt;strong&gt;Divooka&lt;/strong&gt;, every static method in a class becomes a &lt;em&gt;toolbox&lt;/em&gt; entry - with the class name serving as the toolbox &lt;strong&gt;category&lt;/strong&gt;. By grouping related factory methods inside dedicated &lt;code&gt;static class&lt;/code&gt; containers (e.g. &lt;code&gt;PlotConfigurations&lt;/code&gt;), Divooka can automatically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Discover&lt;/strong&gt; all available tools at startup&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Organize&lt;/strong&gt; them under logical headings (one class = one category)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expose&lt;/strong&gt; named methods like &lt;code&gt;ConfigureTextDisplayWithTitleAndSubtitle&lt;/code&gt; directly to end-users&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Were these methods instead constructors on each configuration type, Divooka would lack a natural grouping: you'd have to scan every class for all its constructors, losing that simple &quot;category = class&quot; mapping based on usage.&lt;/p&gt;
&lt;h3 id=&quot;case-study-asp-net-core-s-builder-based-factory-approach&quot;&gt;Case Study: ASP .NET Core's Builder-Based Factory Approach&lt;/h3&gt;
&lt;p&gt;ASP .NET Core also relies heavily on factories - but often via the &lt;strong&gt;builder pattern&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;Host.CreateDefaultBuilder(args)
    .ConfigureServices((ctx, services) =&amp;gt;
    {
        services.AddDbContext&amp;lt;MyDbContext&amp;gt;(opts =&amp;gt; 
            opts.UseSqlServer(connectionString));
        services.AddSingleton&amp;lt;IMyService, MyService&amp;gt;();
    })
    .Build();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here the &quot;factory&quot; is implicit in the &lt;code&gt;IHostBuilder&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fluent configuration&lt;/strong&gt; methods (&lt;code&gt;ConfigureServices&lt;/code&gt;, &lt;code&gt;UseUrls&lt;/code&gt;, etc.) collect options&lt;/li&gt;
&lt;li&gt;The final &lt;code&gt;Build()&lt;/code&gt; call &lt;em&gt;manufactures&lt;/em&gt; the fully wired &lt;code&gt;IHost&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;You get immutability until build time, plus a clear sequence of configuration steps&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;comparing-static-vs-builder-factories&quot;&gt;Comparing Static vs. Builder Factories&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Feature             &lt;/th&gt;&lt;th&gt;Static Factory Methods              &lt;/th&gt;&lt;th&gt;Builder Pattern (ASP NET Core)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Discovery&lt;/strong&gt;       &lt;/td&gt;&lt;td&gt;Simple reflection on static classes &lt;/td&gt;&lt;td&gt;Extension-method based; discoverable by IDE but less obvious at runtime&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Grouping&lt;/strong&gt;        &lt;/td&gt;&lt;td&gt;One class = one category            &lt;/td&gt;&lt;td&gt;Spread across many fluent calls&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Extensibility&lt;/strong&gt;   &lt;/td&gt;&lt;td&gt;Add new methods in classes          &lt;/td&gt;&lt;td&gt;Add new extension methods on interfaces&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Configurability&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Encapsulated per-method logic       &lt;/td&gt;&lt;td&gt;Centralized in a mutable builder state&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Readability&lt;/strong&gt;     &lt;/td&gt;&lt;td&gt;Direct: &lt;code&gt;MyFactory.Create(…)&lt;/code&gt;       &lt;/td&gt;&lt;td&gt;Fluent chaining: &lt;code&gt;builder.Configure…&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In many small‐to‐medium scenarios, &lt;strong&gt;static factory methods&lt;/strong&gt; win on simplicity: you immediately see each creation scenario, and end-users can invoke exactly what they need. In very large frameworks (like ASP .NET Core), the &lt;strong&gt;builder&lt;/strong&gt; shines by aggregating dozens of configuration concerns into a single, coherent pipeline before creating the final product.&lt;/p&gt;
&lt;h3 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;The Factory Pattern - whether via simple static methods or a full builder API - decouples &lt;em&gt;what&lt;/em&gt; you need from &lt;em&gt;how&lt;/em&gt; it's built. Choosing static factory methods:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keeps creation logic in one readable place&lt;/li&gt;
&lt;li&gt;Leverages method naming to self-document different initialization paths&lt;/li&gt;
&lt;li&gt;Allows frameworks like Divooka to auto-discover and categorize tools&lt;/li&gt;
&lt;li&gt;Avoids constructor overload chaos&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Meanwhile, builder-style factories excel when you have large configuration graphs to assemble in a defined sequence. By understanding both approaches, you can pick the pattern that best balances clarity, flexibility, and discoverability in your own projects.&lt;/p&gt;
</description>
<category>Best Practices</category>
<guid isPermaLink="true">https://coderlegion.com/3322/techtalk-20250611-factory-pattern-separating-creation-from-use</guid>
<pubDate>Wed, 11 Jun 2025 13:28:15 +0000</pubDate>
</item>
</channel>
</rss>