<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged designpatterns</title>
<link>https://coderlegion.com/tag/designpatterns</link>
<description>Powered by Question2Answer</description>
<item>
<title>Security by Design in the Age of Artificial Intelligence: Fundamentals, Risks and Resilience Strateg</title>
<link>https://coderlegion.com/30793/security-design-the-age-artificial-intelligence-fundamentals-risks-resilience-strateg</link>
<description>&lt;p&gt;We live in a time when artificial intelligence has moved from being an emerging technology to a critical infrastructure. Algorithms decide what information we see, what transactions are suspicious, what diagnoses are likely, and increasingly, what decisions are optimal for organizations and governments. Companies like OpenAI, Google DeepMind, and Anthropic have accelerated the development of advanced models, making artificial intelligence a central element of the digital economy. In this context, security can no longer be an afterthought or a reactive mechanism. It must be an integral part of the system architecture from the moment of its conception. &lt;/p&gt;
&lt;p&gt;Over the past two decades, digital transformation has been accelerated by cloud, mobility, and big data. In recent years, however, artificial intelligence (AI) has become the main driver of innovation.&lt;br&gt;
But this revolution comes at a cost: the attack surface is growing exponentially.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Security by Design&lt;/strong&gt; is not just about installing additional controls or enforcing stricter access policies. It means deeply integrating security principles into the technological DNA of a system. In the AI era, this approach becomes vital, because the complexity of models, their dependence on massive data, and the often opaque nature of algorithms create an environment in which vulnerabilities can be subtle but devastating.&lt;/p&gt;
&lt;p&gt;The concept of &lt;strong&gt;Security by Design (SbD)&lt;/strong&gt; involves integrating security from the design phase of a system, not as a later stage or a “patch” after vulnerabilities appear. In the AI era, this approach becomes critical because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AI models are dependent on massive data.&lt;/li&gt;
&lt;li&gt;Algorithms can be manipulated.&lt;/li&gt;
&lt;li&gt;Automated decisions can have a major impact on users.&lt;/li&gt;
&lt;li&gt;AI systems can be exploited in new ways (prompt injection, model inversion, data poisoning).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Security by Design in the AI era means &lt;strong&gt;responsible design, technical resilience, and solid governance&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;from-traditional-security-to-algorithmic-security&quot;&gt;From traditional security to algorithmic security&lt;/h3&gt;
&lt;p&gt;The concept of Security by Design is not new. It has been promoted since the 1990s in the field of application security and IT infrastructure. It initially appeared in the field of software security and IT infrastructures, being supported and formalized by organizations such as the National Institute of Standards and Technology (NIST) that developed frameworks such as the NIST Cybersecurity Framework, which promotes the integration of security into all stages of the life cycle. The idea was simple: it is more efficient and safer to prevent vulnerabilities by design than to correct them after the system has been compromised.&lt;/p&gt;
&lt;p&gt;Classic principles include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Principle of least privilege&lt;/li&gt;
&lt;li&gt;Defense in depth&lt;/li&gt;
&lt;li&gt;Fail secure&lt;/li&gt;
&lt;li&gt;Zero Trust&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In traditional systems, security focused on perimeter protection, authentication, encryption and vulnerability management. In the case of artificial intelligence, however, the object of protection is no longer only the infrastructure, but also the model itself. The algorithm becomes a critical asset. Training data becomes an attack surface. Automated decisions can have major legal and ethical implications.&lt;/p&gt;
&lt;p&gt;Thus, security must be extended beyond servers and networks, to the algorithmic and epistemic level of the system, respectively, in the AI context, traditional principles must be extended to cover:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Training data security&lt;/li&gt;
&lt;li&gt;Model integrity&lt;/li&gt;
&lt;li&gt;Robustness against adversarial attacks&lt;/li&gt;
&lt;li&gt;Explicability and auditability&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;vulnerabilities-specific-to-the-ai-era&quot;&gt;Vulnerabilities specific to the AI era&lt;/h3&gt;
&lt;h4 id=&quot;data-poisoning&quot;&gt;Data poisoning&lt;/h4&gt;
&lt;p&gt;An AI system can be compromised not only through unauthorized access, but also through manipulation of the data on which it relies. Data poisoning attacks, in which malicious data is introduced into the training set, can subtly alter the behavior of the model. The result is not an obvious error, but a strategic degradation of performance or an intentional deviation of decisions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data poisoning&lt;/strong&gt; is a type of attack in which the adversary compromises the integrity of an artificial intelligence model by deliberately introducing malicious data into the training set. Unlike attacks that target the system after implementation, data poisoning acts “at the source”, affecting the learning process of the model and influencing its long-term behavior. The injected data can be designed either to degrade the overall performance (unavailability or decreased accuracy) or to create a “backdoor” so that the model reacts erroneously only in the presence of a specific pattern or trigger. For example, a spam detection system could be trained with manipulated messages so that certain malicious expressions are later considered legitimate. The vulnerability is amplified in scenarios where the data comes from open, collaborative or automatically collected sources, without rigorous validation. From a Security by Design perspective, preventing data poisoning involves strict verification of the provenance of the data, auditing of datasets, anomaly detection mechanisms and controlled separation of data streams used for training.&lt;/p&gt;
&lt;h4 id=&quot;adversarial-attacks&quot;&gt;Adversarial Attacks&lt;/h4&gt;
&lt;p&gt;Adversarial attacks are another sophisticated category of threats in which an adversary subtly manipulates the input data of an artificial intelligence model to cause systematic classification or decision errors, without the changes being obvious to human users. In the case of computer vision models, for example, adding minimal perturbations - invisible to the naked eye - to an image can cause the system to misidentify an object (a traffic sign can be misclassified, with serious implications for autonomous vehicles). Similarly, in natural language processing, the insertion of specially constructed linguistic structures or tokens can skew the interpretation of the model. These attacks exploit the mathematical sensitivity of neural networks to small variations in the multidimensional data space, demonstrating that high performance on standard data does not guarantee robustness under adversarial conditions. From a Security by Design perspective, countermeasures include adversarial training, robust input validation, and monitoring for abnormal model behavior in production.&lt;/p&gt;
&lt;h4 id=&quot;model-inversion-amp-data-extraction&quot;&gt;Model Inversion &amp;amp; Data Extraction&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Model Inversion&lt;/strong&gt; is an attack technique in which an adversary attempts to reconstruct sensitive information about the data used to train a model, using only access to the final (black-box or sometimes white-box) model. The central idea is that machine learning models “retain” to some extent statistical characteristics of the training data. If the model is strategically interrogated, the attacker can approximate individual data or sensitive features associated with a particular user. For example, in a facial recognition system or a medical model trained on clinical data, an attacker could reconstruct facial features or information about a specific patient. The vulnerability arises especially when the model is overfitted or when its responses provide detailed probability scores, which can be exploited for reverse inference. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data Extraction&lt;/strong&gt; (or model extraction / training data extraction) goes even further, aiming at directly extracting fragments of data stored by the model during training. In the case of large language models, this can mean regenerating portions of sensitive texts accidentally included in datasets (personal data, API keys, confidential information). Attackers use iterative queries, strategic formulations or optimization techniques to “squeeze” the model of stored information. The risk is amplified when the models are integrated into public applications and provide very detailed answers. From a Security by Design perspective, protection against these attacks involves limiting the granularity of the outputs, using regularization and differential privacy techniques in the training phase, as well as implementing robust mechanisms for monitoring and filtering the generated answers.&lt;/p&gt;
&lt;h4 id=&quot;prompt-injection-in-llms-&quot;&gt;Prompt Injection (in LLMs)&lt;/h4&gt;
&lt;p&gt;Unlike traditional attacks on web applications or infrastructure, prompt injection does not exploit a classic programming bug, but the very probabilistic and contextual nature of the model.&lt;/p&gt;
&lt;p&gt;Prompt Injection is a technique by which a user inserts malicious instructions into an apparently legitimate input, with the aim of modifying the behavior of the model and causing it to ignore the rules or restrictions established by the system.&lt;/p&gt;
&lt;p&gt;Typically, a system based on LLM works as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There is a system prompt (internal instructions, invisible to the user).&lt;/li&gt;
&lt;li&gt;There is a user prompt (user input).&lt;/li&gt;
&lt;li&gt;The model generates a response based on the entire context.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The vulnerability arises because the model treats all text as a sequence of tokens, without making a rigid structural distinction between system and user instructions. Thus, a user can try to &quot;overwrite&quot; the initial rules with a specially constructed input.&lt;/p&gt;
&lt;p&gt;Security by Design in the AI era involves anticipating these scenarios and designing the system so that it is robust, resilient and able to detect behavioral deviations.&lt;/p&gt;
&lt;h3 id=&quot;integrating-security-into-the-ai-lifecycle&quot;&gt;Integrating Security into the AI Lifecycle&lt;/h3&gt;
&lt;p&gt;An AI system goes through several stages: data collection, model training, validation, deployment, and ongoing operation. Each of these phases should be treated as a critical control point.&lt;/p&gt;
&lt;p&gt;In the data collection phase, security means verifying sources, ensuring the integrity of datasets, and protecting sensitive data through anonymization or pseudonymization. Data is the foundation of the model; if this foundation is compromised, the entire system becomes fragile.&lt;/p&gt;
&lt;p&gt;In the training phase, the infrastructure must be isolated and monitored. Models must be versioned, and each experiment must be documented to allow for later auditing. The integrity of generated artifacts must be verified through cryptographic mechanisms, and access to computing resources must be limited according to the principle of least privilege.&lt;/p&gt;
&lt;p&gt;At the time of deployment, API security becomes essential. Rate limiting, multifactor authentication, and monitoring for abnormal behavior are measures that reduce the risk of system exploitation. In the operational phase, continuous monitoring of model performance and detection of model drift are essential to prevent degradation or manipulation of its behavior.&lt;/p&gt;
&lt;p&gt;&lt;u&gt;Security by Design therefore means a continuity of control, not a single event.&lt;/u&gt;&lt;/p&gt;
&lt;h3 id=&quot;zero-trust-and-the-new-paradigm-of-trust&quot;&gt;Zero Trust and the new paradigm of trust&lt;/h3&gt;
&lt;p&gt;In the AI era, the concept of trust must be rethought. The Zero Trust model, which assumes that no user or system is implicitly trusted, becomes extremely relevant. Access to AI models and associated data should only be granted based on clear and verifiable policies.&lt;/p&gt;
&lt;p&gt;This approach is all the more important as AI systems are integrated into complex ecosystems, distributed in the cloud, and connected to multiple data sources. The lack of segmentation or granular access controls can turn a minor incident into a major breach.&lt;/p&gt;
&lt;p&gt;Zero Trust applied to AI is not limited to authentication; it also involves continuous validation of system behavior, verification of model integrity, and permanent analysis of user interactions.&lt;/p&gt;
&lt;h3 id=&quot;regulation-governance-and-accountability&quot;&gt;Regulation, governance and accountability&lt;/h3&gt;
&lt;p&gt;As AI becomes critical infrastructure, regulation becomes inevitable. The European Union has introduced the AI Act, which classifies AI systems according to risk and imposes strict requirements for those considered high-risk. In parallel, the GDPR establishes clear obligations on data protection and the right to explanation.&lt;/p&gt;
&lt;p&gt;These legislative frameworks are not obstacles to innovation, but catalysts for the adoption of Security by Design. They force organizations to document processes, implement audit mechanisms and ensure transparency.&lt;br&gt;
AI governance thus becomes a central element of security. It is not enough for a system to perform; it must be accountable, explainable and compliant with legal norms.&lt;/p&gt;
&lt;h3 id=&quot;the-ethical-dimension-of-security&quot;&gt;The ethical dimension of security&lt;/h3&gt;
&lt;p&gt;Security in the age of AI is not just a technical issue. It is also an ethical one. A model that discriminates or produces biased results can generate damage as serious as a data breach.&lt;/p&gt;
&lt;p&gt;Companies like Microsoft and IBM have developed Responsible AI frameworks that include principles of fairness, transparency, and accountability. These initiatives show that Security by Design must also include protection against social and moral risks.&lt;/p&gt;
&lt;p&gt;Ultimately, &lt;u&gt;security is not just about protecting the system, but also protecting the people affected by its decisions&lt;/u&gt;.&lt;/p&gt;
&lt;h3 id=&quot;red-teaming-and-operational-resilience&quot;&gt;Red Teaming and Operational Resilience&lt;/h3&gt;
&lt;p&gt;A secure system is not one that has not been attacked, but one that has been rigorously tested and proven resilient. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Red Teaming&lt;/strong&gt; in the context of AI systems is a structured process through which specialized teams simulate real-world attacks to identify vulnerabilities before they are exploited in the operational environment, directly contributing to increasing the resilience of the system. Unlike traditional testing, red teaming involves a creative adversarial approach, in which experts try to circumvent model restrictions through prompt injection, adversarial attacks, data exfiltration attempts, or manipulation of algorithmic behavior. The goal is not just to discover specific technical errors, but to assess the ability of the entire architecture - model, infrastructure, access controls, and organizational processes - to withstand real-world pressures. By integrating red teaming into the continuous development and operations (MLOps) cycle, organizations can transform security from a reaction to incidents into a proactive mechanism for strengthening operational resilience, ensuring the safe and stable operation of AI systems in dynamic and potentially hostile conditions.&lt;/p&gt;
&lt;p&gt;This practice transforms security from a defensive to a proactive process. Instead of reacting to incidents, organizations anticipate and model risk scenarios.&lt;/p&gt;
&lt;h3 id=&quot;future-challenges&quot;&gt;Future Challenges&lt;/h3&gt;
&lt;p&gt;As AI evolves towards autonomous systems and agents capable of making complex decisions independently, the attack surface will continue to grow. The integration of AI into critical infrastructure, financial systems, or healthcare systems will amplify the potential impact of vulnerabilities.&lt;/p&gt;
&lt;p&gt;Security by Design must evolve with technology. Collaboration between engineers, security experts, lawyers, ethicists, and policymakers will be required. Without this interdisciplinary approach, the complexity of AI systems may outstrip our ability to control them.&lt;/p&gt;
&lt;h3 id=&quot;conclusion-security-as-the-foundation-of-trust&quot;&gt;Conclusion: Security as the Foundation of Trust&lt;/h3&gt;
&lt;p&gt;In the age of artificial intelligence, security is no longer a technical detail, but the foundation of digital trust. Without Security by Design, AI systems can become vulnerable, manipulable, and potentially dangerous tools. With Security by Design, they can become catalysts for progress, supporting innovation in a responsible and sustainable way.&lt;/p&gt;
&lt;p&gt;Building secure AI is not about slowing down development, but making it sustainable. In a world where algorithms increasingly influence reality, security becomes the invisible architecture that supports the future.&lt;/p&gt;
&lt;h3 id=&quot;bibliography&quot;&gt;Bibliography&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Pearlson, Keri &amp;amp; Novaes Neto, Nelson. „What is Secure-by-Design AI?” &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://mitsloan.mit.edu/ideas-made-to-matter/working-definitions/what-is-secure-by-design-ai&quot;&gt;Definition and framework for integrating security as a basic principle in the design of AI systems&lt;/a&gt;. &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ETSI EN 304 223 - Securing Artificial Intelligence (SAI) &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.etsi.org/deliver/etsi_en/304200_304299/304223/02.01.01_60/en_304223v020101p.pdf&quot;&gt;Security technical standard for AI systems, which includes &quot;secure design&quot; principles in the different stages of the AI lifecycle&lt;/a&gt;. &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UK Government - Code of Practice for the Cyber Security of AI &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.gov.uk/government/publications/ai-cyber-security-code-of-practice/code-of-practice-for-the-cyber-security-of-ai&quot;&gt;Best practices guide for designing and operating AI securely, including the principle of secure design&lt;/a&gt;.  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prasad, Anand. „A Policy Roadmap for Secure by Design AI: Building Trust Through Security-First Development” &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://seceon.com/a-policy-roadmap-for-secure-by-design-ai-building-trust-through-security-first-development/&quot;&gt;Article discussing the need to shift the AI security paradigm from reactive to proactive&lt;/a&gt;.  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Abbas, Rianat et al. „Secure by design - enhancing software products with AI-Driven security measures” &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.researchgate.net/publication/390611686_Secure_by_design_-_enhancing_software_products_with_AI-Driven_security_measures&quot;&gt;Study addressing the integration of AI-based security measures within Secure by Design&lt;/a&gt;.  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30793/security-design-the-age-artificial-intelligence-fundamentals-risks-resilience-strateg</guid>
<pubDate>Mon, 12 Oct 2026 10:17:13 +0000</pubDate>
</item>
<item>
<title>Anatomy of a Short Link, When People Actually Want to Click?</title>
<link>https://coderlegion.com/26675/anatomy-of-a-short-link-when-people-actually-want-to-click</link>
<description>&lt;h3 id=&quot;five-small-details-that-make-a-short-url-feel-clearer-safer-and-more-memorable&quot;&gt;Five small details that make a short url feel clearer, safer, and more memorable&lt;/h3&gt;
&lt;p&gt;A short link is not automatically a good link. Removing characters from a URL solves a length problem, but it does not answer the questions people ask before clicking: Where will this take me? Is it relevant? Can I trust it? Will I remember it later?&lt;/p&gt;
&lt;p&gt;For web developers, the best short link is a small piece of interface design. Its domain, slug, surrounding copy, visual treatment, and technical behavior work together to reduce uncertainty. Get those details right and the link feels intentional instead of anonymous. &lt;/p&gt;
&lt;p&gt;This guide explains five details that make a short link more useful across websites, email, social posts, documentation, QR codes, and messaging apps. The examples are global by design, so they also account for mobile users, multilingual audiences, and people navigating with keyboards or assistive technology.&lt;/p&gt;
&lt;h3 id=&quot;1-a-recognizable-domain-comes-first&quot;&gt;1. A recognizable domain comes first&lt;/h3&gt;
&lt;p&gt;The domain is the first identity signal a person sees. A familiar brand domain tells the reader who is responsible for the destination. An unfamiliar or randomly generated domain makes the same destination feel harder to evaluate.&lt;/p&gt;
&lt;p&gt;Compare these examples:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://xmpl.co/a8K2p
https://docs.example.com/api/authentication
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first is shorter, but the second gives the reader more context. It identifies the organization, the product area, and the subject of the page.&lt;/p&gt;
&lt;p&gt;A branded short link can combine the compactness of a short URL with the recognition of a known name:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://go.example.com/api-auth
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This does not prove that a destination is safe. It does, however, give people a useful signal to check. Security still depends on the actual destination, the service that manages the redirect, and the way your application handles abuse reports and compromised links.&lt;/p&gt;
&lt;h4 id=&quot;developer-checklist&quot;&gt;Developer checklist&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Use HTTPS for the short-link domain.&lt;/li&gt;
&lt;li&gt;Keep the domain easy to read aloud and type.&lt;/li&gt;
&lt;li&gt;Avoid confusing character combinations, such as lowercase &lt;code&gt;l&lt;/code&gt;, uppercase &lt;code&gt;I&lt;/code&gt;, and the number &lt;code&gt;1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use a branded domain when the link represents a company, product, or community.&lt;/li&gt;
&lt;li&gt;Make the domain match the identity shown in the surrounding copy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you do not want to operate the redirect infrastructure yourself, &lt;a rel=&quot;nofollow&quot; href=&quot;https://eslotmain.xyz&quot;&gt;Eslotmain is a link-shortening platform&lt;/a&gt; that offers custom slugs and branded domains. That can help keep a short link recognizable in a campaign or product flow. Treat the domain as an identity layer, not as a substitute for security review. A detailed &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.techtips.fun/blog/build-bulk-10-url-shortner-next-js/&quot;&gt;url shortener tutorial post is available&lt;/a&gt; &lt;/p&gt;
&lt;h3 id=&quot;2-the-slug-should-explain-the-destination&quot;&gt;2. The slug should explain the destination&lt;/h3&gt;
&lt;p&gt;The slug is the part of the short URL after the domain. It is a small space, but it can carry a surprising amount of meaning.&lt;/p&gt;
&lt;p&gt;A random slug such as this is difficult to remember:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://go.example.com/q7N4bP
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A descriptive slug is easier to scan and repeat:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://go.example.com/webinar-replay
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The goal is not to put the whole page title into the URL. The goal is to provide the shortest useful description. Use lowercase letters, hyphens between words, and stable words that will still make sense after the campaign ends.&lt;/p&gt;
&lt;p&gt;Good slugs are usually:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Specific:&lt;/strong&gt; &lt;code&gt;node-logging-guide&lt;/code&gt; says more than &lt;code&gt;guide&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compact:&lt;/strong&gt; remove filler words and unnecessary dates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Readable:&lt;/strong&gt; use words instead of an opaque character string.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stable:&lt;/strong&gt; avoid changing the slug every time the page copy changes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audience-aware:&lt;/strong&gt; use language your intended readers understand.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Be careful with dates, regional terms, and abbreviations. A slug such as &lt;code&gt;summer-sale-uk&lt;/code&gt; may be useful for a time-limited campaign, but &lt;code&gt;product-guide-en&lt;/code&gt; and &lt;code&gt;product-guide-fr&lt;/code&gt; are clearer when you maintain separate language destinations. For a worldwide audience, decide whether language belongs in the slug, the destination path, or the campaign system. Do not make readers guess.&lt;/p&gt;
&lt;p&gt;A descriptive slug is also helpful when someone sees a link without its surrounding page, such as in a screenshot or a copied message. It cannot reveal every redirect detail, but it gives the reader an immediate reason to understand the link.&lt;/p&gt;
&lt;h3 id=&quot;3-the-words-around-the-link-must-set-expectations&quot;&gt;3. The words around the link must set expectations&lt;/h3&gt;
&lt;p&gt;A short URL cannot do all the communication by itself. Link context tells people why the destination matters and what will happen after the click.&lt;/p&gt;
&lt;p&gt;Weak copy:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Read more: &lt;code&gt;https://go.example.com/r4T9&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Clearer copy:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Read the &lt;strong&gt;WebAuthn implementation guide&lt;/strong&gt; for a browser-ready passkey flow: &lt;code&gt;https://go.example.com/webauthn-guide&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The second version answers three questions before the click:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What is the destination?&lt;/li&gt;
&lt;li&gt;Why should I open it?&lt;/li&gt;
&lt;li&gt;What kind of content will I find?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is also an accessibility requirement, not just a conversion tactic. &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.w3.org/WAI/WCAG21/Understanding/link-purpose-in-context.html&quot;&gt;WCAG guidance says that a link's&lt;/a&gt; purpose should be understandable from its text or its immediately associated context. &lt;a rel=&quot;nofollow&quot; href=&quot;https://designsystem.digital.gov/components/link/&quot;&gt;The U.S. Web Design System similarly advises developers&lt;/a&gt; to use unique, meaningful link text and avoid generic phrases such as “click here” and “read more”&lt;/p&gt;
&lt;p&gt;Use the link itself as the meaningful phrase where possible:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p&amp;gt;
  Read the
  &amp;lt;a href=&quot;https://go.example.com/webauthn-guide&quot;&amp;gt;
    WebAuthn implementation guide
  &amp;lt;/a&amp;gt;
  before adding passkey support.
&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Avoid relying on a &lt;code&gt;title&lt;/code&gt; attribute to repair vague link text. The visible words should do most of the work. If the link opens a PDF, starts a download, requires a login, or leaves your site, say so in the visible text or its nearby context.&lt;/p&gt;
&lt;p&gt;For international audiences, keep the context direct and easy to translate. Short sentences with concrete nouns are easier to understand than clever calls to action.&lt;/p&gt;
&lt;h3 id=&quot;4-visual-treatment-should-make-the-click-obvious&quot;&gt;4. Visual treatment should make the click obvious&lt;/h3&gt;
&lt;p&gt;People should not have to move a pointer around the page to discover what is clickable. A link needs a visible affordance, which is a cue that communicates how an element can be used.&lt;/p&gt;
&lt;p&gt;A dependable text-link treatment includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A consistent color that is distinct from ordinary text.&lt;/li&gt;
&lt;li&gt;An underline or another non-color cue in body copy.&lt;/li&gt;
&lt;li&gt;A visible hover state.&lt;/li&gt;
&lt;li&gt;A visible keyboard focus state.&lt;/li&gt;
&lt;li&gt;Sufficient contrast against the background.&lt;/li&gt;
&lt;li&gt;A target large enough to tap comfortably on a phone.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Do not style ordinary headings or noninteractive cards to look like links. That creates false signals. &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.nngroup.com/articles/clickable-elements/&quot;&gt;Nielsen Norman Group describes&lt;/a&gt; consistency, placement, color, borders, and familiar web conventions as important cues for recognizing clickable elements. Its practical lesson is simple: if a design system teaches users one visual pattern for links, use that pattern everywhere.&lt;/p&gt;
&lt;p&gt;The HTML should preserve native link behavior:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;a class=&quot;text-link&quot; href=&quot;https://go.example.com/docs&quot;&amp;gt;
  Read the developer documentation
&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.text-link {
  color: #075985;
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

.text-link:hover,
.text-link:focus-visible {
  color: #0c4a6e;
  text-decoration-thickness: 0.14em;
}

.text-link:focus-visible {
  outline: 3px solid #f59e0b;
  outline-offset: 3px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not remove the outline without replacing it with an equally visible focus style. A keyboard user must be able to find the active link, and pressing Enter should activate it. Use a real &lt;code&gt;&amp;lt;a href=&quot;...&quot;&amp;gt;&lt;/code&gt; for navigation, not a clickable &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; that forces you to recreate browser behavior with JavaScript.&lt;/p&gt;
&lt;h3 id=&quot;5-the-redirect-should-feel-safe-and-predictable&quot;&gt;5. The redirect should feel safe and predictable&lt;/h3&gt;
&lt;p&gt;A short link is a promise: the destination should match the expectation created by the link. Broken redirects, unexpected login walls, surprise downloads, and unrelated landing pages damage trust quickly.&lt;/p&gt;
&lt;p&gt;Before publishing a short link, check the full path from click to destination:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Does the short URL use HTTPS?&lt;/li&gt;
&lt;li&gt;Does it redirect quickly on mobile and desktop?&lt;/li&gt;
&lt;li&gt;Does it land on the page named in the link text?&lt;/li&gt;
&lt;li&gt;Does it preserve the intended language or regional experience?&lt;/li&gt;
&lt;li&gt;Does it avoid unnecessary redirect chains?&lt;/li&gt;
&lt;li&gt;Does it remain valid after the campaign or product release changes?&lt;/li&gt;
&lt;li&gt;Can you disable or update it if the destination becomes unsafe?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Analytics can help you detect problems and improve future links, but measurement should not make the experience opaque. Explain tracking where your privacy rules require it, collect only what you need, and protect link analytics as user data.&lt;/p&gt;
&lt;p&gt;A useful short-link dashboard can show patterns such as clicks, locations, devices, and referrers. Eslotmain publicly describes these analytics as part of its platform, along with quick link creation and a free option for personal links with no credit card required. Developers can use those signals to compare distribution channels, spot a broken campaign, or learn whether a mobile audience needs a different landing page. Analytics should answer a product question, not become a reason to add needless tracking.&lt;/p&gt;
&lt;h3 id=&quot;putting-the-five-details-together&quot;&gt;Putting the five details together&lt;/h3&gt;
&lt;p&gt;Here is a practical example for a developer newsletter:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p&amp;gt;
  The new release adds passkey support. See the
  &amp;lt;a href=&quot;https://go.example.com/passkeys-node&quot;&amp;gt;
    Node.js passkey integration guide
  &amp;lt;/a&amp;gt;
  for setup steps and browser notes.
&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The link works because each part has a job:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Link detail &lt;/th&gt;&lt;th&gt;What the reader learns&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Recognizable domain &lt;/td&gt;&lt;td&gt;Who owns or manages the link&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Descriptive slug &lt;/td&gt;&lt;td&gt;The general destination topic&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Surrounding copy &lt;/td&gt;&lt;td&gt;Why the destination is relevant&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Visible link styling &lt;/td&gt;&lt;td&gt;What can be clicked or tapped&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Predictable redirect &lt;/td&gt;&lt;td&gt;What will happen after activation&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A short link does not need to be loud. It needs to be legible, specific, and consistent with the experience that follows it.&lt;/p&gt;
&lt;h3 id=&quot;a-quick-pre-publish-test-for-web-developers&quot;&gt;A quick pre-publish test for web developers&lt;/h3&gt;
&lt;p&gt;Run this five-minute review before you share a link in production:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Read it without context.&lt;/strong&gt; Can you infer the topic from the domain and slug?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read the sentence only.&lt;/strong&gt; Does the link explain its destination and purpose?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use the keyboard.&lt;/strong&gt; Can you reach the link with Tab and activate it with Enter?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test a phone.&lt;/strong&gt; Is the target easy to tap, and does the redirect load quickly?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Follow the complete redirect.&lt;/strong&gt; Does the final page match the promise?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check a copied version.&lt;/strong&gt; Does the link remain understandable when pasted into a message?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Review the data plan.&lt;/strong&gt; Are analytics transparent, necessary, and handled according to your privacy obligations?&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;final-takeaway&quot;&gt;Final takeaway&lt;/h3&gt;
&lt;p&gt;The anatomy of a link people want to click is not complicated. Start with a recognizable domain, add a readable slug, explain the destination in nearby text, make the link visibly interactive, and keep the redirect safe and predictable.&lt;/p&gt;
&lt;p&gt;Shortening a URL is only the mechanical step. The real work is reducing doubt. When the link tells people where they are going, why it matters, and what to expect, a click becomes a reasonable decision instead of a gamble.&lt;/p&gt;
&lt;h3 id=&quot;frequently-asked-questions&quot;&gt;Frequently Asked Questions&lt;/h3&gt;
&lt;h4 id=&quot;what-makes-a-link-more-clickable-&quot;&gt;What makes a link more clickable?&lt;/h4&gt;
&lt;p&gt;A link becomes more clickable when its destination and benefit are clear before activation. Descriptive link text, a recognizable domain, a readable slug, strong visual treatment, and a reliable redirect all reduce uncertainty.&lt;/p&gt;
&lt;h4 id=&quot;should-developers-use-descriptive-link-text-instead-of-click-here-&quot;&gt;Should developers use descriptive link text instead of “click here”?&lt;/h4&gt;
&lt;p&gt;Yes. Descriptive text helps sighted readers scan the page and helps screen-reader users understand a list of links out of context. Use words that identify the destination or action, such as “download the API reference PDF” or “view the passkey guide.”&lt;/p&gt;
&lt;h4 id=&quot;are-short-links-safe-to-click-&quot;&gt;Are short links safe to click?&lt;/h4&gt;
&lt;p&gt;Short links are not automatically safe because they hide the final destination. Use HTTPS, publish links through a service with abuse controls and management tools, provide useful context, and verify unfamiliar links before opening them.&lt;/p&gt;
&lt;h4 id=&quot;do-branded-short-links-improve-trust-&quot;&gt;Do branded short links improve trust?&lt;/h4&gt;
&lt;p&gt;They can improve recognition because the visible domain is connected to a known organization or product. Branded links do not guarantee that a destination is safe, so they should be combined with secure redirects, honest link context, and regular destination checks.&lt;/p&gt;
&lt;h4 id=&quot;do-short-links-help-seo-&quot;&gt;Do short links help SEO?&lt;/h4&gt;
&lt;p&gt;A short link can improve sharing and readability, but shortening a URL alone does not guarantee higher search rankings. For SEO, focus on the final page's content, canonical setup, crawlability, internal linking, and a clear user experience.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26675/anatomy-of-a-short-link-when-people-actually-want-to-click</guid>
<pubDate>Wed, 09 Sep 2026 22:07:23 +0000</pubDate>
</item>
<item>
<title>Demystifying Dependency Injection: Writing Code That Doesn't Break When Things Change</title>
<link>https://coderlegion.com/25252/demystifying-dependency-injection-writing-code-that-doesnt-break-when-things-change</link>
<description>&lt;h3 id=&quot;what-is-dependency-injection-&quot;&gt;What is Dependency Injection?&lt;/h3&gt;
&lt;p&gt;Dependency Injection is a software design pattern where a program component receives the external resources or tools it needs to function from the outside, rather than creating them itself. In simpler terms, instead of a program hardcoding its own helpers internally, those helpers are 'injected' or passed into it when it starts up. This simple shift in responsibility makes your code incredibly flexible, highly reusable, and easy to modify without breaking existing features.&lt;/p&gt;
&lt;h3 id=&quot;the-hybrid-car-battery-analogy&quot;&gt;The Hybrid Car Battery Analogy&lt;/h3&gt;
&lt;p&gt;To understand this concept, imagine you are building a modern hybrid car. If you permanently solder a proprietary brand of battery directly to the car's frame and engine, you have a tightly coupled system. If that battery eventually degrades, or if a cheaper, more efficient battery is manufactured next year, you are in trouble. You would have to dismantle the entire car just to swap out the battery.&lt;/p&gt;
&lt;p&gt;Instead, car manufacturers design a standardized battery compartment with a universal plug. The car's engine does not care about the internal chemistry or brand of the battery, as long as it fits the plug and delivers the expected voltage. The battery is 'injected' into the car from the outside. If you need to change the battery, you simply unplug the old one and slide in the new one. The car remains untouched.&lt;/p&gt;
&lt;h3 id=&quot;why-dependency-injection-matters-every-day&quot;&gt;Why Dependency Injection Matters Every Day&lt;/h3&gt;
&lt;p&gt;In professional software engineering, tight coupling is a primary cause of technical debt and broken deployments. Without Dependency Injection, developers cannot easily test their code in isolation. For instance, if a checkout system creates its own connection to a payment provider like Stripe inside its code, you cannot run automated tests on the checkout system without accidentally triggering real API calls and charging real credit cards.&lt;/p&gt;
&lt;p&gt;By using Dependency Injection, engineers can swap out the real payment service for a fake 'mock' service during testing. This allows test suites to run in milliseconds without relying on external servers or risking financial errors. Furthermore, when business requirements inevitably pivot—such as switching your payment processor from Stripe to PayPal—you only have to write a new adapter and inject it at startup, rather than rewriting the core checkout logic. This prevents bugs from creeping into unrelated parts of your codebase.&lt;/p&gt;
&lt;h3 id=&quot;dependency-injection-in-action&quot;&gt;Dependency Injection in Action&lt;/h3&gt;
&lt;p&gt;Let's look at how this looks in JavaScript code. First, we will examine code that does &lt;em&gt;not&lt;/em&gt; use Dependency Injection, followed by the improved version that does.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// WITHOUT DEPENDENCY INJECTION (Tightly Coupled)
class CheckoutProcess {
  constructor() {
    // The checkout process is locked into Stripe; we cannot easily change or mock this
    this.paymentService = new StripeProcessor();
  }

  completeOrder(order) {
    this.paymentService.charge(order.amount);
  }
}

// WITH DEPENDENCY INJECTION (Loosely Coupled)
class DecoupledCheckoutProcess {
  constructor(paymentService) {
    // The payment service is injected from the outside
    this.paymentService = paymentService;
  }

  completeOrder(order) {
    this.paymentService.charge(order.amount);
  }
}

// Usage in production:
const stripe = new StripeProcessor();
const productionCheckout = new DecoupledCheckoutProcess(stripe);

// Usage in automated testing (injecting a fake helper instead):
const fakeMockProcessor = {
  charge: (amount) =&amp;gt; console.log('Simulated charge of $' + amount)
};
const testCheckout = new DecoupledCheckoutProcess(fakeMockProcessor);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-takeaway&quot;&gt;The Takeaway&lt;/h3&gt;
&lt;p&gt;Ultimately, Dependency Injection changes how you think about software architecture by turning hardcoded, rigid applications into collections of plug-and-play modules. By separating how a tool is configured from how that tool is actually used, you create a codebase that is inherently stable, highly testable, and ready to adapt to whatever changes your users or business stakeholders demand tomorrow.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;resources&quot;&gt;Resources&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Saurav-TB-Pandey/react-hook-lab&quot;&gt;react-hook-lab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;react-hook-lab:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/react-hook-lab&quot;&gt;npm package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connect with me on LinkedIn:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/pandeysaurav/&quot;&gt;Saurav Pandey&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/08/how-dependency-injection-makes-your.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/saurav_tb_pandey/demystifying-dependency-injection-writing-code-that-doesnt-break-when-things-change-1mnd&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25252/demystifying-dependency-injection-writing-code-that-doesnt-break-when-things-change</guid>
<pubDate>Fri, 04 Sep 2026 06:30:09 +0000</pubDate>
</item>
<item>
<title>Three Go Concurrency Mistakes I See in Almost Every Worker Pool</title>
<link>https://coderlegion.com/23086/three-go-concurrency-mistakes-i-see-in-almost-every-worker-pool</link>
<description>&lt;p&gt;Go makes concurrency feel easy. Goroutines are cheap, channels are built into the language, and the standard library gives you everything you need to spin up a worker pool in under 30 lines. That ease is genuinely valuable — but it also means bugs sneak in quietly. The program compiles, the tests pass, and the mistake only surfaces under load or after hours of runtime.&lt;/p&gt;
&lt;p&gt;After working on high-throughput backend systems in Go, I keep seeing the same three mistakes. None of them are obscure. All of them are fixable in minutes once you know what to look for.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;mistake-1-goroutine-leaks-from-channels-that-never-close&quot;&gt;Mistake 1: Goroutine leaks from channels that never close&lt;/h3&gt;
&lt;p&gt;Here is a worker pool that looks completely reasonable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func startWorkers(jobs &amp;lt;-chan Job) {
    for i := 0; i &amp;lt; 10; i++ {
        go func() {
            for job := range jobs {
                process(job)
            }
        }()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each worker ranges over the &lt;code&gt;jobs&lt;/code&gt; channel and processes whatever comes in. Clean, idiomatic Go. The problem: &lt;strong&gt;if &lt;code&gt;jobs&lt;/code&gt; is never closed, every one of those goroutines blocks forever&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;for range&lt;/code&gt; on a channel blocks until the channel is closed or a value arrives. If the producer stops sending but never calls &lt;code&gt;close(jobs)&lt;/code&gt;, your 10 workers sit there permanently — holding memory, holding stack space, invisible to your metrics unless you’re explicitly tracking goroutine count.&lt;/p&gt;
&lt;p&gt;In a long-running service, this compounds. Every time you restart the worker pool (say, on a config reload or a new batch) without draining the old one, you accumulate leaked goroutines.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The fix: always close the jobs channel when the producer is done, and own that responsibility clearly.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func runBatch(jobList []Job) {
    jobs := make(chan Job, len(jobList))

    // Producer owns the channel and closes it when done
    go func() {
        defer close(jobs) // guaranteed even if we panic
        for _, job := range jobList {
            jobs &amp;lt;- job
        }
    }()

    var wg sync.WaitGroup
    for i := 0; i &amp;lt; 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for job := range jobs {
                process(job)
            }
        }()
    }

    wg.Wait()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two things worth noting here. First, &lt;code&gt;defer close(jobs)&lt;/code&gt; in the producer goroutine means the channel closes even if the producer exits early or panics while sending jobs. Second, the &lt;code&gt;sync.WaitGroup&lt;/code&gt; gives us a clean join point — &lt;code&gt;wg.Wait()&lt;/code&gt; blocks until every worker has finished draining.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quick diagnostic:&lt;/strong&gt; add &lt;code&gt;runtime.NumGoroutine()&lt;/code&gt; to your health endpoint. If that number grows over the lifetime of your service and never comes back down, you have a leak.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;mistake-2-waitgroup-counter-manipulation-in-the-wrong-place&quot;&gt;Mistake 2: WaitGroup counter manipulation in the wrong place&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;sync.WaitGroup&lt;/code&gt; has one sharp edge: &lt;strong&gt;&lt;code&gt;wg.Add&lt;/code&gt; must be called before the goroutine starts, not inside it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This version has a race condition:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;var wg sync.WaitGroup

for _, job := range jobs {
    go func(j Job) {
        wg.Add(1)        // ❌ too late
        defer wg.Done()
        process(j)
    }(job)
}

wg.Wait()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the loop spawns goroutines quickly and the scheduler runs slowly, &lt;code&gt;wg.Wait()&lt;/code&gt; can be reached before any goroutine has called &lt;code&gt;wg.Add(1)&lt;/code&gt;. The counter is zero, &lt;code&gt;Wait()&lt;/code&gt; returns immediately, and your main function exits while workers are still running — silently dropping work.&lt;/p&gt;
&lt;p&gt;This is a classic race condition. It won’t always reproduce, which makes it worse.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The fix is simple: call &lt;code&gt;wg.Add&lt;/code&gt; in the loop body, before launching the goroutine.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;var wg sync.WaitGroup

for _, job := range jobs {
    wg.Add(1) // ✅ happens-before the goroutine starts
    go func(j Job) {
        defer wg.Done()
        process(j)
    }(job)
}

wg.Wait()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the counter increment is guaranteed to happen before &lt;code&gt;wg.Wait()&lt;/code&gt; could possibly observe a zero. The goroutine carries the &lt;code&gt;Done()&lt;/code&gt; responsibility via &lt;code&gt;defer&lt;/code&gt;, so it fires even on panic.&lt;/p&gt;
&lt;p&gt;A secondary pattern worth knowing: if you know the count upfront (say, you’re launching exactly N workers), call &lt;code&gt;wg.Add(N)&lt;/code&gt; once before the loop. That’s even cleaner and harder to get wrong.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;const numWorkers = 10
var wg sync.WaitGroup
wg.Add(numWorkers) // add all at once

for i := 0; i &amp;lt; numWorkers; i++ {
    go func() {
        defer wg.Done()
        for job := range jobs {
            process(job)
        }
    }()
}

wg.Wait()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;mistake-3-unidirectional-channel-types-ignored-on-function-boundaries&quot;&gt;Mistake 3: Unidirectional channel types ignored on function boundaries&lt;/h3&gt;
&lt;p&gt;Go lets you declare channels as send-only (&lt;code&gt;chan&amp;lt;- Job&lt;/code&gt;) or receive-only (&lt;code&gt;&amp;lt;-chan Job&lt;/code&gt;). Most codebases I’ve seen don’t use this consistently — functions just accept &lt;code&gt;chan Job&lt;/code&gt; everywhere. This is technically fine, but it loses something real: &lt;strong&gt;the compiler can no longer tell you when your data flow is wrong.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here is a worker pool where the direction confusion causes a real bug:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func spawnWorkers(jobs chan Job, results chan Result) {
    for i := 0; i &amp;lt; 10; i++ {
        go func() {
            for job := range jobs {
                result := process(job)
                jobs &amp;lt;- result.nextJob  // ❌ worker feeding back into the input channel
                results &amp;lt;- result
            }
        }()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A worker accidentally wrote back into &lt;code&gt;jobs&lt;/code&gt; instead of &lt;code&gt;results&lt;/code&gt;. With bidirectional channels, this compiles without complaint. With directional types, it fails at compile time:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func spawnWorkers(jobs &amp;lt;-chan Job, results chan&amp;lt;- Result) {
    for i := 0; i &amp;lt; 10; i++ {
        go func() {
            for job := range jobs {
                result := process(job)
                jobs &amp;lt;- result.nextJob  // ✅ compile error: cannot send to receive-only channel
                results &amp;lt;- result
            }
        }()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The directional types act as documentation that’s enforced by the compiler. The producer only sees &lt;code&gt;chan&amp;lt;- Job&lt;/code&gt;. The consumer only sees &lt;code&gt;&amp;lt;-chan Job&lt;/code&gt;. Neither can accidentally do the wrong thing.&lt;/p&gt;
&lt;p&gt;A complete worker pool using this pattern correctly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func produce(jobs chan&amp;lt;- Job, jobList []Job) {
    defer close(jobs)
    for _, job := range jobList {
        jobs &amp;lt;- job
    }
}

func consume(jobs &amp;lt;-chan Job, results chan&amp;lt;- Result, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        results &amp;lt;- process(job)
    }
}

func run(jobList []Job) []Result {
    jobs := make(chan Job, 100)
    results := make(chan Result, 100)

    var wg sync.WaitGroup
    for i := 0; i &amp;lt; 10; i++ {
        wg.Add(1)
        go consume(jobs, results, &amp;amp;wg)
    }

    go produce(jobs, jobList)

    // Close results once all workers are done
    go func() {
        wg.Wait()
        close(results)
    }()

    var out []Result
    for result := range results {
        out = append(out, result)
    }
    return out
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every function now only sees what it’s supposed to see. The compiler enforces the data flow. And the pattern is easy to read: &lt;code&gt;produce&lt;/code&gt; feeds in, &lt;code&gt;consume&lt;/code&gt; drains, the closer goroutine shuts down results when all workers finish.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Channel never closed&lt;/strong&gt; — What goes wrong: goroutines leak, memory grows. Fix: the producer owns &lt;code&gt;close(ch)&lt;/code&gt;, always via &lt;code&gt;defer&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;wg.Add&lt;/code&gt; inside the goroutine&lt;/strong&gt; — What goes wrong: work silently dropped, races at shutdown. Fix: call &lt;code&gt;wg.Add&lt;/code&gt; before &lt;code&gt;go func()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bidirectional channels everywhere&lt;/strong&gt; — What goes wrong: wrong-direction sends compile silently. Fix: use &lt;code&gt;chan&amp;lt;-&lt;/code&gt; and &lt;code&gt;&amp;lt;-chan&lt;/code&gt; at function boundaries.&lt;/p&gt;
&lt;p&gt;None of these require clever solutions. They require habit. Once you internalize that the producer owns close, that Add precedes launch, and that channel direction is free documentation, they stop appearing.&lt;/p&gt;
&lt;p&gt;Go 1.25’s &lt;code&gt;go vet&lt;/code&gt; includes a WaitGroup analyzer that flags mistake 2 when &lt;code&gt;Add&lt;/code&gt; is called from inside a goroutine. On older Go versions, test the behavior directly by asserting that all launched work completes; &lt;code&gt;go test -race&lt;/code&gt; is not reliable for this case because &lt;code&gt;WaitGroup&lt;/code&gt; uses internal synchronization. For mistake 1, &lt;code&gt;runtime.NumGoroutine()&lt;/code&gt; in a test that runs your pool repeatedly and checks for growth is the simplest approach. Mistake 3 is pure compiler enforcement — no test needed once the types are right.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;If this was useful, the next post in this series covers &lt;code&gt;context.Context&lt;/code&gt; propagation in worker pools — specifically what happens when you need to cancel in-flight jobs cleanly without leaking the workers themselves.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23086/three-go-concurrency-mistakes-i-see-in-almost-every-worker-pool</guid>
<pubDate>Mon, 20 Jul 2026 16:44:53 +0000</pubDate>
</item>
<item>
<title>Go Interfaces: Why Less Is Almost Always More</title>
<link>https://coderlegion.com/23085/go-interfaces-why-less-is-almost-always-more</link>
<description>&lt;h3 id=&quot;go-interfaces-why-less-is-almost-always-more&quot;&gt;Go Interfaces: Why Less Is Almost Always More&lt;/h3&gt;
&lt;p&gt;If you’re coming to Go from Java or C++, interfaces look familiar at first glance. You define a set of methods, types implement them, you write code against the interface. Same idea, right?&lt;/p&gt;
&lt;p&gt;Not quite. Go interfaces have one property that changes everything about how you should design them: &lt;strong&gt;a type implements an interface implicitly, without declaring it&lt;/strong&gt;. There’s no &lt;code&gt;implements&lt;/code&gt; keyword. If your type has the right methods, it satisfies the interface — whether it knows about the interface or not.&lt;/p&gt;
&lt;p&gt;That single property pushes you toward a design philosophy that trips up most engineers coming from other languages: &lt;strong&gt;interfaces should be small, and they should be defined by the consumer, not the producer&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-the-standard-library-is-trying-to-tell-you&quot;&gt;What the standard library is trying to tell you&lt;/h3&gt;
&lt;p&gt;Look at the most-used interfaces in Go’s standard library:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type Closer interface {
    Close() error
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One method each. That’s not laziness — that’s a deliberate design decision. Because interfaces are satisfied implicitly, a one-method interface can be satisfied by an enormous range of types: files, network connections, buffers, custom types you haven’t written yet. The smaller the interface, the more things satisfy it, the more reusable your code becomes.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;io.ReadWriter&lt;/code&gt; combines two:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type ReadWriter interface {
    Reader
    Writer
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And &lt;code&gt;io.ReadWriteCloser&lt;/code&gt; combines three. Notice that the standard library builds up complexity by &lt;em&gt;composing&lt;/em&gt; small interfaces, not by defining large ones upfront.&lt;/p&gt;
&lt;p&gt;This is the pattern worth internalizing.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-fat-interface-trap&quot;&gt;The fat interface trap&lt;/h3&gt;
&lt;p&gt;Here is how engineers from Java or C++ backgrounds tend to define interfaces in Go. Say you’re building a job processing system and you want to abstract over different job stores:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// ❌ The fat interface
type JobStore interface {
    AddJob(job Job) error
    GetJob(id string) (Job, error)
    DeleteJob(id string) error
    ListJobs(filter Filter) ([]Job, error)
    UpdateJob(job Job) error
    CountJobs(filter Filter) (int, error)
    MarkComplete(id string) error
    MarkFailed(id string, reason error) error
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This feels complete. It covers everything the store can do. But now ask: how many types in your codebase will ever implement all 8 methods? Probably one — your real database implementation. And your test mock has to implement all 8 even if the function under test only calls &lt;code&gt;GetJob&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// Every test mock must implement all 8 methods even if irrelevant
type mockStore struct{}

func (m *mockStore) AddJob(job Job) error                         { return nil }
func (m *mockStore) GetJob(id string) (Job, error)               { return Job{}, nil }
func (m *mockStore) DeleteJob(id string) error                   { return nil }
func (m *mockStore) ListJobs(filter Filter) ([]Job, error)       { return nil, nil }
func (m *mockStore) UpdateJob(job Job) error                     { return nil }
func (m *mockStore) CountJobs(filter Filter) (int, error)        { return 0, nil }
func (m *mockStore) MarkComplete(id string) error                { return nil }
func (m *mockStore) MarkFailed(id string, reason error) error    { return nil }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eight methods of boilerplate for a test that calls one. And if you add a 9th method to the interface, every mock in the entire codebase breaks.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-go-way-consumer-defined-minimal-interfaces&quot;&gt;The Go way: consumer-defined, minimal interfaces&lt;/h3&gt;
&lt;p&gt;Instead of defining one big interface at the store level, define small interfaces at each &lt;em&gt;use site&lt;/em&gt; — the function that actually needs the behavior.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// ✅ Each function declares exactly what it needs

type JobGetter interface {
    GetJob(id string) (Job, error)
}

type JobAdder interface {
    AddJob(job Job) error
}

type JobCompleter interface {
    MarkComplete(id string) error
    MarkFailed(id string, reason error) error
}

func processJob(id string, store JobGetter) error {
    job, err := store.GetJob(id)
    if err != nil {
        return err
    }
    // process...
    return nil
}

func submitJob(job Job, store JobAdder) error {
    return store.AddJob(job)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now &lt;code&gt;processJob&lt;/code&gt; only depends on &lt;code&gt;JobGetter&lt;/code&gt;. Its test mock is one method:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type mockGetter struct {
    job Job
    err error
}

func (m *mockGetter) GetJob(id string) (Job, error) {
    return m.job, m.err
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your real &lt;code&gt;PostgresJobStore&lt;/code&gt; still implements all 8 methods. It satisfies every one of these small interfaces automatically, without any change, because Go interfaces are implicit. The store doesn’t know or care about &lt;code&gt;JobGetter&lt;/code&gt; or &lt;code&gt;JobAdder&lt;/code&gt; — it just has the methods, and that’s enough.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;accepting-interfaces-returning-structs&quot;&gt;Accepting interfaces, returning structs&lt;/h3&gt;
&lt;p&gt;There’s a corollary rule in Go that follows directly from this: &lt;strong&gt;accept interfaces, return concrete types&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// ✅ Accept an interface — flexible, testable
func drainQueue(queue &amp;lt;-chan Job, store JobAdder) error {
    for job := range queue {
        if err := store.AddJob(job); err != nil {
            return err
        }
    }
    return nil
}

// ❌ Return an interface — forces callers to type-assert, hides information
func newJobStore(cfg Config) JobStore {
    return &amp;amp;PostgresJobStore{...}
}

// ✅ Return a concrete type — callers get the full picture
func newJobStore(cfg Config) *PostgresJobStore {
    return &amp;amp;PostgresJobStore{...}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you return a concrete type, callers can always choose to assign it to a narrower interface themselves. When you return a fat interface, you’ve already made that decision for them, and they’re stuck with it.&lt;/p&gt;
&lt;p&gt;The one exception: returning &lt;code&gt;error&lt;/code&gt; is an interface, and that’s intentional — it lets you return any error type, including custom ones. But &lt;code&gt;error&lt;/code&gt; is a one-method interface, so the rule still holds in spirit.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;composing-small-interfaces-when-you-need-more&quot;&gt;Composing small interfaces when you need more&lt;/h3&gt;
&lt;p&gt;Sometimes a function genuinely needs multiple behaviors. Rather than reaching for a fat interface, compose:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type JobProcessor interface {
    JobGetter
    JobCompleter
}

func runWorker(id string, store JobProcessor) error {
    job, err := store.GetJob(id)
    if err != nil {
        return err
    }

    if err := doWork(job); err != nil {
        return store.MarkFailed(id, err)
    }

    return store.MarkComplete(id)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;JobProcessor&lt;/code&gt; is defined at the call site, composed from two smaller interfaces, and &lt;code&gt;PostgresJobStore&lt;/code&gt; satisfies it without modification. The test mock now only needs three methods.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;summary-1&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Interface size&lt;/strong&gt; — Avoid: 8-method interfaces defined upfront. Prefer: 1–3 method interfaces defined at the use site.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Where to define&lt;/strong&gt; — Avoid: the producer side (the package that has the struct). Prefer: the consumer side (the package that calls the function).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What to return&lt;/strong&gt; — Avoid: interfaces from constructors. Prefer: concrete types from constructors.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Complexity&lt;/strong&gt; — Avoid: a single large interface. Prefer: composition of small interfaces.&lt;/p&gt;
&lt;p&gt;Go’s implicit interface satisfaction is not just a syntactic convenience — it’s the mechanism that makes all of this work without coordination between packages. A type in one package can satisfy an interface defined in a completely different package, written years later, with no coupling between them.&lt;/p&gt;
&lt;p&gt;The standard library’s one-method interfaces aren’t minimal because Go is simple. They’re minimal because the designers understood what implicit satisfaction makes possible.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Next in this series: Three Go concurrency mistakes I see in almost every worker pool.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23085/go-interfaces-why-less-is-almost-always-more</guid>
<pubDate>Mon, 20 Jul 2026 16:44:11 +0000</pubDate>
</item>
<item>
<title>Prototype Design Pattern in C#: Cloning Objects Efficiently</title>
<link>https://coderlegion.com/21133/prototype-design-pattern-in-c-cloning-objects-efficiently</link>
<description>&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;Prototype Design Pattern&lt;/strong&gt; is a &lt;strong&gt;creational design pattern&lt;/strong&gt; that allows new objects to be created by copying an existing object (called a &lt;em&gt;prototype&lt;/em&gt;) instead of creating them from scratch.&lt;/p&gt;
&lt;p&gt;This pattern is particularly useful when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Object creation is expensive or complex.&lt;/li&gt;
&lt;li&gt;Many similar objects need to be created.&lt;/li&gt;
&lt;li&gt;You want to avoid repeatedly initializing the same data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of using constructors every time, the application clones an existing object and modifies only the necessary properties.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;understanding-the-prototype-pattern&quot;&gt;Understanding the Prototype Pattern&lt;/h3&gt;
&lt;p&gt;The Prototype pattern works around a simple idea:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;Create new objects by cloning existing objects.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A prototype object acts as a template. When a new object is needed, the application copies the prototype and customizes it.&lt;/p&gt;
&lt;h4 id=&quot;structure&quot;&gt;Structure&lt;/h4&gt;
&lt;p&gt;The following class diagram illustrates the Prototype Pattern:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;denied:attachment:image&quot; alt=&quot;Prototype Pattern Structure&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Components:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prototype (BasicCar)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Declares a cloning method.&lt;/li&gt;
&lt;li&gt;Defines the common interface for cloning.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Concrete Prototypes (Nano, Ford)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Implement the cloning operation.&lt;/li&gt;
&lt;li&gt;Return copies of themselves.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Client (Program)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses prototypes to create new objects.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&quot;prototype-pattern-example-in-c&quot;&gt;Prototype Pattern Example in C&lt;/h2&gt;
&lt;h3 id=&quot;abstract-prototype&quot;&gt;Abstract Prototype&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public abstract class BasicCar
{
    public string ModelName { get; set; }
    public int Price { get; set; }

    public static int SetPrice()
    {
        int price = 0;
        Random r = new Random();
        int p = r.Next(200000, 500000);

        price = p;
        return price;
    }

    public abstract BasicCar Clone();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;explanation&quot;&gt;Explanation&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;BasicCar&lt;/code&gt; acts as the prototype base class.&lt;/p&gt;
&lt;p&gt;It contains:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Common properties (&lt;code&gt;ModelName&lt;/code&gt;, &lt;code&gt;Price&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A utility method for generating random prices&lt;/li&gt;
&lt;li&gt;An abstract &lt;code&gt;Clone()&lt;/code&gt; method that subclasses must implement&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;concrete-prototype-ford&quot;&gt;Concrete Prototype: Ford&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class Ford : BasicCar
{
    public Ford(string m)
    {
        ModelName = m;
    }

    public override BasicCar Clone()
    {
        return (Ford)this.MemberwiseClone();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;explanation-1&quot;&gt;Explanation&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;Ford&lt;/code&gt; class inherits from &lt;code&gt;BasicCar&lt;/code&gt; and implements the cloning logic.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;return (Ford)this.MemberwiseClone();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a copy of the current object using the built-in &lt;code&gt;MemberwiseClone()&lt;/code&gt; method.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;client-code&quot;&gt;Client Code&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;Console.WriteLine(&quot;***Prototype Pattern Demo***\n&quot;);

// Base or Original Copy
BasicCar nano_base = new Nano(&quot;Green Nano&quot;)
{
    Price = 100000
};

BasicCar ford_base = new Ford(&quot;Ford Yellow&quot;)
{
    Price = 500000
};

BasicCar bc1;

// Nano Clone
bc1 = nano_base.Clone();
bc1.Price = nano_base.Price + BasicCar.SetPrice();

Console.WriteLine(
    &quot;Car is: {0}, and it's price is Rs. {1}&quot;,
    bc1.ModelName,
    bc1.Price);

// Ford Clone
bc1 = ford_base.Clone();
bc1.Price = ford_base.Price + BasicCar.SetPrice();

Console.WriteLine(
    &quot;Car is: {0}, and it's price is Rs. {1}&quot;,
    bc1.ModelName,
    bc1.Price);

Console.ReadLine();
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;how-the-program-works&quot;&gt;How the Program Works&lt;/h3&gt;
&lt;h4 id=&quot;step-1-create-prototype-objects&quot;&gt;Step 1: Create Prototype Objects&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;BasicCar nano_base = new Nano(&quot;Green Nano&quot;);
BasicCar ford_base = new Ford(&quot;Ford Yellow&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These objects become the prototypes.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-2-clone-instead-of-creating&quot;&gt;Step 2: Clone Instead of Creating&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;bc1 = nano_base.Clone();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rather than creating a new &lt;code&gt;Nano&lt;/code&gt;, we copy the existing one.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-3-customize-the-clone&quot;&gt;Step 3: Customize the Clone&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;bc1.Price = nano_base.Price + BasicCar.SetPrice();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The cloned object receives a different price while keeping the original model information.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-4-repeat-for-other-car-types&quot;&gt;Step 4: Repeat for Other Car Types&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;bc1 = ford_base.Clone();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The same cloning mechanism works for any prototype implementation.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;understanding-memberwiseclone-&quot;&gt;Understanding &lt;code&gt;MemberwiseClone()&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The Prototype pattern in C# often relies on:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;this.MemberwiseClone();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;MemberwiseClone()&lt;/code&gt; is a protected method of &lt;code&gt;System.Object&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It creates a &lt;strong&gt;shallow copy&lt;/strong&gt; of the current object.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var copy = this.MemberwiseClone();
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;what-gets-copied-&quot;&gt;What Gets Copied?&lt;/h4&gt;
&lt;h5 id=&quot;value-types&quot;&gt;Value Types&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;int
bool
double
struct
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are copied by value.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Original ----&amp;gt; 100
Clone    ----&amp;gt; 100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Changes to one do not affect the other.&lt;/p&gt;
&lt;hr&gt;
&lt;h5 id=&quot;reference-types&quot;&gt;Reference Types&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;List&amp;lt;string&amp;gt;
Address
Customer
Array
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Only the reference is copied.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Original ---&amp;gt; Address Object
Clone ------^
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both objects point to the same instance.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;shallow-copy-vs-deep-copy&quot;&gt;Shallow Copy vs Deep Copy&lt;/h2&gt;
&lt;p&gt;The following comparison summarizes the difference:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Type         &lt;/th&gt;&lt;th&gt;Meaning&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Shallow Copy &lt;/td&gt;&lt;td&gt;Copies fields, but references stay shared&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Deep Copy    &lt;/td&gt;&lt;td&gt;Creates completely independent copies of all nested objects&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;shallow-copy&quot;&gt;Shallow Copy&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public Person Clone()
{
    return (Person)this.MemberwiseClone();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Result:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Person A ---&amp;gt; Address
Person B ---^
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both persons share the same address object.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;deep-copy&quot;&gt;Deep Copy&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public Person DeepClone()
{
    var copy = (Person)this.MemberwiseClone();

    copy.Address = new Address
    {
        City = this.Address.City
    };

    return copy;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Result:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Person A ---&amp;gt; Address A
Person B ---&amp;gt; Address B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now each object has its own independent nested objects.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;is-memberwiseclone-safe-&quot;&gt;Is &lt;code&gt;MemberwiseClone()&lt;/code&gt; Safe?&lt;/h2&gt;
&lt;h4 id=&quot;yes-when-&quot;&gt;Yes, when:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Objects contain only value types.&lt;/li&gt;
&lt;li&gt;Shared references are acceptable.&lt;/li&gt;
&lt;li&gt;You manually clone reference-type members afterward.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;be-careful-when-&quot;&gt;Be Careful When:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Objects contain collections.&lt;/li&gt;
&lt;li&gt;Objects contain nested objects.&lt;/li&gt;
&lt;li&gt;Complete independence is required.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In those situations, implement a deep clone.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;benefits-of-the-prototype-pattern&quot;&gt;Benefits of the Prototype Pattern&lt;/h2&gt;
&lt;h4 id=&quot;1-faster-object-creation&quot;&gt;1. Faster Object Creation&lt;/h4&gt;
&lt;p&gt;Cloning is often faster than constructing and initializing complex objects.&lt;/p&gt;
&lt;h4 id=&quot;2-reduces-repeated-initialization&quot;&gt;2. Reduces Repeated Initialization&lt;/h4&gt;
&lt;p&gt;Objects with many properties can be copied rather than rebuilt.&lt;/p&gt;
&lt;h4 id=&quot;3-simplifies-object-creation&quot;&gt;3. Simplifies Object Creation&lt;/h4&gt;
&lt;p&gt;Clients don't need to know all construction details.&lt;/p&gt;
&lt;h4 id=&quot;4-supports-runtime-object-creation&quot;&gt;4. Supports Runtime Object Creation&lt;/h4&gt;
&lt;p&gt;New objects can be created dynamically from existing prototypes.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;drawbacks&quot;&gt;Drawbacks&lt;/h2&gt;
&lt;h4 id=&quot;1-deep-cloning-can-be-complex&quot;&gt;1. Deep Cloning Can Be Complex&lt;/h4&gt;
&lt;p&gt;Nested object graphs require extra cloning logic.&lt;/p&gt;
&lt;h4 id=&quot;2-shared-references-can-cause-bugs&quot;&gt;2. Shared References Can Cause Bugs&lt;/h4&gt;
&lt;p&gt;Using shallow copies incorrectly may lead to unexpected side effects.&lt;/p&gt;
&lt;h4 id=&quot;3-maintenance-overhead&quot;&gt;3. Maintenance Overhead&lt;/h4&gt;
&lt;p&gt;Every new field may require updates to deep-cloning logic.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;when-should-you-use-the-prototype-pattern-&quot;&gt;When Should You Use the Prototype Pattern?&lt;/h2&gt;
&lt;p&gt;Use Prototype when:&lt;/p&gt;
&lt;p&gt;✅ Object creation is expensive&lt;/p&gt;
&lt;p&gt;✅ Many similar objects are required&lt;/p&gt;
&lt;p&gt;✅ Initialization logic is complex&lt;/p&gt;
&lt;p&gt;✅ You need runtime object duplication&lt;/p&gt;
&lt;p&gt;Examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Game character templates&lt;/li&gt;
&lt;li&gt;Vehicle configurations&lt;/li&gt;
&lt;li&gt;Document templates&lt;/li&gt;
&lt;li&gt;UI component libraries&lt;/li&gt;
&lt;li&gt;Product catalogs&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;Prototype Design Pattern&lt;/strong&gt; is a powerful creational pattern that creates new objects by cloning existing ones. In C#, it is commonly implemented using &lt;code&gt;MemberwiseClone()&lt;/code&gt;, which provides a fast and efficient way to perform shallow copies.&lt;/p&gt;
&lt;p&gt;For simple objects, shallow cloning is often sufficient. However, when objects contain nested reference types, a &lt;strong&gt;deep clone&lt;/strong&gt; should be implemented to ensure complete independence between the original object and its clone.&lt;/p&gt;
&lt;p&gt;In the car example, &lt;code&gt;Nano&lt;/code&gt; and &lt;code&gt;Ford&lt;/code&gt; act as concrete prototypes, while &lt;code&gt;BasicCar&lt;/code&gt; defines the cloning contract. The client can create new car instances efficiently by cloning existing prototypes instead of constructing them from scratch.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21133/prototype-design-pattern-in-c-cloning-objects-efficiently</guid>
<pubDate>Mon, 22 Jun 2026 08:00:27 +0000</pubDate>
</item>
<item>
<title>Exploring a Practical Billing API Built with Modern .NET Patterns</title>
<link>https://coderlegion.com/20781/exploring-a-practical-billing-api-built-with-modern-net-patterns</link>
<description>&lt;h2 id=&quot;exploring-a-practical-billing-api-built-with-modern-net-patterns&quot;&gt;Exploring a Practical Billing API Built with Modern .NET Patterns&lt;/h2&gt;
&lt;p&gt;The Billing repository (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/Billing&quot;&gt;https://github.com/stevsharp/Billing&lt;/a&gt;) is a practical example of how several common .NET architectural patterns can be combined within a billing-focused application.&lt;/p&gt;
&lt;p&gt;Built with ASP.NET Core (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/aspnet/core/&quot;&gt;https://learn.microsoft.com/aspnet/core/&lt;/a&gt;) and Entity Framework Core (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/ef/core/&quot;&gt;https://learn.microsoft.com/ef/core/&lt;/a&gt;), the project demonstrates approaches that many teams use when building modern backend services.&lt;/p&gt;
&lt;h3 id=&quot;a-clear-project-structure&quot;&gt;A Clear Project Structure&lt;/h3&gt;
&lt;p&gt;The repository follows the principles of Clean Architecture, an approach that encourages clear separation between business logic, infrastructure, and application concerns.&lt;/p&gt;
&lt;p&gt;Learn more about Clean Architecture:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/jasontaylordev/CleanArchitecture&quot;&gt;https://github.com/jasontaylordev/CleanArchitecture&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This separation helps keep responsibilities organized and makes it easier to understand where different pieces of functionality belong.&lt;/p&gt;
&lt;h3 id=&quot;focus-on-business-rules&quot;&gt;Focus on Business Rules&lt;/h3&gt;
&lt;p&gt;The project incorporates concepts from Domain-Driven Design (DDD), placing important business rules within the domain layer rather than spreading them across controllers and infrastructure code.&lt;/p&gt;
&lt;p&gt;Learn more about Domain-Driven Design:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://martinfowler.com/bliki/DomainDrivenDesign.html&quot;&gt;https://martinfowler.com/bliki/DomainDrivenDesign.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This approach helps make business behavior more explicit and encourages a design where domain concepts remain central to the application.&lt;/p&gt;
&lt;h3 id=&quot;using-cqrs-in-a-simple-way&quot;&gt;Using CQRS in a Simple Way&lt;/h3&gt;
&lt;p&gt;The repository demonstrates Command Query Responsibility Segregation (CQRS), separating commands (operations that modify data) from queries (operations that retrieve data).&lt;/p&gt;
&lt;p&gt;Learn more about CQRS:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://martinfowler.com/bliki/CQRS.html&quot;&gt;https://martinfowler.com/bliki/CQRS.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For developers interested in learning CQRS, the project provides a straightforward implementation that is relatively easy to follow.&lt;/p&gt;
&lt;h3 id=&quot;reliable-event-handling&quot;&gt;Reliable Event Handling&lt;/h3&gt;
&lt;p&gt;The implementation includes the Outbox Pattern, a common technique used to improve reliability when publishing events from business operations.&lt;/p&gt;
&lt;p&gt;Learn more about the Transactional Outbox Pattern:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://microservices.io/patterns/data/transactional-outbox.html&quot;&gt;https://microservices.io/patterns/data/transactional-outbox.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This pattern helps address scenarios where database changes and event publication need to remain consistent.&lt;/p&gt;
&lt;h3 id=&quot;multi-tenant-considerations&quot;&gt;Multi-Tenant Considerations&lt;/h3&gt;
&lt;p&gt;Many SaaS applications need to support multiple customers while keeping their data isolated. The project includes tenant-aware functionality using authentication claims and data filtering, providing a useful reference for developers exploring multi-tenant architectures.&lt;/p&gt;
&lt;p&gt;Learn more about multitenancy:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/azure/architecture/guide/multitenant/overview&quot;&gt;https://learn.microsoft.com/azure/architecture/guide/multitenant/overview&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;working-with-entity-framework-core&quot;&gt;Working with Entity Framework Core&lt;/h3&gt;
&lt;p&gt;The application uses Entity Framework Core for data access and persistence. Developers looking for practical examples of EF Core usage alongside architectural patterns may find the implementation useful to study.&lt;/p&gt;
&lt;p&gt;Learn more about EF Core:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/ef/core/&quot;&gt;https://learn.microsoft.com/ef/core/&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;building-on-asp-net-core&quot;&gt;Building on ASP.NET Core&lt;/h3&gt;
&lt;p&gt;The API layer is built with ASP.NET Core, Microsoft's framework for building web APIs and cloud-native applications.&lt;/p&gt;
&lt;p&gt;Learn more about ASP.NET Core:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/aspnet/core/&quot;&gt;https://learn.microsoft.com/aspnet/core/&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;good-learning-resource&quot;&gt;Good Learning Resource&lt;/h3&gt;
&lt;p&gt;One of the strengths of the repository is that it brings together several widely used concepts and patterns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Clean Architecture&lt;/li&gt;
&lt;li&gt;Domain-Driven Design (DDD)&lt;/li&gt;
&lt;li&gt;CQRS&lt;/li&gt;
&lt;li&gt;Transactional Outbox Pattern&lt;/li&gt;
&lt;li&gt;Multi-Tenant Design&lt;/li&gt;
&lt;li&gt;JWT Authentication&lt;/li&gt;
&lt;li&gt;Automated Testing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rather than presenting these ideas in isolation, the project shows how they can work together in a cohesive application.&lt;/p&gt;
&lt;h3 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;The Billing repository is a helpful reference for developers who want to explore modern .NET application design. It demonstrates several architectural patterns in a practical context while remaining approachable enough to study and adapt for other projects.&lt;/p&gt;
&lt;p&gt;Repository:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/Billing&quot;&gt;https://github.com/stevsharp/Billing&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Whether you're learning these concepts for the first time or looking for implementation examples, the project offers a useful starting point for further exploration.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20781/exploring-a-practical-billing-api-built-with-modern-net-patterns</guid>
<pubDate>Wed, 17 Jun 2026 05:58:06 +0000</pubDate>
</item>
<item>
<title>Reflection architectural pattern</title>
<link>https://coderlegion.com/20625/reflection-architectural-pattern</link>
<description>&lt;p&gt;I published an article diving into the reflection architectural pattern. If you've ever wondered how Spring Boot uses annotations to magically wire your dependencies, or how ORMs map database fields without explicit code, reflection is the answer. I break down how this pattern actually works, show practical examples, and discuss when you should and shouldn't use it.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/@Victorldev/reflection-architectural-pattern-2249a52a2c81?postPublishedType=repub&quot;&gt;Read the full article&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20625/reflection-architectural-pattern</guid>
<pubDate>Mon, 15 Jun 2026 08:55:35 +0000</pubDate>
</item>
<item>
<title>Refactoring a Simple C# Method Step by Step</title>
<link>https://coderlegion.com/16839/refactoring-a-simple-c-method-step-by-step</link>
<description>&lt;h2 id=&quot;refactoring-a-simple-c-method-step-by-step&quot;&gt;Refactoring a Simple C# Method Step by Step&lt;/h2&gt;
&lt;p&gt;Refactoring is not about changing what the code does. It is about improving how the code is written.&lt;/p&gt;
&lt;p&gt;The goal is to make the code easier to read, easier to test, and easier to maintain.&lt;/p&gt;
&lt;p&gt;In this tutorial, we will refactor a simple user registration method step by step.&lt;/p&gt;
&lt;h3 id=&quot;the-original-code&quot;&gt;The Original Code&lt;/h3&gt;
&lt;p&gt;Imagine we have a method like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public void RegisterUser(string username, string password, int age, string email)
{
    if (username == null || username == &quot;&quot;)
    {
        throw new Exception(&quot;Username is required&quot;);
    }

    if (password == null || password == &quot;&quot;)
    {
        throw new Exception(&quot;Password is required&quot;);
    }

    if (password.Length &amp;lt; 8)
    {
        throw new Exception(&quot;Password must be at least 8 characters&quot;);
    }

    if (age &amp;lt; 18 || age &amp;gt; 120)
    {
        throw new Exception(&quot;Invalid age&quot;);
    }

    if (email == null || !email.Contains(&quot;@&quot;) || !email.Contains(&quot;.&quot;))
    {
        throw new Exception(&quot;Invalid email&quot;);
    }

    Console.WriteLine(&quot;User registered successfully!&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code works, but it has some problems.&lt;/p&gt;
&lt;h3 id=&quot;what-is-wrong-with-this-code-&quot;&gt;What Is Wrong With This Code?&lt;/h3&gt;
&lt;p&gt;The method has too many responsibilities.&lt;/p&gt;
&lt;p&gt;It validates the username, password, age, and email. It also performs the registration logic.&lt;/p&gt;
&lt;p&gt;This makes the method harder to read and harder to test.&lt;/p&gt;
&lt;p&gt;Some other issues are:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Validation logic is mixed with business logic.
The method is becoming too long.
The same type of checks are repeated.
The validation rules cannot easily be reused.
The method uses primitive values everywhere, like string and int.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a common problem called &lt;strong&gt;primitive obsession&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It means we are using simple types like &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, or &lt;code&gt;decimal&lt;/code&gt; for values that have business meaning.&lt;/p&gt;
&lt;p&gt;For example, an email is not just a string. A password is not just a string. They both have rules.&lt;/p&gt;
&lt;h3 id=&quot;step-1-use-guard-clauses&quot;&gt;Step 1: Use Guard Clauses&lt;/h3&gt;
&lt;p&gt;A guard clause is a validation check placed at the beginning of a method.&lt;/p&gt;
&lt;p&gt;If the input is invalid, the method stops immediately.&lt;/p&gt;
&lt;p&gt;This is also called &lt;strong&gt;Fail Fast&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public void RegisterUser(string username, string password, int age, string email)
{
    ArgumentException.ThrowIfNullOrWhiteSpace(username);
    ArgumentException.ThrowIfNullOrWhiteSpace(password);

    if (password.Length &amp;lt; 8)
        throw new ArgumentException(&quot;Password must be at least 8 characters.&quot;, nameof(password));

    if (age is &amp;lt; 18 or &amp;gt; 120)
        throw new ArgumentOutOfRangeException(nameof(age), &quot;Age must be between 18 and 120.&quot;);

    if (!IsValidEmail(email))
        throw new FormatException(&quot;Invalid email format.&quot;);

    Console.WriteLine(&quot;User registered successfully!&quot;);
}

private bool IsValidEmail(string email)
{
    return !string.IsNullOrWhiteSpace(email)
           &amp;amp;&amp;amp; email.Contains(&quot;@&quot;)
           &amp;amp;&amp;amp; email.Contains(&quot;.&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This version is already better.&lt;/p&gt;
&lt;p&gt;We improved the code by using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Guard Clauses
Extract Method
Fail Fast
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;IsValidEmail&lt;/code&gt; method makes the code easier to read because the email validation is separated from the main method.&lt;/p&gt;
&lt;h3 id=&quot;step-2-use-a-request-object&quot;&gt;Step 2: Use a Request Object&lt;/h3&gt;
&lt;p&gt;The method currently receives four parameters.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public void RegisterUser(string username, string password, int age, string email)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is fine for small examples, but in real applications, this can become messy.&lt;/p&gt;
&lt;p&gt;A better approach is to group the input into a request object.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class UserRegistrationRequest
{
    public string Username { get; set; } = string.Empty;
    public string Password { get; set; } = string.Empty;
    public int Age { get; set; }
    public string Email { get; set; } = string.Empty;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the method becomes cleaner:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public void RegisterUser(UserRegistrationRequest request)
{
    ArgumentNullException.ThrowIfNull(request);

    ArgumentException.ThrowIfNullOrWhiteSpace(request.Username);
    ArgumentException.ThrowIfNullOrWhiteSpace(request.Password);

    if (request.Password.Length &amp;lt; 8)
        throw new ArgumentException(&quot;Password must be at least 8 characters.&quot;, nameof(request.Password));

    if (request.Age is &amp;lt; 18 or &amp;gt; 120)
        throw new ArgumentOutOfRangeException(nameof(request.Age), &quot;Age must be between 18 and 120.&quot;);

    if (!IsValidEmail(request.Email))
        throw new FormatException(&quot;Invalid email format.&quot;);

    Console.WriteLine(&quot;User registered successfully!&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This uses the &lt;strong&gt;DTO pattern&lt;/strong&gt;, or &lt;strong&gt;Data Transfer Object&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A DTO is useful when we want to pass data between layers, for example from the UI to the application layer.&lt;/p&gt;
&lt;h3 id=&quot;step-3-extract-validation-into-a-validator&quot;&gt;Step 3: Extract Validation Into a Validator&lt;/h3&gt;
&lt;p&gt;The registration method still contains validation logic.&lt;/p&gt;
&lt;p&gt;We can move that logic into a separate class.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class UserRegistrationValidator
{
    public void Validate(UserRegistrationRequest request)
    {
        ArgumentNullException.ThrowIfNull(request);

        ArgumentException.ThrowIfNullOrWhiteSpace(request.Username);
        ArgumentException.ThrowIfNullOrWhiteSpace(request.Password);

        if (request.Password.Length &amp;lt; 8)
            throw new ArgumentException(&quot;Password must be at least 8 characters.&quot;, nameof(request.Password));

        if (request.Age is &amp;lt; 18 or &amp;gt; 120)
            throw new ArgumentOutOfRangeException(nameof(request.Age), &quot;Age must be between 18 and 120.&quot;);

        if (!IsValidEmail(request.Email))
            throw new FormatException(&quot;Invalid email format.&quot;);
    }

    private static bool IsValidEmail(string email)
    {
        return !string.IsNullOrWhiteSpace(email)
               &amp;amp;&amp;amp; email.Contains(&quot;@&quot;)
               &amp;amp;&amp;amp; email.Contains(&quot;.&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the registration method becomes very small:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public void RegisterUser(UserRegistrationRequest request)
{
    var validator = new UserRegistrationValidator();

    validator.Validate(request);

    Console.WriteLine(&quot;User registered successfully!&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is much better.&lt;/p&gt;
&lt;p&gt;The service now focuses on the registration process.&lt;/p&gt;
&lt;p&gt;The validator focuses on validation.&lt;/p&gt;
&lt;p&gt;This follows the &lt;strong&gt;Single Responsibility Principle&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;step-4-inject-the-validator&quot;&gt;Step 4: Inject the Validator&lt;/h3&gt;
&lt;p&gt;Creating the validator inside the method works, but it is not ideal for testing.&lt;/p&gt;
&lt;p&gt;This line creates a direct dependency:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var validator = new UserRegistrationValidator();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A better approach is to inject the validator through the constructor.&lt;/p&gt;
&lt;p&gt;First, create an interface:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public interface IUserRegistrationValidator
{
    void Validate(UserRegistrationRequest request);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then update the validator:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class UserRegistrationValidator : IUserRegistrationValidator
{
    public void Validate(UserRegistrationRequest request)
    {
        ArgumentNullException.ThrowIfNull(request);

        ArgumentException.ThrowIfNullOrWhiteSpace(request.Username);
        ArgumentException.ThrowIfNullOrWhiteSpace(request.Password);

        if (request.Password.Length &amp;lt; 8)
            throw new ArgumentException(&quot;Password must be at least 8 characters.&quot;, nameof(request.Password));

        if (request.Age is &amp;lt; 18 or &amp;gt; 120)
            throw new ArgumentOutOfRangeException(nameof(request.Age), &quot;Age must be between 18 and 120.&quot;);

        if (!IsValidEmail(request.Email))
            throw new FormatException(&quot;Invalid email format.&quot;);
    }

    private static bool IsValidEmail(string email)
    {
        return !string.IsNullOrWhiteSpace(email)
               &amp;amp;&amp;amp; email.Contains(&quot;@&quot;)
               &amp;amp;&amp;amp; email.Contains(&quot;.&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the service can receive the validator from dependency injection:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class UserRegistrationService
{
    private readonly IUserRegistrationValidator _validator;

    public UserRegistrationService(IUserRegistrationValidator validator)
    {
        _validator = validator;
    }

    public void RegisterUser(UserRegistrationRequest request)
    {
        _validator.Validate(request);

        Console.WriteLine(&quot;User registered successfully!&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This improves testability because we can mock the validator in unit tests.&lt;/p&gt;
&lt;h3 id=&quot;step-5-use-value-objects-for-stronger-domain-rules&quot;&gt;Step 5: Use Value Objects for Stronger Domain Rules&lt;/h3&gt;
&lt;p&gt;In a more domain-driven design approach, we can create value objects.&lt;/p&gt;
&lt;p&gt;For example, instead of passing email as a plain string, we create an &lt;code&gt;Email&lt;/code&gt; type.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public sealed record Email
{
    public string Value { get; }

    public Email(string value)
    {
        if (string.IsNullOrWhiteSpace(value) ||
            !value.Contains(&quot;@&quot;) ||
            !value.Contains(&quot;.&quot;))
        {
            throw new FormatException(&quot;Invalid email format.&quot;);
        }

        Value = value;
    }

    public override string ToString() =&amp;gt; Value;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can do the same for password.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public sealed record Password
{
    public string Value { get; }

    public Password(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
            throw new ArgumentException(&quot;Password is required.&quot;, nameof(value));

        if (value.Length &amp;lt; 8)
            throw new ArgumentException(&quot;Password must be at least 8 characters.&quot;, nameof(value));

        Value = value;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the method can receive stronger types:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public void RegisterUser(string username, Password password, int age, Email email)
{
    ArgumentException.ThrowIfNullOrWhiteSpace(username);

    if (age is &amp;lt; 18 or &amp;gt; 120)
        throw new ArgumentOutOfRangeException(nameof(age), &quot;Age must be between 18 and 120.&quot;);

    Console.WriteLine(&quot;User registered successfully!&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is powerful because invalid email or password values cannot exist in the system.&lt;/p&gt;
&lt;p&gt;The validation happens when the object is created.&lt;/p&gt;
&lt;h3 id=&quot;step-6-use-the-specification-pattern-for-complex-rules&quot;&gt;Step 6: Use the Specification Pattern for Complex Rules&lt;/h3&gt;
&lt;p&gt;Sometimes validation rules become more complex.&lt;/p&gt;
&lt;p&gt;For example, age validation may start simple:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;if (age is &amp;lt; 18 or &amp;gt; 120)
    throw new ArgumentOutOfRangeException(nameof(age));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But later, the rule may change depending on the country, user type, subscription, product, or business scenario.&lt;/p&gt;
&lt;p&gt;In that case, we can move the rule into a separate specification class.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Specification Pattern&lt;/strong&gt; allows us to define business rules as reusable objects.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public interface ISpecification&amp;lt;T&amp;gt;
{
    bool IsSatisfiedBy(T entity);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can create a specific rule for age validation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class AgeSpecification : ISpecification&amp;lt;int&amp;gt;
{
    public bool IsSatisfiedBy(int age)
    {
        return age is &amp;gt;= 18 and &amp;lt;= 120;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we can use it inside our validator:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class UserRegistrationValidator
{
    private readonly ISpecification&amp;lt;int&amp;gt; _ageSpecification;

    public UserRegistrationValidator(ISpecification&amp;lt;int&amp;gt; ageSpecification)
    {
        _ageSpecification = ageSpecification;
    }

    public void Validate(UserRegistrationRequest request)
    {
        ArgumentNullException.ThrowIfNull(request);

        ArgumentException.ThrowIfNullOrWhiteSpace(request.Username);
        ArgumentException.ThrowIfNullOrWhiteSpace(request.Password);

        if (request.Password.Length &amp;lt; 8)
            throw new ArgumentException(&quot;Password must be at least 8 characters.&quot;, nameof(request.Password));

        if (!_ageSpecification.IsSatisfiedBy(request.Age))
            throw new ArgumentOutOfRangeException(nameof(request.Age), &quot;Age must be between 18 and 120.&quot;);

        if (!IsValidEmail(request.Email))
            throw new FormatException(&quot;Invalid email format.&quot;);
    }

    private static bool IsValidEmail(string email)
    {
        return !string.IsNullOrWhiteSpace(email)
               &amp;amp;&amp;amp; email.Contains(&quot;@&quot;)
               &amp;amp;&amp;amp; email.Contains(&quot;.&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives us a cleaner and more flexible design.&lt;/p&gt;
&lt;p&gt;If the age rule changes later, we do not need to modify the validator directly. We can create a new specification.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class AdultUserSpecification : ISpecification&amp;lt;UserRegistrationRequest&amp;gt;
{
    public bool IsSatisfiedBy(UserRegistrationRequest request)
    {
        return request.Age &amp;gt;= 18;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or we can create a more complete business rule:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class ValidUserRegistrationSpecification : ISpecification&amp;lt;UserRegistrationRequest&amp;gt;
{
    public bool IsSatisfiedBy(UserRegistrationRequest request)
    {
        return !string.IsNullOrWhiteSpace(request.Username)
               &amp;amp;&amp;amp; !string.IsNullOrWhiteSpace(request.Password)
               &amp;amp;&amp;amp; request.Password.Length &amp;gt;= 8
               &amp;amp;&amp;amp; request.Age is &amp;gt;= 18 and &amp;lt;= 120
               &amp;amp;&amp;amp; !string.IsNullOrWhiteSpace(request.Email)
               &amp;amp;&amp;amp; request.Email.Contains(&quot;@&quot;)
               &amp;amp;&amp;amp; request.Email.Contains(&quot;.&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Specification Pattern is useful when:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Business rules are complex.
Rules change often.
Rules must be reused in different places.
Rules need to be combined.
You want to avoid large if statements inside services.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For simple validation, a validator is usually enough.&lt;/p&gt;
&lt;p&gt;For complex domain rules, the Specification Pattern gives you more flexibility.&lt;/p&gt;
&lt;p&gt;Patterns used here:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Specification Pattern
Open/Closed Principle
Single Responsibility Principle
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-7-use-result-instead-of-exceptions&quot;&gt;Step 7: Use Result Instead of Exceptions&lt;/h3&gt;
&lt;p&gt;Exceptions are fine for unexpected errors.&lt;/p&gt;
&lt;p&gt;But in APIs, UI forms, and validation-heavy workflows, returning a result can sometimes be cleaner.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public record Result(bool Success, string Error)
{
    public static Result Ok() =&amp;gt; new(true, string.Empty);

    public static Result Fail(string error) =&amp;gt; new(false, error);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the registration method can return a result:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public Result RegisterUser(UserRegistrationRequest request)
{
    if (string.IsNullOrWhiteSpace(request.Username))
        return Result.Fail(&quot;Username is required.&quot;);

    if (string.IsNullOrWhiteSpace(request.Password))
        return Result.Fail(&quot;Password is required.&quot;);

    if (request.Password.Length &amp;lt; 8)
        return Result.Fail(&quot;Password must be at least 8 characters.&quot;);

    if (request.Age is &amp;lt; 18 or &amp;gt; 120)
        return Result.Fail(&quot;Age must be between 18 and 120.&quot;);

    if (!IsValidEmail(request.Email))
        return Result.Fail(&quot;Invalid email format.&quot;);

    return Result.Ok();
}

private static bool IsValidEmail(string email)
{
    return !string.IsNullOrWhiteSpace(email)
           &amp;amp;&amp;amp; email.Contains(&quot;@&quot;)
           &amp;amp;&amp;amp; email.Contains(&quot;.&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is useful when you do not want validation failures to throw exceptions.&lt;/p&gt;
&lt;p&gt;It is especially useful in:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;APIs
Blazor forms
MVC forms
CQRS command handlers
UI validation flows
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;final-recommended-version&quot;&gt;Final Recommended Version&lt;/h3&gt;
&lt;p&gt;For a real application, a clean structure would look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class UserRegistrationService
{
    private readonly IUserRegistrationValidator _validator;

    public UserRegistrationService(IUserRegistrationValidator validator)
    {
        _validator = validator;
    }

    public void RegisterUser(UserRegistrationRequest request)
    {
        _validator.Validate(request);

        Console.WriteLine(&quot;User registered successfully!&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public interface IUserRegistrationValidator
{
    void Validate(UserRegistrationRequest request);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class UserRegistrationValidator : IUserRegistrationValidator
{
    private readonly ISpecification&amp;lt;int&amp;gt; _ageSpecification;

    public UserRegistrationValidator(ISpecification&amp;lt;int&amp;gt; ageSpecification)
    {
        _ageSpecification = ageSpecification;
    }

    public void Validate(UserRegistrationRequest request)
    {
        ArgumentNullException.ThrowIfNull(request);

        ArgumentException.ThrowIfNullOrWhiteSpace(request.Username);
        ArgumentException.ThrowIfNullOrWhiteSpace(request.Password);

        if (request.Password.Length &amp;lt; 8)
            throw new ArgumentException(&quot;Password must be at least 8 characters.&quot;, nameof(request.Password));

        if (!_ageSpecification.IsSatisfiedBy(request.Age))
            throw new ArgumentOutOfRangeException(nameof(request.Age), &quot;Age must be between 18 and 120.&quot;);

        if (!IsValidEmail(request.Email))
            throw new FormatException(&quot;Invalid email format.&quot;);
    }

    private static bool IsValidEmail(string email)
    {
        return !string.IsNullOrWhiteSpace(email)
               &amp;amp;&amp;amp; email.Contains(&quot;@&quot;)
               &amp;amp;&amp;amp; email.Contains(&quot;.&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public interface ISpecification&amp;lt;T&amp;gt;
{
    bool IsSatisfiedBy(T entity);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class AgeSpecification : ISpecification&amp;lt;int&amp;gt;
{
    public bool IsSatisfiedBy(int age)
    {
        return age is &amp;gt;= 18 and &amp;lt;= 120;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class UserRegistrationRequest
{
    public string Username { get; set; } = string.Empty;
    public string Password { get; set; } = string.Empty;
    public int Age { get; set; }
    public string Email { get; set; } = string.Empty;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;summary-2&quot;&gt;Summary&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Refactoring Technique           &lt;/th&gt;&lt;th&gt;What It Improves&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Guard Clauses                   &lt;/td&gt;&lt;td&gt;Makes validation clearer&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Extract Method                  &lt;/td&gt;&lt;td&gt;Makes code easier to read&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;DTO                             &lt;/td&gt;&lt;td&gt;Groups related input data&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Validator Pattern               &lt;/td&gt;&lt;td&gt;Separates validation from business logic&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Dependency Injection            &lt;/td&gt;&lt;td&gt;Improves testability&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Value Objects                   &lt;/td&gt;&lt;td&gt;Protects domain integrity&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Specification Pattern           &lt;/td&gt;&lt;td&gt;Makes complex rules reusable&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Open/Closed Principle           &lt;/td&gt;&lt;td&gt;Allows adding new rules without changing existing code&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Result Pattern                  &lt;/td&gt;&lt;td&gt;Works well for APIs and UI flows&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Single Responsibility Principle &lt;/td&gt;&lt;td&gt;Keeps classes focused&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;conclusion-2&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;The best refactoring is not always the most advanced one.&lt;/p&gt;
&lt;p&gt;Start simple.&lt;/p&gt;
&lt;p&gt;First, use guard clauses.&lt;/p&gt;
&lt;p&gt;Then extract repeated logic into methods.&lt;/p&gt;
&lt;p&gt;After that, move validation into its own class.&lt;/p&gt;
&lt;p&gt;When the domain becomes more important, consider value objects.&lt;/p&gt;
&lt;p&gt;When business rules become more complex, consider the Specification Pattern.&lt;/p&gt;
&lt;p&gt;And when working with APIs or UI forms, consider returning a result instead of throwing exceptions for expected validation failures.&lt;/p&gt;
&lt;p&gt;A good refactor should make the code easier to understand without making it unnecessarily complicated.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/16839/refactoring-a-simple-c-method-step-by-step</guid>
<pubDate>Wed, 06 May 2026 08:58:22 +0000</pubDate>
</item>
<item>
<title>Understanding the Producer-Consumer Pattern in C#</title>
<link>https://coderlegion.com/16051/understanding-the-producer-consumer-pattern-in-c</link>
<description>&lt;h2 id=&quot;understanding-the-producer-consumer-pattern-in-c-sharp&quot;&gt;Understanding the Producer-Consumer Pattern in C Sharp&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;Producer-Consumer pattern&lt;/strong&gt; is one of the most common patterns in concurrent programming.&lt;/p&gt;
&lt;p&gt;In simple English, it means this:&lt;/p&gt;
&lt;p&gt;One part of the program creates work.&lt;br&gt;
Another part of the program processes that work.&lt;/p&gt;
&lt;p&gt;The part that creates work is called the &lt;strong&gt;Producer&lt;/strong&gt;.&lt;br&gt;
The part that processes work is called the &lt;strong&gt;Consumer&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Between them, there is usually a shared queue.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;private static readonly Queue&amp;lt;int&amp;gt; _queue = new();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The producer adds items into the queue, and the consumer removes items from the queue.&lt;/p&gt;
&lt;h3 id=&quot;a-real-life-example&quot;&gt;A real-life example&lt;/h3&gt;
&lt;p&gt;Imagine a kitchen.&lt;/p&gt;
&lt;p&gt;The chef prepares meals and places them on the counter.&lt;br&gt;
The waiter takes meals from the counter and serves them to customers.&lt;/p&gt;
&lt;p&gt;The chef is the &lt;strong&gt;Producer&lt;/strong&gt;.&lt;br&gt;
The waiter is the &lt;strong&gt;Consumer&lt;/strong&gt;.&lt;br&gt;
The counter is the &lt;strong&gt;Queue&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;But there is one problem.&lt;/p&gt;
&lt;p&gt;The counter has limited space.&lt;/p&gt;
&lt;p&gt;If the counter is full, the chef must wait before placing more meals.&lt;br&gt;
If the counter is empty, the waiter must wait before taking anything.&lt;/p&gt;
&lt;p&gt;That is exactly what the Producer-Consumer pattern solves.&lt;/p&gt;
&lt;p&gt;It helps two parts of a system work together safely, even when they work at different speeds.&lt;/p&gt;
&lt;h3 id=&quot;the-code-example&quot;&gt;The code example&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public sealed class ProducerConsumer
{
    private static readonly Queue&amp;lt;int&amp;gt; _queue = new();

    private const int MaxSize = 5;

    private static readonly SemaphoreSlim _itemsAvailable = new(0);

    private static readonly SemaphoreSlim _spaceAvailable = new(MaxSize);

    private static readonly object _lock = new();

    public async Task Producer(int id, int count)
    {
        for (int i = 0; i &amp;lt; count; i++)
        {
            await _spaceAvailable.WaitAsync();

            lock (_lock)
            {
                _queue.Enqueue(i);

                Console.BackgroundColor = ConsoleColor.Green;
                Console.WriteLine($&quot;Producer {id} produced {i}&quot;);
                Console.ResetColor();
            }

            _itemsAvailable.Release();

            Console.WriteLine($&quot;Producer {id} waiting for space...&quot;);

            await Task.Delay(300);
        }
    }

    public async Task Consumer(int id)
    {
        while (true)
        {
            await _itemsAvailable.WaitAsync();

            int item;

            lock (_lock)
            {
                item = _queue.Dequeue();

                Console.BackgroundColor = ConsoleColor.Red;
                Console.WriteLine($&quot;Consumer {id} consumed {item}&quot;);
                Console.ResetColor();
            }

            _spaceAvailable.Release();

            Console.WriteLine($&quot;Consumer {id} waiting for items...&quot;);

            await Task.Delay(500);
        }
    }

    public async Task Run()
    {
        var producer = Producer(1, 10);

        var consumer = Consumer(1);

        await producer;

        await Task.Delay(3000);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;what-this-code-does&quot;&gt;What this code does&lt;/h3&gt;
&lt;p&gt;This example creates a small queue that can hold up to 5 items.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;private const int MaxSize = 5;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The producer creates numbers and adds them into the queue.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;_queue.Enqueue(i);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The consumer takes numbers from the queue.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;item = _queue.Dequeue();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So the flow is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Producer -&amp;gt; Queue -&amp;gt; Consumer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The producer does not directly give the item to the consumer.&lt;/p&gt;
&lt;p&gt;Instead, it places the item into the queue.&lt;/p&gt;
&lt;p&gt;The consumer then takes the item from the queue when it is ready.&lt;/p&gt;
&lt;p&gt;This is useful because the producer and consumer do not need to work at the exact same speed.&lt;/p&gt;
&lt;h3 id=&quot;why-the-queue-has-a-maximum-size&quot;&gt;Why the queue has a maximum size&lt;/h3&gt;
&lt;p&gt;The queue has a maximum size of 5.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;private const int MaxSize = 5;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That means the producer is not allowed to keep adding items forever.&lt;/p&gt;
&lt;p&gt;If the queue becomes full, the producer must wait.&lt;/p&gt;
&lt;p&gt;This is important because, in real applications, unlimited queues can become dangerous. If a producer creates work faster than consumers can process it, memory usage can grow too much.&lt;/p&gt;
&lt;p&gt;A bounded queue helps apply &lt;strong&gt;back pressure&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Back pressure means:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;The producer is slowed down when the system cannot process work fast enough.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This keeps the system more stable.&lt;/p&gt;
&lt;h3 id=&quot;what-is-semaphoreslim-&quot;&gt;What is &lt;code&gt;SemaphoreSlim&lt;/code&gt;?&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;SemaphoreSlim&lt;/code&gt; is a lightweight synchronization tool in .NET.&lt;/p&gt;
&lt;p&gt;In simple English, it is like a counter that controls how many operations are allowed to continue.&lt;/p&gt;
&lt;p&gt;In this example, we use two semaphores:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;private static readonly SemaphoreSlim _itemsAvailable = new(0);

private static readonly SemaphoreSlim _spaceAvailable = new(MaxSize);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first one is &lt;code&gt;_itemsAvailable&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;private static readonly SemaphoreSlim _itemsAvailable = new(0);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It starts at &lt;code&gt;0&lt;/code&gt; because, at the beginning, the queue is empty.&lt;/p&gt;
&lt;p&gt;So the consumer must wait here:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;await _itemsAvailable.WaitAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Wait until at least one item is available.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The second one is &lt;code&gt;_spaceAvailable&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;private static readonly SemaphoreSlim _spaceAvailable = new(MaxSize);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It starts at &lt;code&gt;MaxSize&lt;/code&gt;, which is &lt;code&gt;5&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That means, at the beginning, the queue has 5 free spaces.&lt;/p&gt;
&lt;p&gt;So the producer waits here:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;await _spaceAvailable.WaitAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Wait until there is free space in the queue.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Microsoft’s documentation explains that &lt;code&gt;Wait&lt;/code&gt; or &lt;code&gt;WaitAsync&lt;/code&gt; enters the semaphore and decreases its count, while &lt;code&gt;Release&lt;/code&gt; increases the count again. When the count reaches zero, new waiters must wait until something releases the semaphore. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/standard/threading/semaphore-and-semaphoreslim?utm_source=chatgpt.com&quot; title=&quot;Semaphore and SemaphoreSlim - .NET&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
&lt;h3 id=&quot;how-the-producer-works&quot;&gt;How the producer works&lt;/h3&gt;
&lt;p&gt;The producer first waits for free space:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;await _spaceAvailable.WaitAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If there is space, it enters the &lt;code&gt;lock&lt;/code&gt; block and adds an item to the queue:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;lock (_lock)
{
    _queue.Enqueue(i);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then it releases &lt;code&gt;_itemsAvailable&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;_itemsAvailable.Release();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tells the consumer:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;One item is now available.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So the producer flow is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Wait for space
Add item to queue
Signal that an item is available
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;how-the-consumer-works&quot;&gt;How the consumer works&lt;/h3&gt;
&lt;p&gt;The consumer first waits for an item:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;await _itemsAvailable.WaitAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If an item is available, it enters the &lt;code&gt;lock&lt;/code&gt; block and removes the item from the queue:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;lock (_lock)
{
    item = _queue.Dequeue();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then it releases &lt;code&gt;_spaceAvailable&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;_spaceAvailable.Release();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tells the producer:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;One space is now free.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So the consumer flow is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Wait for item
Remove item from queue
Signal that space is available
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;why-do-we-still-need-lock-&quot;&gt;Why do we still need &lt;code&gt;lock&lt;/code&gt;?&lt;/h3&gt;
&lt;p&gt;The semaphores control &lt;strong&gt;when&lt;/strong&gt; the producer and consumer can continue.&lt;/p&gt;
&lt;p&gt;But they do not protect the queue itself.&lt;/p&gt;
&lt;p&gt;The queue is shared between the producer and the consumer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;private static readonly Queue&amp;lt;int&amp;gt; _queue = new();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Queue&amp;lt;T&amp;gt;&lt;/code&gt; is not thread-safe by itself. If two threads try to access it at the same time, we can get incorrect behavior.&lt;/p&gt;
&lt;p&gt;That is why we use a lock:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;private static readonly object _lock = new();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;lock&lt;/code&gt; statement makes sure that only one thread enters the protected block at a time. Microsoft’s C# documentation describes &lt;code&gt;lock&lt;/code&gt; as a way to ensure that only a single thread exclusively reads or writes a shared resource. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/lock?utm_source=chatgpt.com&quot; title=&quot;The lock statement - synchronize access to shared resources&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;So this code is protected:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;lock (_lock)
{
    _queue.Enqueue(i);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And this code is also protected:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;lock (_lock)
{
    item = _queue.Dequeue();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One important rule is that we should not use &lt;code&gt;await&lt;/code&gt; inside a &lt;code&gt;lock&lt;/code&gt; block. The C# documentation explicitly says that &lt;code&gt;await&lt;/code&gt; cannot be used inside the body of a &lt;code&gt;lock&lt;/code&gt; statement. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/lock?utm_source=chatgpt.com&quot; title=&quot;The lock statement - synchronize access to shared resources&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;That is why this code is correct:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;await _spaceAvailable.WaitAsync();

lock (_lock)
{
    _queue.Enqueue(i);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The async waiting happens before the lock, not inside it.&lt;/p&gt;
&lt;h3 id=&quot;other-options-instead-of-lock&quot;&gt;Other options instead of &lt;code&gt;lock&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;In this example, &lt;code&gt;lock&lt;/code&gt; is the best simple option.&lt;/p&gt;
&lt;p&gt;But there are other synchronization tools we can use.&lt;/p&gt;
&lt;h3 id=&quot;option-1-monitor&quot;&gt;Option 1: &lt;code&gt;Monitor&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;In C#, &lt;code&gt;lock&lt;/code&gt; is actually a cleaner syntax over &lt;code&gt;Monitor&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;lock (_lock)
{
    _queue.Enqueue(i);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is similar to this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;Monitor.Enter(_lock);

try
{
    _queue.Enqueue(i);
}
finally
{
    Monitor.Exit(_lock);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Microsoft’s documentation explains that &lt;code&gt;Monitor.Enter&lt;/code&gt; and &lt;code&gt;Monitor.Exit&lt;/code&gt; provide functionality equivalent to the C# &lt;code&gt;lock&lt;/code&gt; statement, but the language syntax wraps the logic in a &lt;code&gt;try/finally&lt;/code&gt; block so the monitor is released safely. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-threading-monitor?utm_source=chatgpt.com&quot; title=&quot;System.Threading.Monitor class - .NET&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Monitor&lt;/code&gt; gives you more manual control.&lt;/p&gt;
&lt;p&gt;For example, you can use &lt;code&gt;Monitor.TryEnter&lt;/code&gt; if you do not want to wait forever:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;if (Monitor.TryEnter(_lock))
{
    try
    {
        _queue.Enqueue(i);
    }
    finally
    {
        Monitor.Exit(_lock);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use &lt;code&gt;Monitor&lt;/code&gt; when you need extra control, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Try to enter without waiting forever
Use timeout logic
Manually control entering and exiting
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For normal code, &lt;code&gt;lock&lt;/code&gt; is usually better because it is shorter, cleaner, and less error-prone.&lt;/p&gt;
&lt;h3 id=&quot;option-2-mutex&quot;&gt;Option 2: &lt;code&gt;Mutex&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;A &lt;code&gt;Mutex&lt;/code&gt; also gives exclusive access to one thread at a time.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;private static readonly Mutex _mutex = new();

_mutex.WaitOne();

try
{
    _queue.Enqueue(i);
}
finally
{
    _mutex.ReleaseMutex();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important difference is that a &lt;code&gt;Mutex&lt;/code&gt; can also be used across processes.&lt;/p&gt;
&lt;p&gt;For example, if two different applications on the same machine need to access the same file, a named &lt;code&gt;Mutex&lt;/code&gt; can help coordinate them.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;private static readonly Mutex _mutex = new(false, &quot;Global\\MyProducerConsumerMutex&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Microsoft’s documentation says that &lt;code&gt;Mutex&lt;/code&gt; can provide exclusive access to a resource, uses more system resources than &lt;code&gt;Monitor&lt;/code&gt;, and can synchronize threads in different processes. It also explains that named system mutexes are visible throughout the operating system and can be used to synchronize process activity. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/standard/threading/mutexes&quot; title=&quot;Mutexes - .NET | Microsoft Learn&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;Mutex&lt;/code&gt; when different processes need to coordinate access to the same resource.&lt;/p&gt;
&lt;p&gt;For normal synchronization inside one application, prefer &lt;code&gt;lock&lt;/code&gt; or &lt;code&gt;Monitor&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;why-this-pattern-is-useful&quot;&gt;Why this pattern is useful&lt;/h3&gt;
&lt;p&gt;The Producer-Consumer pattern is useful when one part of the system produces work and another part processes it.&lt;/p&gt;
&lt;p&gt;Some real-world examples are:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Background job processing
Logging systems
File processing
Message queues
Image processing
Email sending
Report generation
Data pipelines
Server request handling
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example, imagine an application that receives uploaded files.&lt;/p&gt;
&lt;p&gt;One part of the application accepts the files.&lt;br&gt;
Another part processes them in the background.&lt;/p&gt;
&lt;p&gt;Instead of processing everything immediately, the application can place each file into a queue.&lt;/p&gt;
&lt;p&gt;Then one or more consumers can process the files one by one.&lt;/p&gt;
&lt;p&gt;This keeps the application responsive.&lt;/p&gt;
&lt;h3 id=&quot;one-issue-with-the-current-code&quot;&gt;One issue with the current code&lt;/h3&gt;
&lt;p&gt;The current consumer runs forever:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;while (true)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is okay for a learning example, but in real production code, we usually want a clean way to stop the consumer.&lt;/p&gt;
&lt;p&gt;For example, we could use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;CancellationToken
A special final message
Channel completion
BlockingCollection.CompleteAdding()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without a stopping mechanism, the consumer keeps waiting forever, even after the producer has finished.&lt;/p&gt;
&lt;h3 id=&quot;alternatives-to-this-approach&quot;&gt;Alternatives to this approach&lt;/h3&gt;
&lt;p&gt;The manual version is great for learning because it shows what happens behind the scenes.&lt;/p&gt;
&lt;p&gt;But in real applications, .NET gives us higher-level tools.&lt;/p&gt;
&lt;h3 id=&quot;alternative-1-channel-lt-t-gt-&quot;&gt;Alternative 1: &lt;code&gt;Channel&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;For modern async code, &lt;code&gt;Channel&amp;lt;T&amp;gt;&lt;/code&gt; is usually the best alternative.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;System.Threading.Channels&lt;/code&gt; provides synchronization structures for passing data between producers and consumers asynchronously. Microsoft’s documentation describes channels as an implementation of the producer-consumer model, where producers asynchronously produce data and consumers asynchronously consume it through a FIFO queue. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/core/extensions/channels&quot; title=&quot;Channels - .NET | Microsoft Learn&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var channel = Channel.CreateBounded&amp;lt;int&amp;gt;(5);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Producer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;await channel.Writer.WriteAsync(item);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Consumer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var item = await channel.Reader.ReadAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With &lt;code&gt;Channel&amp;lt;T&amp;gt;&lt;/code&gt;, we do not need to manually combine:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Queue&amp;lt;T&amp;gt; + lock + SemaphoreSlim
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The channel already handles much of that logic for us.&lt;/p&gt;
&lt;p&gt;A bounded channel can also apply back pressure. When the channel reaches its capacity, the default behavior is that &lt;code&gt;WriteAsync&lt;/code&gt; waits until space becomes available. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/core/extensions/channels&quot; title=&quot;Channels - .NET | Microsoft Learn&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;Channel&amp;lt;T&amp;gt;&lt;/code&gt; when you are building:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Async background workers
Job queues
Pipelines
Producer-consumer services
High-throughput async processing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For modern .NET applications, this is usually the cleanest option.&lt;/p&gt;
&lt;h3 id=&quot;alternative-2-blockingcollection-lt-t-gt-&quot;&gt;Alternative 2: &lt;code&gt;BlockingCollection&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;BlockingCollection&amp;lt;T&amp;gt;&lt;/code&gt; is another producer-consumer tool in .NET.&lt;/p&gt;
&lt;p&gt;Microsoft describes &lt;code&gt;BlockingCollection&amp;lt;T&amp;gt;&lt;/code&gt; as a thread-safe collection that implements the Producer-Consumer pattern, supports concurrent adding and taking from multiple threads, supports optional maximum capacity, and can block when the collection is empty or full. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/standard/collections/thread-safe/blockingcollection-overview?utm_source=chatgpt.com&quot; title=&quot;BlockingCollection Overview - .NET&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var collection = new BlockingCollection&amp;lt;int&amp;gt;(boundedCapacity: 5);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Producer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;collection.Add(item);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Consumer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var item = collection.Take();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is simpler than manually writing semaphore logic.&lt;/p&gt;
&lt;p&gt;However, &lt;code&gt;BlockingCollection&amp;lt;T&amp;gt;&lt;/code&gt; is mainly blocking-based. That makes it useful for classic threaded code, but less ideal for modern async workflows.&lt;/p&gt;
&lt;p&gt;For async code, &lt;code&gt;Channel&amp;lt;T&amp;gt;&lt;/code&gt; is usually a better fit.&lt;/p&gt;
&lt;h3 id=&quot;alternative-3-concurrentqueue-lt-t-gt-&quot;&gt;Alternative 3: &lt;code&gt;ConcurrentQueue&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ConcurrentQueue&amp;lt;T&amp;gt;&lt;/code&gt; is a thread-safe FIFO queue.&lt;/p&gt;
&lt;p&gt;It allows multiple threads to safely enqueue and dequeue items without manually using &lt;code&gt;lock&lt;/code&gt;. Microsoft’s documentation describes it as a thread-safe first in, first out collection and states that its public and protected members are thread-safe and can be used concurrently from multiple threads. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentqueue-1?view=net-10.0&quot; title=&quot;ConcurrentQueue&amp;lt;T&amp;gt; Class (System.Collections.Concurrent) | Microsoft Learn&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;private static readonly ConcurrentQueue&amp;lt;int&amp;gt; _queue = new();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Producer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;_queue.Enqueue(item);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Consumer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;if (_queue.TryDequeue(out var item))
{
    Console.WriteLine(item);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This removes the need for a manual lock.&lt;/p&gt;
&lt;p&gt;However, &lt;code&gt;ConcurrentQueue&amp;lt;T&amp;gt;&lt;/code&gt; does not automatically wait when the queue is empty. It also does not automatically block producers when the queue is full.&lt;/p&gt;
&lt;p&gt;So if you need waiting behavior, you still need another signal mechanism, or you can use &lt;code&gt;Channel&amp;lt;T&amp;gt;&lt;/code&gt; instead.&lt;/p&gt;
&lt;h3 id=&quot;alternative-4-semaphore&quot;&gt;Alternative 4: &lt;code&gt;Semaphore&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Semaphore&lt;/code&gt; is the older and heavier version compared with &lt;code&gt;SemaphoreSlim&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Both are counting synchronization primitives, but &lt;code&gt;Semaphore&lt;/code&gt; can be useful when system-wide synchronization is needed. Microsoft’s documentation explains that a &lt;code&gt;Semaphore&lt;/code&gt; uses &lt;code&gt;WaitOne&lt;/code&gt;, while &lt;code&gt;SemaphoreSlim&lt;/code&gt; uses &lt;code&gt;Wait&lt;/code&gt; or &lt;code&gt;WaitAsync&lt;/code&gt;. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/standard/threading/semaphore-and-semaphoreslim?utm_source=chatgpt.com&quot; title=&quot;Semaphore and SemaphoreSlim - .NET&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;Semaphore&lt;/code&gt; when you need cross-process counting synchronization.&lt;/p&gt;
&lt;p&gt;For normal async code inside one application, prefer &lt;code&gt;SemaphoreSlim&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;which-option-should-you-use-&quot;&gt;Which option should you use?&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Option                                &lt;/th&gt;&lt;th&gt;Best use case&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;lock&lt;/code&gt;                                &lt;/td&gt;&lt;td&gt;Simple in-process protection of shared data&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Monitor&lt;/code&gt;                             &lt;/td&gt;&lt;td&gt;Same idea as &lt;code&gt;lock&lt;/code&gt;, but with more manual control&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Mutex&lt;/code&gt;                               &lt;/td&gt;&lt;td&gt;Cross-process exclusive locking&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;SemaphoreSlim&lt;/code&gt;                       &lt;/td&gt;&lt;td&gt;Async-friendly in-process counting synchronization&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Semaphore&lt;/code&gt;                           &lt;/td&gt;&lt;td&gt;Cross-process counting synchronization&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Queue&amp;lt;T&amp;gt;&lt;/code&gt; + &lt;code&gt;lock&lt;/code&gt; + &lt;code&gt;SemaphoreSlim&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Learning how the pattern works internally&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Channel&amp;lt;T&amp;gt;&lt;/code&gt;                          &lt;/td&gt;&lt;td&gt;Modern async producer-consumer workflows&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;BlockingCollection&amp;lt;T&amp;gt;&lt;/code&gt;               &lt;/td&gt;&lt;td&gt;Classic blocking producer-consumer workflows&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;ConcurrentQueue&amp;lt;T&amp;gt;&lt;/code&gt;                  &lt;/td&gt;&lt;td&gt;Thread-safe queue operations without waiting logic&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For this specific article, the manual approach is perfect because it teaches the mechanics.&lt;/p&gt;
&lt;p&gt;For production code, especially in modern .NET, I would usually prefer &lt;code&gt;Channel&amp;lt;T&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;final-summary&quot;&gt;Final summary&lt;/h3&gt;
&lt;p&gt;The Producer-Consumer pattern helps two parts of an application work together safely.&lt;/p&gt;
&lt;p&gt;The producer creates work.&lt;br&gt;
The consumer processes work.&lt;br&gt;
The queue stores work temporarily.&lt;br&gt;
The semaphores control when each side is allowed to continue.&lt;br&gt;
The lock protects the shared queue from unsafe access.&lt;/p&gt;
&lt;p&gt;In this example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Producer -&amp;gt; Queue -&amp;gt; Consumer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;_spaceAvailable&lt;/code&gt; protects the queue from becoming too full.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;await _spaceAvailable.WaitAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;_itemsAvailable&lt;/code&gt; protects the consumer from reading an empty queue.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;await _itemsAvailable.WaitAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;lock&lt;/code&gt; protects the queue itself.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;lock (_lock)
{
    _queue.Enqueue(i);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Monitor&lt;/code&gt; can be used when we need more manual control.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;Monitor.Enter(_lock);

try
{
    _queue.Enqueue(i);
}
finally
{
    Monitor.Exit(_lock);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Mutex&lt;/code&gt; can be used when different processes need to coordinate access to the same resource.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;private static readonly Mutex _mutex = new(false, &quot;Global\\MyProducerConsumerMutex&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a simple but powerful pattern.&lt;/p&gt;
&lt;p&gt;Once you understand it, tools like &lt;code&gt;Channel&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;BlockingCollection&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;ConcurrentQueue&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;Monitor&lt;/code&gt;, and &lt;code&gt;Mutex&lt;/code&gt; become much easier to understand.&lt;/p&gt;
&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;
&lt;p&gt;Microsoft documentation for &lt;code&gt;SemaphoreSlim&lt;/code&gt;, &lt;code&gt;Semaphore&lt;/code&gt;, &lt;code&gt;lock&lt;/code&gt;, &lt;code&gt;Monitor&lt;/code&gt;, &lt;code&gt;Mutex&lt;/code&gt;, &lt;code&gt;Channel&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;BlockingCollection&amp;lt;T&amp;gt;&lt;/code&gt;, and &lt;code&gt;ConcurrentQueue&amp;lt;T&amp;gt;&lt;/code&gt; was used as the technical reference for this article. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/standard/threading/semaphore-and-semaphoreslim?utm_source=chatgpt.com&quot; title=&quot;Semaphore and SemaphoreSlim - .NET&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16051/understanding-the-producer-consumer-pattern-in-c</guid>
<pubDate>Tue, 28 Apr 2026 09:15:07 +0000</pubDate>
</item>
<item>
<title>Don’t Pass Array or Variables to Laravel Blade Views Instead Do This</title>
<link>https://coderlegion.com/14222/dont-pass-array-or-variables-to-laravel-blade-views-instead-do-this</link>
<description>&lt;p&gt;Are you a Laravel developer. Do you love Blade. It’s simple, expressive, and fits naturally with the framework. But let’s be honest, most projects eventually turn their Blade files into dumping grounds for random variables. The Controllers pass loose data, views end up receiving unstructured data, partials rely on implicit assumptions, and the whole setup becomes fragile and error-prone.&lt;/p&gt;
&lt;h3 id=&quot;the-result-&quot;&gt;The Result?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;No autocomplete.&lt;/li&gt;
&lt;li&gt;Runtime errors from typos.&lt;/li&gt;
&lt;li&gt;Partial views with zero contracts.&lt;/li&gt;
&lt;li&gt;A mess that only grows as the app scales.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It doesn’t have to be this way. Blade files deserve the same contracts and structure we give to APIs, database models, or services. With a few clear practices, you can bring discipline, contracts, and structure to Blade. &lt;/p&gt;
&lt;p&gt;This article will show you the complete approach.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The problem of passing data as arrays or variables in views — solved by introducing &lt;strong&gt;ViewModels&lt;/strong&gt; as contracts.&lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;Autocomplete in Blade&lt;/strong&gt; so your IDE actually helps.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Annotation&lt;/strong&gt; to views and partials bound to their own ViewModels.&lt;/li&gt;
&lt;li&gt;Lock down data structures with &lt;strong&gt;DTO classes&lt;/strong&gt; for strict typing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Establish Principles&lt;/strong&gt; for sustainable Blade development.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;p&gt;Remember, we are only targeting blade for now. Not Twig or Antlr.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Grab a coffee — this is a long read. But by the end, you’ll never look at Blade the same way again.&lt;/p&gt;
&lt;h3 id=&quot;the-crime-scene-what-s-wrong-with-array-soup-in-blade&quot;&gt;The Crime Scene: What’s Wrong With “Array Soup” in Blade&lt;/h3&gt;
&lt;p&gt;Here’s the standard Laravel approach we’ve all seen in controllers.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class ProductController extends Controller
{
    public function index()
    {
		$products = Product::all();
		$categories = Category::all();
		$brand = Brand::all();
        $title = 'Products';
		
		return view('products.index', compact('products','categories','brands','title') );
    }
}

// Or may be

class ProductController extends Controller
{
    public function index()
    {
        return view('products.index', [
            'title' =&amp;gt; 'Products',
            'products' =&amp;gt; Product::all(),
            'categories' =&amp;gt; Category::all(),
            'brand' =&amp;gt; Brand::all(),	
        ]);
    }
}

// Or a little cleaner approach

class ProductController extends Controller
{
    public function index()
    {
		$data = [
            'title' =&amp;gt; 'Products',
            'products' =&amp;gt; Product::all(),
            'categories' =&amp;gt; Category::all(),
            'brand' =&amp;gt; Brand::all(),	
		];
		
		return view('products.index', $data);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here is how we do it for partials.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// pass no data, access product from current blade context inside partials/product.blade.php
@foreach ($products as $product)
    @include('products.partials.product')
@endforeach

// or pass some additional data
@foreach ($products as $product)
    @include('products.partials.product', ['editable' =&amp;gt; $editable])
@endforeach
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally for components.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@foreach ($products as $product)
    &amp;lt;x-product-row :name=&quot;$product-&amp;gt;name&quot; :price =&quot;$product-&amp;gt;price&quot; /&amp;gt;
@endforeach
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-old-lovely-blade&quot;&gt;The Old Lovely Blade&lt;/h3&gt;
&lt;p&gt;Here is how it works well.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;h1&amp;gt;{{ $title }}&amp;lt;/h1&amp;gt;

&amp;lt;ul&amp;gt;
    @foreach ($products as $product)
        &amp;lt;li&amp;gt;{{ $product-&amp;gt;name }} ({{ $product-&amp;gt;price }})&amp;lt;/li&amp;gt;
        {{-- or partial --}}
        @include('products.partials.product')        
        {{-- or component --}}
        &amp;lt;x-product-row :name=&quot;$product-&amp;gt;name&quot; :price =&quot;$product-&amp;gt;price&quot; /&amp;gt;
    @endforeach
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works but ask yourself:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Do you actually remember all the variables passed to your views?&lt;/li&gt;
&lt;li&gt;Do you get autocomplete in Blade to help you catch typos?&lt;/li&gt;
&lt;li&gt;What about partials and includes — are you sure the data they rely on is really there?&lt;/li&gt;
&lt;li&gt;If a teammate edits the controller, how do you even know the view won’t silently break?&lt;/li&gt;
&lt;li&gt;How many times have you opened a Blade file and had no clue what’s in scope?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;1-introducing-viewmodels-contracts-for-your-views&quot;&gt;1. Introducing ViewModels: Contracts for Your Views&lt;/h3&gt;
&lt;p&gt;Instead of throwing arrays around, define a ViewModel, a simple &lt;strong&gt;DTO class.&lt;/strong&gt; It declares exactly what data is expected.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

namespace App\ViewModels;


class ProductsViewModel
{
    public string $title;
    public array $products;
    public array $categories;
    public array $brand;    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And controller.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class ProductController extends Controller
{
    public function index()
    {
        $viewModel = new ProductsViewModel();

        $viewModel-&amp;gt;title = 'Products';
        $viewModel-&amp;gt;products = Product::all();
        $viewModel-&amp;gt;categories = Category::all();
        $viewModel-&amp;gt;brands = Brand::all();        

        return view('products.index', ['model' =&amp;gt; $viewModel]);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Suggestion:&lt;/strong&gt; &lt;em&gt;Always pass view-model with name &lt;strong&gt;model&lt;/strong&gt; for consistancy. This will help in autocomplete&lt;/em&gt;.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now blade file will look like this.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;h1&amp;gt;{{ $model-&amp;gt;title }}&amp;lt;/h1&amp;gt;

&amp;lt;ul&amp;gt;
    @foreach ($model-&amp;gt;products as $product)
        &amp;lt;li&amp;gt;{{ $product-&amp;gt;name }} ({{ $product-&amp;gt;price }})&amp;lt;/li&amp;gt;
        {{-- or partial  --}}
        @include('products.partials.product',['model' =&amp;gt; $product])  
        {{-- or component --}}
        &amp;lt;x-product-row :model=&quot;$product&quot; /&amp;gt;
    @endforeach
&amp;lt;/ul&amp;gt;

@include('partials.brands', ['model' =&amp;gt; $model-&amp;gt;brands])
@include('partials.categories', ['model' =&amp;gt; $model-&amp;gt;categories])
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Suggestion**&lt;/strong&gt;:&lt;strong&gt; For partials and components, always pass data with name &lt;/strong&gt;model** for  consistancy. This will help in autocomplete._&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The contract is explicit.&lt;/li&gt;
&lt;li&gt;Adding or removing data requires updating the ViewModel.&lt;/li&gt;
&lt;li&gt;Each view, partial or component has a single data access point &lt;strong&gt;$model&lt;/strong&gt; to start with.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-installing-extension-for-autocomplete&quot;&gt;2. Installing Extension for Autocomplete&lt;/h3&gt;
&lt;p&gt;This step does not require any code setup but requires installing plugin/extension in your IDE. I use &lt;strong&gt;VS Code&lt;/strong&gt; for Laravel development. I found an excellent extension &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.devsense.com/en&quot;&gt;PHP Tools by DevSense&lt;/a&gt; for VS Code which I used to use in &lt;strong&gt;Visual Studio&lt;/strong&gt; when working with &lt;strong&gt;.Net framework&lt;/strong&gt;. This extension is available for &lt;a rel=&quot;nofollow&quot; href=&quot;https://cursor.com/&quot;&gt;Cursor&lt;/a&gt;, &lt;a rel=&quot;nofollow&quot; href=&quot;https://zed.dev/&quot;&gt;Zed&lt;/a&gt; ,&lt;a rel=&quot;nofollow&quot; href=&quot;https://visualstudio.microsoft.com/&quot;&gt;Visual Studio&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://code.visualstudio.com/Download&quot;&gt;VS Code&lt;/a&gt;. But if you are using a different editor you need to find similar extension.&lt;/p&gt;
&lt;p&gt;Once installed we need to move to next step. Take a look at the &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.devsense.com/en/features&quot;&gt;feature list&lt;/a&gt; to find what's available.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; When installing PHP Tools I had to ditch Intellisense and Intelliphence but I still get all what they provide with PHP Tools.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;3-enabling-autocomplete-in-blade&quot;&gt;3. Enabling Autocomplete in Blade&lt;/h3&gt;
&lt;p&gt;Even with ViewModels, Blade doesn’t automatically know the type of &lt;code&gt;`$model&lt;/code&gt;`. The trick is to annotate it in blade views, partials and components:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// views/products/index.blade.php
@php
    /** @var \App\ViewModels\ProductsViewModel $model */
@endphp

&amp;lt;ul&amp;gt;
    @foreach ($model-&amp;gt;products as $product)
        &amp;lt;li&amp;gt;{{ $product-&amp;gt;name }} ({{ $product-&amp;gt;price }})&amp;lt;/li&amp;gt;
        {{-- or partial  --}}
        @include('products.partials.product')  
        {{-- or component --}}
        &amp;lt;x-product-row :name=&quot;$product-&amp;gt;name&quot; :price =&quot;$product-&amp;gt;price&quot; /&amp;gt;
    @endforeach
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You need to do same in partial or component. Here is partial.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// views/products/partials/product.blade.php
@php
    /** @var \App\Models\Product $model */
@endphp

&amp;lt;span&amp;gt;
    {{ $model-&amp;gt;name }}
&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, a component.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// views/components/product.blade.php
@php
    /** @var \App\View\Components\Product $model */
@endphp

&amp;lt;span&amp;gt;
    {{ $model-&amp;gt;name }}
&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, in VS Code, typing &lt;code&gt;`$model-&amp;gt;`&lt;/code&gt; will bring up real autocomplete suggestions.&lt;/p&gt;
&lt;p&gt;This is a game-changer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No more guessing property names.&lt;/li&gt;
&lt;li&gt;Mistyped variables get caught instantly.&lt;/li&gt;
&lt;li&gt;New developers know exactly what’s available in the view.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4-enforce-types-at-runtime&quot;&gt;4. Enforce Types at Runtime&lt;/h3&gt;
&lt;p&gt;Since now we are passing ViewModels into views, partials or components and defining annonations in each blade file, lets enforce blade to accept specific type of class. To do this i added two files.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;TypedViewFactory&lt;/strong&gt; – A custom view factory that parses &lt;code&gt;`@var`&lt;/code&gt; declarations at the top of Blade files and enforces them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TypedViewServiceProvider&lt;/strong&gt; – Wires everything into Laravel automatically.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Checkout the code &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/raheelshan/typed-blade-views&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Result:&lt;/strong&gt; if you declare a type in a Blade file, Laravel must pass the correct class, array, or collection. Otherwise, it throws an exception.&lt;/p&gt;
&lt;p&gt;Declaring Types in Blade makes views predictable and strict.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Main View&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{{-- products/index.blade.php --}}
@php /** @var App\ViewModels\ProductsViewModel $model */ @endphp
&amp;lt;h1&amp;gt;{{ $model-&amp;gt;title }}&amp;lt;/h1&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Only &lt;code&gt;`ProductsViewModel`&lt;/code&gt; works — anything else throws an exception.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Partial&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{{-- products/partials/product.blade.php --}}
@php 
    /** @var App\Models\Product $model */ 
@endphp
&amp;lt;div&amp;gt;{{ $model-&amp;gt;title }}&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The partial now demands a &lt;code&gt;`Post`&lt;/code&gt; instance, not random data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Component&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// views/components/product.blade.php
@php
    /** @var \App\View\Components\Product $model */
@endphp

&amp;lt;span&amp;gt;
    {{ $model-&amp;gt;name }}
&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The component demands Product class instance now.&lt;/p&gt;
&lt;p&gt;You can do the same with arrays and collection or other primitive types.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// example of array
@php 
    /** @var App\Models\Product[] $model*/ 
@endphp

// Or collection
@php 
    /** @var \Illuminate\Support\Collection&amp;lt;App\Models\Product&amp;gt; $model */ 
@endphp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now if wrong type data is passed it will throw the error.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;View [products] expects $model of type App\ViewModels\ProductsViewModel, 
but got string.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similiarly, partials and components will throw the error if passed wrong type of data.&lt;/p&gt;
&lt;h3 id=&quot;5-going-fully-typed-with-dto-classes&quot;&gt;5. Going Fully Typed With DTO Classes&lt;/h3&gt;
&lt;p&gt;So far, we’ve wrapped data in ViewModels, but what about the data itself? Passing raw &lt;strong&gt;Eloquent&lt;/strong&gt; models to views can be risky — &lt;em&gt;they carry too much baggage and autocomplete will present you a jungle of eloquent methods&lt;/em&gt; along with model properties and we will have to stuggle finding properties again . To counter this issue, let's create &lt;strong&gt;DTO Classes&lt;/strong&gt; to describe exactly what's needed. &lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Since &lt;code&gt;component&lt;/code&gt; itself can act as &lt;code&gt;DTO class&lt;/code&gt; and carry specific properties, it does not need additional DTO class.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Lets create a BaseDTO class to define some common methods.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

namespace App\DTO;

use InvalidArgumentException;

abstract class BaseDTO
{
    public function __construct(array $data = [])
    {
        foreach ($data as $key =&amp;gt; $value) {
            if (!property_exists($this, $key)) {
                throw new InvalidArgumentException(
                    &quot;Property '{$key}' is not defined in &quot; . static::class
                );
            }
            $this-&amp;gt;$key = $value;
        }
    }

    public function __get($name)
    {
        throw new InvalidArgumentException(
            &quot;Tried to access undefined property '{$name}' on &quot; . static::class
        );
    }

    public function __set($name, $value)
    {
        throw new InvalidArgumentException(
            &quot;Tried to set undefined property '{$name}' on &quot; . static::class
        );
    }

    public static function columns(): array
    {
        return array_map(
            fn($prop) =&amp;gt; $prop-&amp;gt;getName(),
            (new \ReflectionClass(static::class))-&amp;gt;getProperties()
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now our actual &lt;strong&gt;SimpleProduct.php&lt;/strong&gt; class to define only specific properties required for blade view or partial.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

namespace App\DTO\Product;

use App\DTO\BaseDTO;

class SimpleProduct extends BaseDTO
{
    public string $name;
    public string $image;
    public float $price;
    public int $stock;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here's our controller now.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class ProductController extends Controller
{
    public function index()
    {
        $viewModel = new ProductsViewModel();

        $viewModel-&amp;gt;products = Product::select(SimpleProduct::columns())-&amp;gt;get();
        // other code      

        return view('products.index', ['model' =&amp;gt; $viewModel]);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And in partial instead of defining Eloquent model as annotation define DTO class as annotation.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// views/products/partials/product.blade.php
@php
    /** @var \App\DTO\Product\SimpleProduct $model */
@endphp

&amp;lt;span&amp;gt;
    {{ $model-&amp;gt;name }}
&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now autcomplete will present you only DTO properties.&lt;/p&gt;
&lt;p&gt;But wait. There's some issue. We passed Eloquent model but annotating DTO class. How's that supposed to work?&lt;/p&gt;
&lt;p&gt;Pay attention on what we selected. Only sepecific columns right. So even though we passed eloquent, due to annotation we will be able to see only DTO properties. But we have to follow one condition.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Suggestion:&lt;/strong&gt; DTO class properties must be same as Eloquent model columns.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;Now here are some points to consider when working this approach.&lt;/p&gt;
&lt;h4 id=&quot;strict-enforcement&quot;&gt;Strict Enforcement&lt;/h4&gt;
&lt;p&gt;If you only select &lt;code&gt;`DTO::columns()`&lt;/code&gt; in your query and pass raw models to Blade, IDE autocomplete will still work (thanks to the DTO annotation). But at runtime, Blade can still access any hidden column, relation, or accessor. To enforce discipline beyond autocomplete, wrap results into DTOs.  &lt;/p&gt;
&lt;h4 id=&quot;schema-drift-protection&quot;&gt;Schema Drift Protection&lt;/h4&gt;
&lt;p&gt;If a DTO includes a property that doesn’t exist in the table, MySQL will fail loudly with “Unknown column error”. This is actually a feature — it ensures your DTOs always stay in sync with the schema instead of drifting silently.&lt;/p&gt;
&lt;h4 id=&quot;losing-eloquent-helpers&quot;&gt;Losing Eloquent Helpers&lt;/h4&gt;
&lt;p&gt;DTOs are deliberately &quot;dumb&quot; carriers. Any accessors, relations, or helpers from your Eloquent models are gone. Prepare and transform data in the controller or service layer before converting to DTOs. Views should only see the minimal contract they need.&lt;/p&gt;
&lt;h4 id=&quot;mapping-overhead&quot;&gt;Mapping Overhead&lt;/h4&gt;
&lt;p&gt;Mapping into DTOs isn’t as heavy as it sounds. With a &lt;code&gt;`BaseDTO`&lt;/code&gt; base and a mapper helper, it’s just one extra line in the query pipeline. Think of it as casting: you do it once, and the rest of your view logic becomes predictable and safe.  &lt;/p&gt;
&lt;h4 id=&quot;a-generic-dto-mapper&quot;&gt;A Generic DTO mapper&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;class DTOMapper
{
    public static function map(object $source, string $dtoClass): object
    {
        $dtoReflection = new ReflectionClass($dtoClass);
        $properties = $dtoReflection-&amp;gt;getProperties();

        $args = [];

        foreach ($properties as $property) {
            $name = $property-&amp;gt;getName();

            if (isset($source-&amp;gt;$name)) {
                $args[$name] = $source-&amp;gt;$name;
            }
        }

        return new $dtoClass(...$args);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example implementation.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// For models
$dto = DTOMapper::map($product, SimpleProduct::class);

// for arrays or collection
$dtos = $products-&amp;gt;map(fn($p) =&amp;gt; DTOMapper::map($p, ProductDTO::class));
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;principles-of-structured-blade&quot;&gt;Principles of Structured Blade&lt;/h3&gt;
&lt;p&gt;This approach only holds up if you enforce some ground rules:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;No untyped arrays in views&lt;/strong&gt; Passing raw arrays to Blade is outlawed. Every view should receive a single, typed contract.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One ViewModel per Blade file&lt;/strong&gt; Keep the mental load low. A view gets exactly one entry point: its ViewModel.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Partials and components need contracts too&lt;/strong&gt; Even the tiniest include deserves its own ViewModel. Contracts everywhere, no freeloaders.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Always annotate with &lt;code&gt;@var&lt;/code&gt;&lt;/strong&gt; Autocomplete is non-negotiable. Every Blade file starts with a &lt;code&gt;`@var`&lt;/code&gt; docblock.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Always pass data as model for consistancy&lt;/strong&gt; Every view, partial or component must be passed viewmodel as &lt;code&gt;$model&lt;/code&gt; so that all blade views has single entry point to start with.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Business logic stays out of Blade&lt;/strong&gt; Views format and render. Data shaping, conditions, and prep all belong in the ViewModel.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use DTOs to avoid Eloquent jungle&lt;/strong&gt; Keep Eloquent in the backend. Views should only see pure, predictable DTOs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strict access, fail loudly&lt;/strong&gt; If a property isn’t defined in the ViewModel, the view should throw—no guessing, no silent nulls.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;final-word&quot;&gt;Final Word&lt;/h3&gt;
&lt;p&gt;Blade isn’t broken. The way most developers use it is.&lt;/p&gt;
&lt;p&gt;Treating Blade files as unstructured trash bins creates brittle, unmaintainable code. Adding ViewModels, autocomplete, partial contracts, and DTOs gives your views the same level of professionalism as the rest of your codebase.&lt;/p&gt;
&lt;p&gt;This isn’t overkill. It’s discipline. And discipline is what separates fragile projects from sustainable ones.&lt;/p&gt;
&lt;p&gt;So stop passing arrays. Start passing contracts. Give your Blade files the structure they deserve.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you found this post helpful, consider supporting my work — it means a lot.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://raheelshan.com/payment&quot;&gt;Support my work&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14222/dont-pass-array-or-variables-to-laravel-blade-views-instead-do-this</guid>
<pubDate>Wed, 08 Apr 2026 10:20:36 +0000</pubDate>
</item>
<item>
<title>Introducing The Atomic Query Construction (AQC) Design Pattern</title>
<link>https://coderlegion.com/14221/introducing-the-atomic-query-construction-aqc-design-pattern</link>
<description>&lt;p&gt;When I started my career as a developer, one of my senior team members used to construct queries in core PHP using concatenation based on different parameters. Once the construction was complete, he would echo it for different use cases and run it in PHPMyAdmin or SQLyog. Once satisfied, he’d move on. I always liked the way he built those queries.&lt;/p&gt;
&lt;p&gt;Later, when I moved on to CodeIgniter and eventually Laravel, I noticed something: Developers often write SQL or ORM-based queries buried deep inside controllers, services, views, event listeners — even helpers. This scattered approach leads to redundancy and painful maintenance. We’ve all seen this.&lt;/p&gt;
&lt;p&gt;This always bugged me. Why repeat the same logic scattered across the application when it can be centralized? &lt;/p&gt;
&lt;p&gt;Being an OOP geek, I finally settled on a pattern that works cleanly and elegantly. To counter this problem, I am introducing a new design principle: Atomic Query Construction — or AQC. It’s a design approach where each query lives in its own class. You write it once and use it everywhere. It’s structured, predictable, and aligned with better software design principles.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-is-aqc-&quot;&gt;What is AQC?&lt;/h3&gt;
&lt;p&gt;AQC stands for Atomic Query Construction. At its core, it’s a principle of isolating each query into its own class. Each of these classes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Accepts Parameters&lt;/li&gt;
&lt;li&gt;Constructs Query based on those parameters for different use cases&lt;/li&gt;
&lt;li&gt;Returns Results&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You only call this class whenever that specific query is needed — nothing else. It becomes the single source of truth for that operation.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;rules-of-the-pattern&quot;&gt;Rules of the Pattern&lt;/h3&gt;
&lt;h5 id=&quot;1-one-class-one-responsibility&quot;&gt;1. One Class, One Responsibility&lt;/h5&gt;
&lt;p&gt;Every class should focus on a single, well-defined query. Stick to the Single Responsibility Principle (SRP).&lt;/p&gt;
&lt;h5 id=&quot;2-accept-parameters&quot;&gt;2. Accept Parameters&lt;/h5&gt;
&lt;p&gt;Each class should accept parameters — filters, identifiers, flags — whatever’s needed to shape the query dynamically.&lt;/p&gt;
&lt;h5 id=&quot;3-construct-the-query-internally&quot;&gt;3. Construct the Query Internally&lt;/h5&gt;
&lt;p&gt;Based on the parameters, the class should build and return a query.&lt;/p&gt;
&lt;h5 id=&quot;4-single-source-of-truth&quot;&gt;4. Single Source of Truth&lt;/h5&gt;
&lt;p&gt;Anywhere in the app where you need this data — whether in a controller, event, or service — call this class. Do NOT rewrite the query logic elsewhere.&lt;/p&gt;
&lt;h5 id=&quot;5-handle-method&quot;&gt;5. Handle Method&lt;/h5&gt;
&lt;p&gt;Each class should expose a single public method, handle(). It can have private helpers internally, but the interface stays clean.&lt;/p&gt;
&lt;h5 id=&quot;6-consistent-naming&quot;&gt;6. Consistent Naming&lt;/h5&gt;
&lt;p&gt;Class names must be clear and consistent. For a resource like &lt;code&gt;Product&lt;/code&gt;, your AQC classes might be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GetAllProducts&lt;/li&gt;
&lt;li&gt;GetProduct&lt;/li&gt;
&lt;li&gt;StoreProduct&lt;/li&gt;
&lt;li&gt;UpdateProduct&lt;/li&gt;
&lt;li&gt;DeleteProduct&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;benefits&quot;&gt;Benefits&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Modular&lt;/strong&gt;: Every query is isolated and self-contained. You know exactly where to look when changes are needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reusable&lt;/strong&gt;: Once written, the query class can be reused across the app with different params or use cases.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flexible&lt;/strong&gt;: It supports multiple scenarios (admin view, frontend listing, etc.) using dynamic input.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clean Separation&lt;/strong&gt;: Keeps your controllers, services, and views clean. No query logic there — just calls to your AQC classes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testable&lt;/strong&gt;: Because logic is in isolated classes, it’s easy to write unit tests without involving controllers or database state.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;implementation-example&quot;&gt;Implementation Example&lt;/h3&gt;
&lt;p&gt;Let’s say we’re working with a &lt;code&gt;Product&lt;/code&gt; model in a typical eCommerce app. First, create a folder:  &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app/AQC/Product/
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GetAllProducts.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GetProduct.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;StoreProduct.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UpdateProduct.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DeleteProduct.php&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;“While I’ll use Laravel in the code examples, this pattern is framework-agnostic and can be adapted to any OOP-based backend environment — Node.js, PHP, Python, etc.”&lt;/p&gt;
&lt;p&gt;GetAllProducts.php&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

namespace App\AQC\Product;

use App\Models\Product;

class GetAllProducts
{
    public static function handle($params = [], $paginate = true, $scenario = 'default')
    {
        $productObj = Product::latest('id');

        if (isset($params['category_id']) &amp;amp;&amp;amp; $params['category_id'] &amp;gt; 0) {
            $productObj-&amp;gt;where('category_id', $params['category_id']);
        }

        if (isset($params['brand_id']) &amp;amp;&amp;amp; $params['brand_id'] &amp;gt; 0) {
            $productObj-&amp;gt;where('brand_id', $params['brand_id']);
        }

        // add more conditions for different use cases

        switch ($scenario) {
            case 'minimal':
                $productObj-&amp;gt;select(['id', 'name']);
                break;
            case 'compact':
                $productObj-&amp;gt;select(['id', 'name', 'price', 'image']);
                break;
            case 'admin':
                $productObj-&amp;gt;select(['id', 'name', 'price', 'sku', 'image', 'stock', 'cost']);
                break;
            default:
                $productObj-&amp;gt;select('*');
        }

        return $paginate
            ? $productObj-&amp;gt;paginate(Product::PAGINATE)
            : $productObj-&amp;gt;get();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Usage:  &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Admin listing
$products = GetAllProducts::handle(); 
// return paginated product order id latest 

// Frontend product listing
$products = GetAllProducts::handle($request-&amp;gt;all(), true, 'compact');
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;GetProduct.php&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

namespace App\AQC\Product;
use App\Models\Product;

class GetProduct
{
    public static function handle($id, $params = [], $scenario = 'default')
    {
        $productObj = Product::where('id', $id);

        if (isset($params['active']) &amp;amp;&amp;amp; $params['active']) {
            $productObj-&amp;gt;where('active', $params['active']);
        }

        // add more conditions for different use cases

        switch ($scenario) {
            case 'minimal':
                $productObj-&amp;gt;select(['id', 'name']);
                break;
            case 'compact':
                $productObj-&amp;gt;select(['id', 'name', 'price', 'image']);
                break;
            case 'admin':
                $productObj-&amp;gt;select(['id', 'name', 'price', 'sku', 'image', 'stock', 'cost']);
                break;
            default:
                $productObj-&amp;gt;select('*');
        }

        return $productObj-&amp;gt;first();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Admin edit screen
$product = GetProduct::handle($productID, [], 'admin');

// Frontend product detail
$product = GetProduct::handle($productID, [], 'compact');
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;StoreProduct.php&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

namespace App\AQC\Product;
use App\Models\Product;

class StoreProduct
{
    public static function handle($params)
    {
        $product = new Product();
        $product-&amp;gt;fill($params);

        if (isset($params['type']) &amp;amp;&amp;amp; $params['type'] === 'combo') {
            $product-&amp;gt;is_combo = true;
        }

        if (!isset($params['sku'])) {
            $product-&amp;gt;sku = self::generateSku($params);
        }

        $product-&amp;gt;save();
        return $product;
    }

    private static function generateSku($params)
    {
        // SKU generation logic
        return 'SKU-' . rand(1000, 9999);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;UpdateProduct.php&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

namespace App\AQC\Product;
use App\Models\Product;

class UpdateProduct
{
    public static function handle($id, $params)
    {
        $product = Product::find($id);

        if (!$product) return null;

        if (isset($params['cost'])) $product-&amp;gt;cost = $params['cost'];
        if (isset($params['price'])) $product-&amp;gt;price = $params['price'];
        if (isset($params['stock'])) $product-&amp;gt;stock = $params['stock'];

        // add more fields as needed

        $product-&amp;gt;save();
        return $product;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DeleteProduct.php&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

namespace App\AQC\Product;
use App\Models\Product;

class DeleteProduct
{
    public static function handle($params = [])
    {
        $productsObj = Product::query();

        if (!empty($params['category_id'])) {
            $productsObj-&amp;gt;where('category_id', $params['category_id']);
        }

        if (!empty($params['brand_id'])) {
            $productsObj-&amp;gt;where('brand_id', $params['brand_id']);
        }

        if (!empty($params['product_id'])) {
            $productsObj-&amp;gt;where('id', $params['product_id']);
        }

        if (!$product) return false;

        $productObj-&amp;gt;delete();

        return true;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;Each class builds the query, covers various scenarios, and cleanly separates logic. You can now reuse queries across your entire app — controllers, API endpoints, and admin panels — without rewriting anything.&lt;/p&gt;
&lt;p&gt;When a new requirement comes in, you know exactly where to go. No more chasing query logic across 20 files.&lt;/p&gt;
&lt;p&gt;“With AQC, every query has a home. No more hunting across layers of your app to find and fix logic. Write it once. Use it everywhere. Welcome to clean, atomic thinking.”&lt;/p&gt;
&lt;p&gt;If you try AQC in your projects, I’d love to hear how it works for you — or what you’d improve.&lt;/p&gt;
&lt;p&gt;I’ll be sharing more Laravel architecture patterns soon. Follow along if you’re into clean, scalable code.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you’d like personal mentorship or 1-on-1 help with &lt;code&gt;AQC Design Pattern&lt;/code&gt;, I’m available on &lt;a rel=&quot;nofollow&quot; href=&quot;https://topmate.io&quot;&gt;Topmate.io&lt;/a&gt; here.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://topmate.io/raheelshan/&quot;&gt;Schedule a booking&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you found this post helpful, consider supporting my work — it means a lot.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://raheelshan.com/payment&quot;&gt;Support My Work&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14221/introducing-the-atomic-query-construction-aqc-design-pattern</guid>
<pubDate>Wed, 08 Apr 2026 10:17:35 +0000</pubDate>
</item>
<item>
<title>Chain of Responsibility for LLM Response Handling</title>
<link>https://coderlegion.com/12947/chain-of-responsibility-for-llm-response-handling</link>
<description>&lt;p&gt;While building a &lt;a rel=&quot;nofollow&quot; href=&quot;https://jigjoy.ai/spektrum&quot;&gt;vibe coding platform&lt;/a&gt;, I started building &lt;a rel=&quot;nofollow&quot; href=&quot;https://jigjoy.ai/mozaik&quot;&gt;Mozaik&lt;/a&gt;, a library for working with LLMs. Very soon, a need emerged to handle multiple LLM providers - and with that, different request and response formats. To avoid messy code, I applied the Chain of Responsibility design pattern to handle the different responses coming back from an LLM.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pxzwkt0h502gtx1irpay.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;Instead of branching logic, each response flows through a chain of small handlers. Each handler checks one thing (structured output, tool call, plain text, empty response) and either handles it or forwards it.&lt;/p&gt;
&lt;p&gt;This keeps response handling explicit and composable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;each handler has a single responsibility&lt;/li&gt;
&lt;li&gt;handlers are easy to test in isolation&lt;/li&gt;
&lt;li&gt;new response types can be added without touching existing logic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Structured output validation is just another handler in the chain, not a special case.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zj2p822ay7svjywlk54j.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/12947/chain-of-responsibility-for-llm-response-handling</guid>
<pubDate>Mon, 16 Mar 2026 15:09:55 +0000</pubDate>
</item>
<item>
<title>Reporting and Production Make Terrible Roommates</title>
<link>https://coderlegion.com/12783/reporting-and-production-make-terrible-roommates</link>
<description>&lt;p&gt;I tend to think of reporting and production as incompatible roommates. They need the same space, they optimize for completely different things, and every accommodation one makes for the other is a debt that gets called in later. The production schema is usually what accumulates the most debt and gets hit the hardest.&lt;/p&gt;
&lt;p&gt;Consider what happens when the analytics team asks for a denormalized &lt;code&gt;order_summary&lt;/code&gt; view on the production database so their dashboards load faster. The DBA obliges, adds a materialized view, and now every schema migration has to account for it. Six months later the application team wants to split the &lt;code&gt;orders&lt;/code&gt; table into &lt;code&gt;orders&lt;/code&gt; and &lt;code&gt;order_line_items&lt;/code&gt;. But the view is embedded in 10 dashboard queries and a nightly export job, and most of those queries also join it back to columns on &lt;code&gt;orders&lt;/code&gt; directly. Redefining the view over the new tables is the easy part. Rewriting and revalidating a dozen queries the application team doesn't own is not, so the refactor stalls, and the production schema fossilizes around a reporting concern.&lt;/p&gt;
&lt;h3 id=&quot;reporting-tends-to-win-every-schema-negotiation&quot;&gt;Reporting Tends to Win Every Schema Negotiation&lt;/h3&gt;
&lt;p&gt;The cause is structural. A transactional schema optimizes for write consistency, referential integrity, and the access patterns of the application that owns it. A reporting schema optimizes for read throughput, aggregation, and the access patterns of analysts and dashboards. When both share a schema, every design decision becomes a negotiation.&lt;/p&gt;
&lt;p&gt;Once dashboards reach executives or exports reach outside consumers, reporting tends to win that negotiation, because saying no gets more expensive with every dashboard that already depends on a yes. Its requests arrive one at a time as small additions (a column, an index, a view), each cheap to accept and expensive to remove. A refactor's payoff is deferred, while a broken dashboard is visible to leadership the same morning.&lt;/p&gt;
&lt;p&gt;Not every system needs a separation on day one. A small team with a single database, low reporting complexity, and a schema that's still fluid can query production directly without meaningful friction. But this fossilization is predictable. It emerges when reporting consumers multiply, when dashboards become load-bearing, and when schema changes require cross-team coordination.&lt;/p&gt;
&lt;p&gt;Architects who recognize this trajectory can keep the door open for separation without building the full pipeline prematurely. That means resisting the urge to denormalize production schemas for reporting convenience, and keeping reporting access patterns from becoming implicit contracts on the production schema. When the separation does happen, it can be reactive, tapping into what the database already captures, or intentional, making the application responsible for producing reporting-quality records in the write path.&lt;/p&gt;
&lt;h3 id=&quot;reactive-separation-reuses-what-the-database-already-captures&quot;&gt;Reactive Separation Reuses What the Database Already Captures&lt;/h3&gt;
&lt;h4 id=&quot;a-dedicated-reporting-replica-isolates-load-not-schema&quot;&gt;A Dedicated Reporting Replica Isolates Load, Not Schema&lt;/h4&gt;
&lt;p&gt;The simplest place to start is to point reporting tools at a read replica of the production DB. Many teams already have replicas for distributing query load, and so dedicating one to reporting keeps analytical queries from competing with production traffic. No new infrastructure, no async pipeline, no application code changes.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  ┌─────────────┐         ┌─────────────────┐
  │ Application │────────&amp;gt;│  Production DB   │
  └─────────────┘  writes │  (Primary)       │
                          └────────┬─────────┘
                                   │ replication
                                   v
                          ┌─────────────────┐
                          │  Read Replica    │
                          │  (Same Schema)   │
                          └────────┬─────────┘
                                   │ direct queries
                          ┌────────┴─────────┐
                          │  BI / Dashboards  │
                          └──────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a feasible fit when reporting needs are straightforward and the production schema is close enough to what reporting consumers need. It also assumes data that's a few seconds stale is acceptable. &quot;A few seconds stale&quot; is the optimistic case, though. Heavy analytical queries on the replica can cause replication lag to spike well beyond that, especially during peak reporting windows.&lt;/p&gt;
&lt;p&gt;The isolation is partial, too. On a PostgreSQL standby, a long report can block the replica from applying changes. The replica then either cancels the report or, with &lt;code&gt;hot_standby_feedback&lt;/code&gt; on, stops the primary from cleaning up dead rows. Reporting indexes land on production too, because a replica can't hold indexes of its own.&lt;/p&gt;
&lt;p&gt;The replica breaks down when reporting needs diverge far enough from the production schema's shape. Reporting consumers write increasingly complex queries with multiple joins, or they start requesting schema changes to production to make their queries simpler, which is exactly the distortion this post is about.&lt;/p&gt;
&lt;p&gt;The replica can't capture intermediate transitions either. Reporting queries see whatever state the database holds when they run, so if a record changes twice between one query and the next, the intermediate state is invisible.&lt;/p&gt;
&lt;h4 id=&quot;change-data-capture-state-history-without-intent&quot;&gt;Change Data Capture: State History Without Intent&lt;/h4&gt;
&lt;p&gt;CDC tools like Debezium tap the database's transaction log and emit changes as events. The application writes normally to whatever schema makes sense, and CDC streams those changes to a separate store. Unlike the replica approach, CDC captures every intermediate state change because it reads from the transaction log rather than querying current state.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  ┌─────────────┐         ┌─────────────────┐
  │ Application │────────&amp;gt;│  Production DB   │
  └─────────────┘  writes └────────┬─────────┘
                                   │ transaction log
                                   v
                          ┌─────────────────┐
                          │  CDC Connector   │
                          │  (e.g. Debezium) │
                          └────────┬─────────┘
                                   │ change events
                                   v
                          ┌─────────────────┐
                          │  Stream / Queue  │
                          │  (Kafka, Kinesis)│
                          └────────┬─────────┘
                                   │
                     ┌─────────────┴─────────────┐
                     v                           v
            ┌────────────────┐          ┌────────────────┐
            │  Transform (T) │          │  Schema        │
            │  Reshape/Join  │          │  Registry      │
            └───────┬────────┘          └────────────────┘
                    v
            ┌────────────────┐
            │ Reporting Store│
            │ (Warehouse/DL) │
            └────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CDC's greatest strength is that it requires no application code changes and no new abstractions in the write path. That makes it often the only viable option for legacy systems where changing the write path is too risky, or for teams that need separation now and can't modify every service that writes data. It also tends to deliver complete payloads, because the log usually carries the full new row even when the application updated a single field.&lt;/p&gt;
&lt;p&gt;The first limitation is semantic. CDC events originate from the database layer, so they capture &lt;em&gt;what&lt;/em&gt; changed but not &lt;em&gt;why&lt;/em&gt; it changed. A row update that represents a customer canceling an order looks identical to a row update that represents a system correcting a data entry error.&lt;/p&gt;
&lt;p&gt;The obvious workaround is to add columns like &lt;code&gt;cancelled_by&lt;/code&gt; and &lt;code&gt;cancel_reason&lt;/code&gt;. They record the why only when the application itself needs them, because then they're domain state and CDC carries them. Adding them only for reporting is the distortion this post started with. And some intent has no natural row to live on at all, such as a correction or a change spanning several entities. For financial ledgers or audit-critical workflows, where intent is as important as state, the write path has to record the intent itself, which is what the outbox and event sourcing below do.&lt;/p&gt;
&lt;p&gt;The second limitation is the absence of a contract boundary. The table structure &lt;em&gt;is&lt;/em&gt; the contract, implicitly. When that schema changes, nothing fails at build time. The CDC pipeline either silently emits differently shaped events or breaks at runtime, and reporting consumers discover the problem in production rather than in development.&lt;/p&gt;
&lt;p&gt;Two mitigations narrow that gap without closing it. A transformation layer that maps raw tables onto a stable reporting model, such as warehouse staging models, shrinks the repair after a schema change to one place. But the reporting side owns that layer, so it learns of production changes only after they ship. A schema registry can partially close this gap by rejecting incompatible schema versions when the pipeline tries to register them, but that's added infrastructure catching incompatibility at runtime rather than at build time.&lt;/p&gt;
&lt;p&gt;The third limitation is database dependency. CDC is only as good as the log the database exposes. PostgreSQL's logical decoding, MySQL's binlog, and DynamoDB Streams are mature options. But a managed service that restricts log access, or a stream that retains changes only briefly (DynamoDB Streams keeps 24 hours), can push teams toward application-layer alternatives earlier than expected.&lt;/p&gt;
&lt;p&gt;Reading the log also loads the primary. Logical decoding adds write-ahead log volume to every write. A PostgreSQL replication slot also holds that log on the primary until the connector consumes it, so a stalled CDC pipeline can fill the production database's disk.&lt;/p&gt;
&lt;h3 id=&quot;intentional-separation-captures-intent-in-the-write-path&quot;&gt;Intentional Separation Captures Intent in the Write Path&lt;/h3&gt;
&lt;p&gt;Reactive approaches separate the workload but not the context. They capture &lt;em&gt;what&lt;/em&gt; changed, not &lt;em&gt;who&lt;/em&gt; changed it or &lt;em&gt;why&lt;/em&gt;. That context exists at the application layer when the write happens, and it's lost the moment the data hits the database unless someone deliberately captures it.&lt;/p&gt;
&lt;h4 id=&quot;the-outbox-pattern-makes-the-contract-explicit&quot;&gt;The Outbox Pattern Makes the Contract Explicit&lt;/h4&gt;
&lt;p&gt;The outbox pattern makes the application responsible for producing reporting-quality records. Instead of letting the database schema define the downstream contract implicitly, the application writes a versioned record to an outbox table within the same database transaction as the domain state change. Either both commit or neither does. A separate process reads from the outbox and projects into whatever reporting store analytics needs. That relay can crash after publishing and before marking a record published, so delivery is at least once, and consumers deduplicate on the event ID. The application controls the payload shape, the versioning, and the context included in each record.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  ┌─────────────┐
  │ Application │
  └──────┬──────┘
         │ single transaction
         v
  ┌──────────────────────────────────────┐
  │          Production DB               │
  │                                      │
  │  ┌──────────────┐  ┌──────────────┐  │
  │  │ Domain Table  │  │ Outbox Table │  │
  │  │ (orders,      │  │ (versioned   │  │
  │  │  customers)   │  │  records)    │  │
  │  └──────────────┘  └──────┬───────┘  │
  └───────────────────────────┼──────────┘
                              │ poll / stream
                              v
                     ┌─────────────────┐
                     │ Relay Process   │
                     │ (reads outbox)  │
                     └────────┬────────┘
                              │ publish
                              v
                     ┌─────────────────┐
                     │ Reporting Store │
                     └─────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Outbox table via a relational database:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE outbox (
    id              BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    event_id        UUID          NOT NULL DEFAULT gen_random_uuid(),  -- globally unique, used downstream
    aggregate_type  VARCHAR(100)  NOT NULL,  -- e.g. 'Order', 'Customer'
    aggregate_id    VARCHAR(100)  NOT NULL,  -- e.g. order ID
    event_type      VARCHAR(100)  NOT NULL,  -- e.g. 'OrderCancelled'
    schema_version  INT           NOT NULL,  -- contract versioning
    occurred_at     TIMESTAMPTZ   NOT NULL DEFAULT now(),
    initiated_by    VARCHAR(200),            -- who: user ID, system name
    reason          VARCHAR(500),            -- why: 'customer_request', 'admin_override'
    payload         JSONB         NOT NULL,  -- full state snapshot + context
    published       BOOLEAN       NOT NULL DEFAULT FALSE
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Outbox record via a Kinesis/Kafka stream (JSON envelope):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;id&quot;: &quot;a1b2c3d4-e5f6-7890-abcd-ef1234567890&quot;,
  &quot;aggregateType&quot;: &quot;Order&quot;,
  &quot;aggregateId&quot;: &quot;ORD-20260218-4417&quot;,
  &quot;eventType&quot;: &quot;OrderCancelled&quot;,
  &quot;schemaVersion&quot;: 2,
  &quot;occurredAt&quot;: &quot;2026-02-18T14:32:08.771Z&quot;,
  &quot;initiatedBy&quot;: &quot;user:jsmith&quot;,
  &quot;reason&quot;: &quot;customer_request&quot;,
  &quot;payload&quot;: {
    &quot;orderId&quot;: &quot;ORD-20260218-4417&quot;,
    &quot;customerId&quot;: &quot;CUST-8821&quot;,
    &quot;previousStatus&quot;: &quot;Confirmed&quot;,
    &quot;newStatus&quot;: &quot;Cancelled&quot;,
    &quot;lineItems&quot;: 3,
    &quot;totalAmount&quot;: 284.50,
    &quot;currency&quot;: &quot;USD&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The outbox has an explicit, versionable contract boundary. A breaking change to the outbox record is a deliberate code change that goes through review. If a developer renames a column in the production schema, the outbox record doesn't change unless someone deliberately updates it. A contract test in CI fails the build when the record's shape changes, before any dashboard breaks. Unlike a test on CDC tables, it lives in code its owners already test. The outbox table itself is a reporting concern in the production database, but domain tables never read or join it. Changing a domain table changes only the code that maps it into the record.&lt;/p&gt;
&lt;p&gt;Because a polling relay doesn't rely on transaction log capabilities or vendor-specific change feed APIs, any database that supports transactions supports the pattern. A relay built on CDC, such as Debezium's outbox event router, inherits CDC's operational risks but not its implicit contract.&lt;/p&gt;
&lt;p&gt;The outbox does not require a record for every database write. It only fires when a specific entity type has a meaningful state change, and only for the types that reporting cares about. Background jobs updating internal timestamps produce nothing. A deletion does produce a record, because knowing that an entity was removed and who removed it is itself meaningful. This keeps the coupling concentrated in write paths that produce meaningful state transitions, not spread across every query and update in the codebase.&lt;/p&gt;
&lt;p&gt;That selectivity is also the outbox's main risk. A bulk admin script or hand-written SQL fix that changes an order's status without writing to the outbox leaves reporting silently incomplete. CDC can't miss a change that way, because it reads every committed change. Emitting the record from the domain model's state transition closes most of that gap, and out-of-band SQL against tracked tables has to write its own outbox record.&lt;/p&gt;
&lt;p&gt;The outbox is my recommendation for teams that have outgrown a read replica, need reporting to know who acted and why, and don't need full event sourcing.&lt;/p&gt;
&lt;h4 id=&quot;cqrs-and-event-sourcing-keep-every-transition-at-the-highest-cost&quot;&gt;CQRS and Event Sourcing Keep Every Transition at the Highest Cost&lt;/h4&gt;
&lt;p&gt;CQRS (Command Query Responsibility Segregation) formally separates the write model from the read model. The write side accepts commands and persists state. The read side maintains whatever views consumers need. The two sides don't have to share a schema, or even storage. What CQRS adds is the explicit acknowledgment that changing state and reading it are different jobs that can deserve different models. CQRS does not require event sourcing. Its write side can persist state normally in a traditional database.&lt;/p&gt;
&lt;p&gt;Event sourcing takes this further by changing what the write side stores. Instead of persisting current state and producing reporting records alongside it, every state mutation is recorded as an immutable event, and current state is derived by replaying those events. The event log becomes the source of truth, not the current snapshot. Every transition is preserved in the order it occurred. Production state is a projection of the event stream, and so is reporting state, and so is any other view you need. If the analytics team changes their requirements six months from now, you replay the same events through a new projection and the full history is there.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;                          Commands
                              │
                              v
                     ┌─────────────────┐
                     │   Write Side    │
                     │ (Command Handler│
                     │  + Aggregates)  │
                     └────────┬────────┘
                              │ append events
                              v
                     ┌─────────────────┐
                     │  Event Store    │
                     │  (append-only)  │
                     └────────┬────────┘
                              │ project
                              v
                     ┌─────────────────┐
                     │  Projected      │
                     │  State Tables   │
                     │  (per entity)   │
                     └────────┬────────┘
                              │ query (read side)
              ┌───────────────┼───────────────┐
              v               v               v
     ┌────────────┐  ┌─────────────┐  ┌────────────────┐
     │  Prod API  │  │  Reporting  │  │  Audit /       │
     │  Queries   │  │  Store      │  │  Compliance    │
     └────────────┘  └─────────────┘  └────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Event store document (append-only, NoSQL):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;streamId&quot;: &quot;Order-ORD-4417&quot;,
  &quot;position&quot;: 4,
  &quot;eventType&quot;: &quot;OrderCancelled&quot;,
  &quot;occurredAt&quot;: &quot;2026-02-18T14:32:07Z&quot;,
  &quot;payload&quot;: {
    &quot;initiatedBy&quot;: &quot;user:jsmith&quot;,
    &quot;reason&quot;: &quot;customer_request&quot;,
    &quot;previousStatus&quot;: &quot;Confirmed&quot;,
    &quot;newStatus&quot;: &quot;Cancelled&quot;,
    &quot;lineItems&quot;: 3,
    &quot;totalAmount&quot;: 284.50,
    &quot;currency&quot;: &quot;USD&quot;
  },
  &quot;metadata&quot;: {
    &quot;correlationId&quot;: &quot;req-88a1c&quot;,
    &quot;causationId&quot;: &quot;cmd-cancel-4417&quot;,
    &quot;userId&quot;: &quot;user:jsmith&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Projected state table (derived from events, used by reporting/ETL):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE order_projections (
    order_id         VARCHAR(100) PRIMARY KEY,
    customer_id      VARCHAR(100) NOT NULL,
    current_status   VARCHAR(50)  NOT NULL,
    item_count       INT          NOT NULL,
    total_amount     DECIMAL(12,2) NOT NULL,
    currency         VARCHAR(3)   NOT NULL,
    placed_at        TIMESTAMPTZ,
    shipped_at       TIMESTAMPTZ,
    cancelled_at     TIMESTAMPTZ,
    cancelled_by     VARCHAR(200),
    cancel_reason    VARCHAR(500),
    last_event_pos   INT          NOT NULL,  -- tracks replay position
    projected_at     TIMESTAMPTZ  NOT NULL DEFAULT now()
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The projected table above is what reporting reads in practice. Reporting consumers rarely subscribe to the event stream directly, which keeps the stream internal to the domain. A current-state projection like &lt;code&gt;order_projections&lt;/code&gt; keeps only the latest cancellation. When reporting needs history, the domain team that owns the projections builds a history-shaped one, such as a status-transition table.&lt;/p&gt;
&lt;p&gt;This is a good fit for domains where the complete history of state transitions is genuinely valuable, like financial ledgers, audit-critical workflows, or systems where &quot;undo&quot; and &quot;replay&quot; are first-class requirements.&lt;/p&gt;
&lt;p&gt;Most teams should not reach for this combination. Martin Fowler's CQRS article warns that most of the CQRS cases he has run into have not gone well. Many systems fit a CRUD mental model and should stay that way. CQRS should only apply to specific bounded contexts where the read and write access patterns are genuinely different, not across entire applications. Event sourcing compounds the cost. Events are immutable and permanent, so schema design requires careful thought. Aggregate replay gets expensive without snapshotting, and debugging production issues means reasoning about event sequences rather than inspecting current state.&lt;/p&gt;
&lt;h3 id=&quot;choosing-an-approach-by-what-reporting-needs-to-know&quot;&gt;Choosing an Approach by What Reporting Needs to Know&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Approach &lt;/th&gt;&lt;th&gt;Use when &lt;/th&gt;&lt;th&gt;Key limitation &lt;/th&gt;&lt;th&gt;Complexity&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Read replica &lt;/td&gt;&lt;td&gt;Schema shape is close to what reporting needs; slight staleness is acceptable &lt;/td&gt;&lt;td&gt;Replication lag under analytical load; no intermediate state history &lt;/td&gt;&lt;td&gt;Low&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CDC (e.g. Debezium) &lt;/td&gt;&lt;td&gt;Write path can't be modified; full row history is needed &lt;/td&gt;&lt;td&gt;Captures state, not business intent; table structure is an implicit contract &lt;/td&gt;&lt;td&gt;Medium&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Outbox pattern &lt;/td&gt;&lt;td&gt;Business context (who/why) matters; write path can be modified &lt;/td&gt;&lt;td&gt;A write path that skips the outbox silently drops data; relay process and versioning discipline needed &lt;/td&gt;&lt;td&gt;Medium&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CQRS + Event Sourcing &lt;/td&gt;&lt;td&gt;Audit-critical domain; &quot;undo&quot; and &quot;replay&quot; are first-class requirements &lt;/td&gt;&lt;td&gt;Events are permanent; aggregate replay is expensive without snapshotting &lt;/td&gt;&lt;td&gt;High&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;separate-early-or-pay-later&quot;&gt;Separate Early or Pay Later&lt;/h3&gt;
&lt;p&gt;Separating early means separating the access path before dashboards depend on base tables, not building a pipeline. The first request for a reporting-only column or view on production is the cue. Every shortcut that ties these workloads together makes the eventual separation harder. The analytics team and the application team end up negotiating every schema change, and the production schema accumulates the concerns of whoever complained most recently.&lt;/p&gt;
&lt;p&gt;A read replica is enough to start, but only if dashboards query plain views rather than base tables. Those views live in a reporting schema the application team owns and can redefine, with grants that reach nothing else. Migrations still update the views, but now the team running the migrations owns them. When those views get too slow or too costly to redefine, move past the replica, to CDC if reporting needs only what changed and to an outbox if it needs why.&lt;/p&gt;
&lt;p&gt;Whichever step comes next, reporting reads an owned surface rather than raw tables, whether that's views on the replica, a transformation layer on CDC, or an outbox record. That owned surface keeps the schema from freezing, and the separate store keeps reporting load off production.&lt;/p&gt;
&lt;p&gt;Separation doesn't remove the coupling, but it gives the coupling an owner. With an outbox, the application team owns the record and its versions, and the analytics team owns the projection built from it. New questions about who or why still reach the application team, but as a reviewed change to a versioned record, and older records won't carry the new context. A production schema change that leaves the record's version intact ships without asking the analytics team.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/12783/reporting-and-production-make-terrible-roommates</guid>
<pubDate>Wed, 11 Mar 2026 14:01:18 +0000</pubDate>
</item>
<item>
<title>Observability Is Authored, Not Installed</title>
<link>https://coderlegion.com/11786/observability-is-authored-not-installed</link>
<description>&lt;p&gt;I have been a part of a dev team where poor observability constantly brought us to a standstill. Not because the tooling was missing, but because the data it collected never carried meaningful context. Alerts fired constantly, so operations teams ignored them. Dashboards existed for every service, but none of them answered the questions that mattered during incidents. Investigations that should have taken minutes took hours. It got bad enough that slow diagnosis alone stretched incidents into significant SLA violations.&lt;/p&gt;
&lt;p&gt;We questioned the choice of platforms, dashboards, and alerting rules. Yet none of those could help, because no tool could report a distinction our code never recorded. The problem was upstream, since our code didn't know the difference between &quot;I handled this correctly&quot; and &quot;something is actually broken.&quot;&lt;/p&gt;
&lt;h3 id=&quot;the-classification-problem-handled-outcomes-logged-as-errors&quot;&gt;The Classification Problem: Handled Outcomes Logged as Errors&lt;/h3&gt;
&lt;p&gt;Consider a payment processing system. A customer's card gets declined for insufficient funds. The payment gateway returns a rejection, and the system logs it as an ERROR.&lt;/p&gt;
&lt;p&gt;But this is the system working correctly. Insufficient funds is a handled business case, not an exception. Because it's logged as an error, though, it shows up in error dashboards, triggers error-rate alerts, and adds to the ambient noise that operators learn to tune out.&lt;/p&gt;
&lt;p&gt;Over time, &quot;payment errors&quot; become background radiation. The team knows most of them are just declined cards, so they stop investigating. Then the gateway starts timing out, or a partner pushes a breaking change, and the actual problem gets buried. Nobody notices because &quot;payment errors are always high.&quot;&lt;/p&gt;
&lt;p&gt;The usual response is to blame the team for ignoring alerts. It is a discipline problem, yes, but the discipline that's missing is upstream, in the code that treats expected outcomes as errors.&lt;/p&gt;
&lt;p&gt;The fix belongs in that code, which should sort each outcome by what it means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Expected success&lt;/strong&gt;: The happy path. Logged at DEBUG if at all.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expected failure&lt;/strong&gt;: Business logic correctly rejecting something, like declined payments, validation failures on input from outside callers, or rejecting a caller over its quota. This is INFO, not ERROR.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Degraded but functional&lt;/strong&gt;: The system recovered, but something is wearing thin. Retries running past their usual count, or a dependency throttling your calls. This is WARN. It isn't broken yet, but it needs attention before it becomes broken.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unexpected failure&lt;/strong&gt;: Something genuinely went wrong that demands investigation. This is the only category that should be ERROR. A call that fails past its retries belongs here even against a dependency known to be flaky, because no code path handled it. If that becomes routine, the fix is a handled path like a circuit breaker or fallback, which moves it to degraded.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Typed results make this distinction structural instead of incidental. When expected failures are returned as typed results rather than thrown as exceptions, the classification is baked into the code's structure. A declined payment returns a result; a gateway timeout that exhausts its retries throws an exception. A logging boundary still has to map each result type to a log level, but typed results put that decision in one visible, testable place instead of leaving it to guesswork at every call site.&lt;/p&gt;
&lt;p&gt;The log level is only one way to carry that decision. A structured outcome field or a counter works as well, as long as the code sets it. For conditions that only show in aggregate, like routine retry counts, response times approaching SLA thresholds, or connection pools running hot, a metric carries the decision better than a WARN line per request.&lt;/p&gt;
&lt;p&gt;When classification is right, every downstream tool benefits. Dashboards that track error rates become genuine health indicators because errors represent actual unexpected failures, not business logic working as designed. Log queries become surgical because structured errors with proper context let you filter to a specific tenant or operation in minutes. Once thresholds and routing are set well, alerts become actionable, because every error they count demands investigation.&lt;/p&gt;
&lt;p&gt;When classification is wrong, the opposite happens. Alerts fire for expected outcomes, so operators learn to ignore them. Dashboards become decoration because nobody trusts what the numbers represent. Every investigation becomes archaeology because the data that should answer your questions is buried under noise.&lt;/p&gt;
&lt;h4 id=&quot;only-the-code-can-make-the-call&quot;&gt;Only the Code Can Make the Call&lt;/h4&gt;
&lt;p&gt;The usual alternative is to classify downstream, but no downstream tool can classify outcomes the code didn't make distinguishable. Each option runs into that limit in its own way.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A collector rule&lt;/strong&gt; can change the level of decline lines in one central place. But it matches message text, so it breaks silently when the message changes, and it never sees lines that sampling already dropped.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Filters by logger category or exception type&lt;/strong&gt; quiet framework and library noise without matching text. But they can't split a decline from a breakage inside the same operation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An SLO&lt;/strong&gt; holds the classification rule in one shared place. But it can only count a field the code emits.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Status codes look like a field the code emits, and auto-instrumentation can tell a 402 decline from a 504 timeout. But a status code carries the meaning only when the code puts it there. A decline wrapped in a 200 response body reaches the platform with the wrong label. A partner's breaking schema change gets the wrong label too, because it arrives as a 400 that looks just like a validation failure.&lt;/p&gt;
&lt;p&gt;Only the code knows the difference, and it knows because someone designed it to. A partner integration shows what that design looks like. If the code validates each request against its own rules before sending, any schema or contract 400 the partner returns afterward is unexpected by definition. Sometimes the 400 reveals a rule your validator missed rather than a partner change. ERROR is still right, because the gap is a bug in your code, and fixing it removes that noise. Rejections based on the partner's own state, like a closed account, are different. They're expected outcomes, so map the partner's error codes for them explicitly.&lt;/p&gt;
&lt;h4 id=&quot;business-judgment-belongs-in-counters-not-log-levels&quot;&gt;Business Judgment Belongs in Counters, Not Log Levels&lt;/h4&gt;
&lt;p&gt;When the system correctly declines a card for insufficient funds, it's tempting to log that as WARN because you want the metric reviewed often. But a correctly handled decline is the system working as designed, not degrading. Whether the decline rate is &quot;concerning&quot; is a business question that changes with strategy and context, so log levels shouldn't encode that judgment. Leave business interpretation to reports and dashboards where it can evolve, not to code where it gets baked in and forgotten.&lt;/p&gt;
&lt;p&gt;Declines still deserve watching, just not through the log level. Feed those reports from a decline counter rather than from INFO lines, because many teams sample INFO logs and a counter keeps every decline. The counter also catches breakage that looks like declines. A sudden spike in correctly handled declines can mean a bug sending wrong amounts or a card-testing attack, so the same counter can carry an anomaly alert routed to engineering, separate from the error rate.&lt;/p&gt;
&lt;h3 id=&quot;context-is-authored-not-accumulated&quot;&gt;Context Is Authored, Not Accumulated&lt;/h3&gt;
&lt;p&gt;The instinct is to compensate with volume: write verbose logs everywhere so you'll have context when you need it. But a trace-level log is not a dump file. Nearly every bug I've seen diagnosed from trace logs involved information the code already had at the point of failure and could have put in the error or warning itself. In those cases the problem wasn't insufficient logging volume; it was that nobody authored the context where it mattered.&lt;/p&gt;
&lt;p&gt;A stronger version of the volume argument comes from &lt;em&gt;Observability Engineering&lt;/em&gt;, by Charity Majors, Liz Fong-Jones, and George Miranda. It records one wide structured event per request with dozens of attributes, so you can ask questions afterward that nobody predicted. That approach handles unknowns far better than trace dumps, but it doesn't escape authoring. The event still needs an outcome field that says whether a decline was expected or broken, and domain fields like the tenant and the gateway's response code only exist if the code puts them there. Without them, a high-cardinality store is a faster way to search through the same unclassified noise.&lt;/p&gt;
&lt;p&gt;Failures should carry their own context. When an operation fails, the error log should include what was being attempted, what went wrong, and enough identifying information to correlate it. Consider a checkout failure. Instead of logging &quot;payment processing failed,&quot; a properly authored error captures the operation (&quot;charge_payment&quot;), the correlation ID, the gateway's response code, and the amount. That's structured enough to query and specific enough to reproduce the failure without re-running production traffic.&lt;/p&gt;
&lt;p&gt;What solves most domain-logic bugs is understanding what the user did and what they sent, not tracing the code's internal flow. Connecting what they did across services takes a correlation key, and carrying that key is a solved problem. The W3C Trace Context standard defines how it travels between services, and most structured logging libraries can stamp it on every entry. With that key and errors authored like the checkout one, you have what you need to reproduce the problem.&lt;/p&gt;
&lt;p&gt;Reproducing a failure from its input, rather than tracing it, borrows the idea behind event sourcing, where a state can be rebuilt by replaying the events that produced it. For deterministic domain logic, you don't need to trace every intermediate step if you can reconstruct the scenario from the input, the outcome, and the state the operation read.&lt;/p&gt;
&lt;p&gt;The state the operation read is the part the request can't supply. A race, or a record that changed after the failure, can't be replayed from the input alone. So the error should carry the versions of the records it loaded. A version only helps while the old record still exists. If the store overwrites records in place, carry the field values the decision used instead.&lt;/p&gt;
&lt;p&gt;Most of the context a failure needs can be chosen in advance, because the facts that decide an outcome are ones the code already handles. They are the operation, its input, the response from whatever it called, and the versions of the records it read. Environment facts like the build version, config, feature flags, and host belong there too, and the logging setup can attach those to every entry without per-call code. Some deciding facts won't be predictable. Those show up as incidents where someone had to add logging to find the cause.&lt;/p&gt;
&lt;p&gt;What gets logged must be intentional. You know the domain, so you know the potential inputs, what's valid, and what's sensitive. That knowledge lets you author a safe context, enough to reproduce the problem without exposing data that shouldn't be in a log. An allowlist of input fields, hashed identifiers, or a reference to a stored payload can carry the input safely. If you don't understand the domain well enough to make that distinction, that's the source of the problem, not the logging infrastructure.&lt;/p&gt;
&lt;p&gt;The difference between a useful error and a useless one is whether someone authored the context intentionally or hoped that raw volume would cover it.&lt;/p&gt;
&lt;h3 id=&quot;installed-tooling-reports-mechanics-not-meaning&quot;&gt;Installed Tooling Reports Mechanics, Not Meaning&lt;/h3&gt;
&lt;p&gt;Classification and context both come from the code, but some observability really is installed. A platform catches transport failures on its own, like a timeout on an outbound call, and span timings and profilers expose creeping latency without any per-call code. Trace logging, toggled on temporarily for one flow, still diagnoses ordering bugs where the sequence itself is the evidence.&lt;/p&gt;
&lt;p&gt;What they can't supply is meaning. They see that a call failed or slowed down, but not whether a rejection was the system working as designed, which fields an error needs, or whether an answer was wrong. Wrong output that never raises an error only surfaces in business-outcome metrics, and those are authored too, because someone has to decide which outcome to count. That's the sense in which observability is authored. The installed part tells you something failed, and only the authored part tells you whether it matters and why.&lt;/p&gt;
&lt;h3 id=&quot;the-black-box-test-diagnose-from-outputs-alone&quot;&gt;The Black Box Test: Diagnose From Outputs Alone&lt;/h3&gt;
&lt;p&gt;The word observability comes from control theory, where Rudolf Kálmán defined it in 1960. A system is observable when its internal state can be inferred from its external outputs. The practical test applies that definition. When something breaks, can you diagnose it from the system's outputs alone? Or do you need to add logging, redeploy, and wait for it to happen again? If the answer is the latter, your code doesn't explain itself yet.&lt;/p&gt;
&lt;p&gt;Software never passes completely, so run the test as a rate. For each recent incident, ask whether the first responder could identify the failing operation, its input, and the cause from existing telemetry. Score it from the timeline rather than memory. An incident fails if anyone added logging before the cause was found, whether by redeploy, a live logpoint, or a trace toggle. That holds even when toggling trace logging was the right move for an ordering bug. The failing share should fall over several quarters. With only a few incidents a quarter, read the trend over a year.&lt;/p&gt;
&lt;h4 id=&quot;attaching-a-debugger-hides-the-gaps&quot;&gt;Attaching a Debugger Hides the Gaps&lt;/h4&gt;
&lt;p&gt;Classification and context are design decisions, but many developers never test whether their logging actually answers the questions it needs to. One habit that contributes is the debugger. Stepping through code is the right tool for a logic bug in development, but the habit hurts when it becomes the only way surprises get explained. A developer who resolves every local surprise by stepping through code can get little practice diagnosing from outputs, so gaps in those outputs are easy to miss until production.&lt;/p&gt;
&lt;p&gt;Some organizations extend this habit into production with remote debugging, but that's a security liability. A debugger attached to a running container, or to any production process, can read its memory, including secrets and customer data. It can also change what it executes, so every open debug port is another way into production.&lt;/p&gt;
&lt;p&gt;Snapshot debuggers capture variables without pausing, so they're gentler, and redaction and audit can contain their exposure. But they still keep the habit of reaching into the process instead of fixing what it outputs. You should be observing system outputs, not reading a live process's memory.&lt;/p&gt;
&lt;p&gt;Production should be a black box. If your default instinct when something breaks in production is to attach to it rather than read the outputs, you'll rarely feel the pressure to make those outputs useful. The classification stays sloppy, the context stays thin, and the errors stay vague. Not because you don't know better, but because you've never needed better.&lt;/p&gt;
&lt;p&gt;Developers who diagnose from observable behavior, whether testing locally against containerized dependencies or against remote systems, tend to build the discipline. They feel the pain of vague errors and missing context firsthand, and they fix it at the source because they have no easier option.&lt;/p&gt;
&lt;h4 id=&quot;owners-who-get-paged-close-the-gaps&quot;&gt;Owners Who Get Paged Close the Gaps&lt;/h4&gt;
&lt;p&gt;That discipline holds best when builders own what they operate. You don't log payment declines as errors when you're the one who gets paged for &quot;high error rate on payment service.&quot; You don't dump verbose logs instead of authoring context when you're the one parsing them at 3 AM.&lt;/p&gt;
&lt;p&gt;Paging pressure only fixes code when it reaches the code. A paged team can just as easily raise thresholds or mute alerts. What can turn the pain into a source fix is bringing the authors into incident diagnosis and filing every misclassified outcome or context-free error found there as a bug against the owning team.&lt;/p&gt;
&lt;p&gt;Tracking open telemetry bugs per owning team, beside the black box test's failing share, gives each team a number that backlog triage can see. Filing against the owning team matters most when the author sits on another team, because that author would otherwise never feel what their classification and context choices cause downstream.&lt;/p&gt;
&lt;p&gt;Better tooling alone won't make your code explain itself. Running the black box test against it, and filing what it finds against the code that caused it, is how it starts to.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/11786/observability-is-authored-not-installed</guid>
<pubDate>Mon, 16 Feb 2026 19:01:55 +0000</pubDate>
</item>
<item>
<title>How Shared Libraries Become Shared Shackles</title>
<link>https://coderlegion.com/11002/how-shared-libraries-become-shared-shackles</link>
<description>&lt;p&gt;This is a highly opinionated take on shared libraries and the damage they do to team autonomy and development tempo. Teams deliver value faster and more consistently when they can make decisions, ship changes, and evolve their domains without coordinating across organizational boundaries. Shared libraries erode exactly that independence.&lt;/p&gt;
&lt;p&gt;After seeing costs explode for trivial tasks and critical production updates failing to deliver on time in nearly every organization I have witnessed, I am willing to take a rather &quot;extreme&quot; stance on the subject.&lt;/p&gt;
&lt;p&gt;This post focuses on distributed architectures, where the consequences are most severe.&lt;/p&gt;
&lt;h3 id=&quot;shared-libraries-violate-core-principles&quot;&gt;Shared Libraries Violate Core Principles&lt;/h3&gt;
&lt;p&gt;Distributing components isn't just about distributing work. It's about the Single Responsibility Principle applied at the system level: clear ownership, implementation isolation, and infrastructural independence. The share-nothing principle makes this explicit. Services should be autonomous, independently deployable, and free from implementation coupling. DORA's research on loosely coupled teams links that independence to delivery performance, finding that teams who can change and deploy without depending on other teams deploy more often and recover faster.&lt;/p&gt;
&lt;p&gt;Shared libraries violate these principles. They couple teams through shared implementation despite being distributed in name, creating little monoliths that bind development tempo across teams that were meant to operate independently.&lt;/p&gt;
&lt;p&gt;Yet the pitch keeps coming: &quot;We have this code in five places. Let's consolidate it into a shared library. We'll save time, ensure consistency, and make everyone's life easier.&quot; It sounds reasonable, it really does, yet the decision only calculates the cost of duplication while ignoring the cost of sharing across teams, domains, and technical boundaries.&lt;/p&gt;
&lt;h3 id=&quot;the-costs-the-pitch-doesn-t-count&quot;&gt;The Costs the Pitch Doesn't Count&lt;/h3&gt;
&lt;h4 id=&quot;version-conflicts-and-upgrade-pain&quot;&gt;Version conflicts and upgrade pain&lt;/h4&gt;
&lt;p&gt;Five teams now depend on your library. They release on different cadences and at some point one or more teams require a breaking change. Now you're either maintaining multiple versions indefinitely or forcing upgrades on teams that have other priorities. The &quot;one place to maintain&quot; becomes &quot;one place that blocks everyone.&quot;&lt;/p&gt;
&lt;h4 id=&quot;teams-blocked-waiting-for-changes&quot;&gt;Teams blocked waiting for changes&lt;/h4&gt;
&lt;p&gt;A team needs functionality the library doesn't have, and a wrapper in their own code can't supply it. They can't just add it. They need to coordinate with the library owners, get the change approved, wait for a release, and then upgrade. An afternoon's change in a team's own code can stretch into weeks of waiting.&lt;/p&gt;
&lt;h4 id=&quot;debugging-across-boundaries&quot;&gt;Debugging across boundaries&lt;/h4&gt;
&lt;p&gt;When something breaks, the investigation now spans your code and the library code. Your team doesn't own the library, yet it runs inside your service, so the investigation is still yours. The abstraction that was supposed to simplify your team's work has added a layer to dig through.&lt;/p&gt;
&lt;h4 id=&quot;bloat-or-fragmentation-pick-your-poison&quot;&gt;Bloat or fragmentation, pick your poison&lt;/h4&gt;
&lt;p&gt;The library starts focused. Then another team needs something slightly different. Then another. The library accumulates features to serve multiple masters, becoming a grab-bag of loosely related functionality coupled together because they share a package, not because they belong together. The disciplined alternative is to split it into many small, focused packages, but that creates its own problem: an entourage of dependencies that each consuming team must track, version, and coordinate with whenever they change.&lt;/p&gt;
&lt;h4 id=&quot;obscured-accountability&quot;&gt;Obscured accountability&lt;/h4&gt;
&lt;p&gt;Shared libraries don't reduce your quality burden; they move it somewhere less visible. If the library has a bug, your service has a bug. The library just adds a dependency you don't own and can't fix on your own schedule.&lt;/p&gt;
&lt;h4 id=&quot;polyglot-stacks-multiply-the-library&quot;&gt;Polyglot stacks multiply the library&lt;/h4&gt;
&lt;p&gt;The shared library pitch assumes a homogeneous technology landscape that many organizations don't have. If your organization has services in C#, Java, Python, and Go, do you keep four versions of every shared library in sync? In polyglot environments, the &quot;shared&quot; library becomes a second-class citizen in every language except the one the authoring team uses. Whatever consistency the library buys stops at the language boundary, so you need another way to stay consistent anyway.&lt;/p&gt;
&lt;h4 id=&quot;release-discipline-softens-these-costs-without-removing-them&quot;&gt;Release discipline softens these costs without removing them&lt;/h4&gt;
&lt;p&gt;The usual escapes each leave part of the cost in place:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pinning an old version&lt;/strong&gt; only delays the choice until a fix a team needs ships past a breaking change, and backporting that fix is the multiple-version maintenance again.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automated update tools&lt;/strong&gt; raise pull requests for breaking upgrades as well, but each consuming team still changes its own code to take one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Outside contributions&lt;/strong&gt; shorten the owners' queue, but the change still needs their review and a release.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Forking or patching locally&lt;/strong&gt; puts the team back to copying the code.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-cohesion-and-coupling-diagnosis&quot;&gt;The Cohesion and Coupling Diagnosis&lt;/h3&gt;
&lt;p&gt;If two services genuinely need the same function, you have three possibilities:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's a cohesion problem.&lt;/strong&gt; That function belongs in one place and should be called, not duplicated. Extract it into a service with an API. Now there's a clear owner, a clear contract, and no shared implementation coupling consumers together.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's a coupling problem.&lt;/strong&gt; You've drawn your boundaries wrong. The services that &quot;need&quot; the same code are more related than you thought. Reconsider where the boundary belongs rather than papering over the boundary violation with a shared dependency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's genuinely independent.&lt;/strong&gt; The similarity is coincidental. Both services need to format dates, parse JSON, or validate email addresses, each a few lines of glue around the runtime. Copy the code. Move on. Each copy can evolve with its service.&lt;/p&gt;
&lt;h4 id=&quot;every-answer-has-a-price&quot;&gt;Every answer has a price&lt;/h4&gt;
&lt;p&gt;A service adds a network hop, an availability dependency, and an API to version, and new features still wait on its owner. In exchange, the owner changes the internals without any consumer moving, and only a breaking contract change asks callers to act.&lt;/p&gt;
&lt;p&gt;Copying has limits too. The copies can drift, and a copied bug gets fixed once per copy. For small code that rarely changes, that's still cheaper than the coordination. Copies also don't show up in a dependency scanner, so what you copy should be the glue around the runtime or a mature library, not a hand-rolled parser or validator.&lt;/p&gt;
&lt;p&gt;Some code can't be copied at all. A rule that must produce the same answer everywhere, like a tax calculation, would let two copies disagree in a way a user or the data could see. That's the cohesion case, and it needs one running implementation. A shared library deployed at different versions in different services disagrees just as two copies would.&lt;/p&gt;
&lt;h4 id=&quot;fixing-a-bug-once-is-a-boundary-signal&quot;&gt;Fixing a bug once is a boundary signal&lt;/h4&gt;
&lt;p&gt;The common rebuttal is &quot;but if there's a bug, I fix it once and it propagates everywhere.&quot; The answer depends on where the bug lives. Bugs in third-party libraries get fixed upstream on the vendor's cycle, and you take the fix by upgrading. Logging is another common candidate for sharing, but what a service logs and traces has no single fix to propagate. Each service authors its logs and telemetry out of its own domain knowledge, aligned to shared values about how behavior gets classified and what context gets captured.&lt;/p&gt;
&lt;p&gt;What remains is mostly business logic. If your business logic is so coupled across services that a single bug requires simultaneous fixes everywhere, you don't have a sharing problem, you have a boundary problem. So the default is no, and the burden of proof sits with the library to show it will stay narrow and stable.&lt;/p&gt;
&lt;h3 id=&quot;don-t-reinvent-the-wheel-vs-don-t-share-internal-types&quot;&gt;Don't Reinvent the Wheel vs. Don't Share Internal Types&lt;/h3&gt;
&lt;p&gt;Using mature, well-tested libraries for universal problems makes sense. Logging frameworks, HTTP clients, serialization libraries, and authentication middleware exist because these problems are universal and well-understood. Someone else solved them better than you would, and the cost of depending on their solution is low because the solution is stable.&lt;/p&gt;
&lt;p&gt;Sharing your internal &lt;code&gt;CustomerDto&lt;/code&gt; across services is different. Sharing your &quot;standard&quot; repository pattern is different. Sharing your domain models between bounded contexts is different. These aren't universal problems with stable solutions. They're your internal abstractions, and forcing them on other teams assumes those teams should think the same way you do.&lt;/p&gt;
&lt;p&gt;The dividing line is stability, not who wrote the code. An internal library kept narrow, versioned, and slow to break behaves like an external one. Internal libraries that carry domain or team-specific abstractions tend not to stay that way, because those abstractions change whenever your domain does.&lt;/p&gt;
&lt;h3 id=&quot;sdks-are-different&quot;&gt;SDKs Are Different&lt;/h3&gt;
&lt;p&gt;An SDK abstracts what you expose: the public contract of a service or platform. A good SDK earns its existence by encoding integration complexity that would be expensive and error-prone for every consumer to reimplement: orchestrating multi-step workflows, managing state across API calls, handling idempotency, and abstracting version differences.&lt;/p&gt;
&lt;p&gt;A shared library abstracts how you think internally, like your domain models, patterns, and &quot;standard way&quot; of doing things. The shared library serves a governance impulse, not the consumer. And unlike an SDK, it tries to couple internal teams to the same release cycle and the same implementation decisions.&lt;/p&gt;
&lt;p&gt;The SDK says: &quot;Here's how to use our thing.&quot;&lt;br&gt;
The shared library says: &quot;Here's how you should build your thing.&quot;&lt;/p&gt;
&lt;p&gt;One is a service to consumers. The other is an imposition on autonomous teams disguised as help.&lt;/p&gt;
&lt;h3 id=&quot;your-runtime-already-solved-this&quot;&gt;Your Runtime Already Solved This&lt;/h3&gt;
&lt;p&gt;The shared library pitch often targets &quot;utility code&quot; that your runtime already provides. If you're using .NET, the framework gives you HTTP clients, JSON serialization, logging abstractions, dependency injection, and configuration management. Why would you need an internal shared library wrapping &lt;code&gt;HttpClient&lt;/code&gt; when &lt;code&gt;HttpClient&lt;/code&gt; exists and is battle-tested by millions of applications?&lt;/p&gt;
&lt;p&gt;Your wrapper just adds coordination overhead on top of something that didn't need wrapping.&lt;/p&gt;
&lt;h3 id=&quot;the-principle-is-broader-than-distribution&quot;&gt;The Principle Is Broader Than Distribution&lt;/h3&gt;
&lt;p&gt;The same problem exists in a modular monolith wherever different teams own different domains. There, shared packages between domains still couple teams to the same change cycles. The blast radius is contained, though, because teams share a deployable and version conflicts surface as build errors.&lt;/p&gt;
&lt;p&gt;In a distributed system, a change that would have been a merge conflict becomes a multi-team coordination effort with blocked releases and stale dependencies. Topology changes the severity, not the coupling. If Domain A and Domain B in separate services share domain code, they're a distributed monolith with extra steps.&lt;/p&gt;
&lt;p&gt;Two practices look like exceptions to this coupling, but each accepts the cost rather than escaping it. The first is the monorepo that enforces a single version of every dependency, which Google's &lt;em&gt;Software Engineering at Google&lt;/em&gt; calls the One-Version Rule. Version skew can't happen, but a breaking change to the library has to update every consumer before it lands. The coordination moves to whoever changes the library. Google absorbs it with large-scale-change tooling most organizations lack.&lt;/p&gt;
&lt;p&gt;The second is domain-driven design's Shared Kernel. Eric Evans conditions it on neither team changing it without consulting the other, which accepts the coordination cost on purpose.&lt;/p&gt;
&lt;h3 id=&quot;the-api-client-library-obsession&quot;&gt;The API Client Library Obsession&lt;/h3&gt;
&lt;p&gt;The most common incarnation of shared library dysfunction is the API client package: a library of hand-written contracts, DTOs, and client code that consumers are expected to import when calling your service. I have never seen this pattern result in anything short of chaos.&lt;/p&gt;
&lt;p&gt;The pitch sounds reasonable: &quot;We'll publish a client library so consumers don't have to write their own HTTP calls or define their own contracts.&quot; But this solves a problem that doesn't exist while creating several that do.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Every API should have documentation describing its contracts.&lt;/strong&gt; If your API is well-documented with clear schemas, consumers can generate or write their own clients. The documentation is the contract, and a schema check in each consumer's CI catches drift from it. A machine-readable schema such as an OpenAPI document or a &lt;code&gt;.proto&lt;/code&gt; file is that documentation in another form. A stubs package generated from the schema, with no hand-written code, is too. The trouble starts when the package carries hand-written DTOs and runtime behavior, binding every consumer to the producer's model and policies.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Every consumer has different needs.&lt;/strong&gt; Service A might need three fields from one endpoint. Service B might need ten fields from a different endpoint. Service C might need to call the same endpoint but transform the response differently. When you force everyone to use your client library, you're imposing your view of how your API should be consumed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Client libraries impose one team's operational policy on every consumer.&lt;/strong&gt; Teams building client libraries inevitably add caching strategies, retry policies, circuit breakers, and connection pooling configurations. Those decisions belong to the calling service, which is the only one that knows its own latency budget, its failure tolerance, and how much damage a stale read does in its domain. The producer knows none of that. A client library freezes those choices upstream, where changing them requires a coordinated release across every consumer.&lt;/p&gt;
&lt;p&gt;The producer still has a part to play, but on its own side of the boundary. It protects itself server-side with rate limits and publishes which operations are safe to retry, its backoff contract, and how its idempotency keys and pagination tokens work. The caller honors that contract through its own resilience library.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The absurdity becomes obvious with frontend consumers.&lt;/strong&gt; The iOS app doesn't need a backend team's Swift package to call its API. The iOS team reads the documentation, or generates a client from the published schema itself, and maps responses to whatever structures suit the application.&lt;/p&gt;
&lt;p&gt;This reflexive reach for client libraries has been conditioned by years of cargo-culting. Patterns that made sense in public cloud SDKs with complex auth flows got carried into internal services with straightforward REST endpoints, where they don't. When an internal API needs multi-step orchestration to call correctly, that logic belongs behind the API, where the producer controls it, not in a package every consumer has to upgrade.&lt;/p&gt;
&lt;h3 id=&quot;the-governance-theater-problem&quot;&gt;The Governance Theater Problem&lt;/h3&gt;
&lt;p&gt;Shared libraries often emerge from a governance impulse: &quot;Teams are doing things inconsistently. We need to standardize.&quot;&lt;/p&gt;
&lt;p&gt;The instinct isn't wrong, and consistency matters. But a mandated shared library is governance theater.&lt;/p&gt;
&lt;p&gt;If teams are building things inconsistently, ask why. Usually it's because they don't share the same understanding of what matters, what the tradeoffs are, and what &quot;good&quot; looks like. That's an alignment problem. It requires conversation, documentation, and shared values.&lt;/p&gt;
&lt;p&gt;Forcing everyone to use the same library doesn't create alignment. It creates compliance.&lt;/p&gt;
&lt;p&gt;Governance through values: &quot;Here's why we authenticate this way, here are the tradeoffs, here's what we're optimizing for. Align your implementation to these principles.&quot;&lt;/p&gt;
&lt;p&gt;Governance through code: &quot;Use this library or you're non-compliant.&quot;&lt;/p&gt;
&lt;p&gt;The first preserves autonomy. Teams understand the principles and can make good decisions in novel situations. The second creates coupling while providing the illusion of alignment. Teams can comply without understanding, and when they hit a situation the library doesn't cover, they have no principle to fall back on.&lt;/p&gt;
&lt;p&gt;Governing through values doesn't mean leaving them unenforced. Conformance tests, linters, and reviews against the stated principles keep alignment from decaying. They gate builds rather than ship inside services, so when a rule tightens, each team fixes its own code without waiting on another team's release, and the rule can start as a warning.&lt;/p&gt;
&lt;p&gt;An optional &quot;paved road&quot; library that teams can leave still fits, because the values come first and the library is one documented way to follow them. It turns into theater when using the library becomes the compliance check.&lt;/p&gt;
&lt;h3 id=&quot;the-exception-thin-stable-code-like-security-protocols&quot;&gt;The Exception: Thin, Stable Code Like Security Protocols&lt;/h3&gt;
&lt;p&gt;There's one domain where shared libraries routinely make sense: security protocols like ingress handling, service-to-service authentication, and encryption standards.&lt;/p&gt;
&lt;p&gt;Why security is different:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The domain is stable and well-understood.&lt;/strong&gt; Authentication patterns don't change week to week.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The cost of getting it wrong is catastrophic.&lt;/strong&gt; Security isn't a place for teams to make independent decisions and learn from mistakes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The surface area is thin and focused.&lt;/strong&gt; A good security library does one thing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Autonomy isn't the goal.&lt;/strong&gt; You actually want teams to do security the same way. The coupling is a feature, not a bug.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The four conditions earn the exception, not the fact that it's security. They are the burden of proof the default puts on any shared library. In-process code that meets them earns it too, such as redacting personal data before logs leave a service, or a thin package that configures OpenTelemetry identically everywhere without deciding what a service logs.&lt;/p&gt;
&lt;p&gt;Uniformity alone doesn't earn the exception. Code that must behave identically everywhere but fails the conditions belongs in an external library or a service instead. Trace-context propagation must match across every service, yet it needs no internal library, because OpenTelemetry's libraries already implement the W3C Trace Context standard.&lt;/p&gt;
&lt;p&gt;Even here, mature external libraries or a service mesh should do the protocol work, and the internal library should be the thinnest layer that applies your organization's policy. Thinness keeps its forced upgrades rare. When a vulnerability does force one, uniform and immediate rollout is the point. The moment it starts accumulating &quot;helpful&quot; utilities beyond its core purpose, it's sliding toward the problems that plague other shared libraries.&lt;/p&gt;
&lt;h3 id=&quot;what-to-do-instead&quot;&gt;What to Do Instead&lt;/h3&gt;
&lt;p&gt;When you feel the urge to create a shared library, pause and diagnose the problem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If it's a capability multiple services need:&lt;/strong&gt; Build a service and expose an API, unless the logic is stable enough to version like an external library.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If the services need the same logic because they share a domain:&lt;/strong&gt; Redraw the boundary. The shared code is a symptom of a split in the wrong place.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If it's a pattern you want to standardize:&lt;/strong&gt; Write documentation. Explain the principles, the tradeoffs, and the reasoning. Let teams implement the pattern in their own codebases. They'll understand it better than if they'd just imported your abstraction.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If it's truly just duplicated code:&lt;/strong&gt; Let it be duplicated. For small code that rarely changes, the coordination cost of sharing exceeds the maintenance cost of duplication.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If it's a security primitive, or code that meets the same four conditions:&lt;/strong&gt; Fine. Build the library. Keep it minimal, stable, and focused. Recognize it's a necessary evil, not a model to emulate.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The shared library is a solution to a problem that rarely exists in the form people imagine. Duplicating small, stable code isn't what slows teams down. Coordination overhead is.&lt;/p&gt;
&lt;p&gt;Share values, and the shared library more often becomes unnecessary.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/11002/how-shared-libraries-become-shared-shackles</guid>
<pubDate>Tue, 03 Feb 2026 16:35:47 +0000</pubDate>
</item>
<item>
<title>Chain of Responsibility pattern</title>
<link>https://coderlegion.com/9988/chain-of-responsibility-pattern</link>
<description>&lt;p&gt;About &lt;strong&gt;8 years ago&lt;/strong&gt; I came across the Chain of Responsibility pattern. I remember I was genuinely happy when I understood it, because all those long &lt;code&gt;if / else if / else&lt;/code&gt; blocks suddenly felt unnecessary. Instead of one method trying to decide everything, you build a small chain of handlers. Each handler either handles the request, or passes it to the next one.&lt;/p&gt;
&lt;p&gt;Wikipedia describes it as a source of requests and a series of processing objects. Each processing object contains logic that decides what it can handle, and forwards the rest to the next one. The nice part is that the chain can be rearranged and extended without rewriting one big method. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern&quot; title=&quot;Chain-of-responsibility pattern - Wikipedia&quot;&gt;Wikipedia&lt;/a&gt;)&lt;/p&gt;
&lt;h4 id=&quot;the-classic-example-your-repo-&quot;&gt;The classic example (your repo)&lt;/h4&gt;
&lt;p&gt;In your repo, the idea is very clear:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You have a base &lt;code&gt;Handler&lt;/code&gt; with a &lt;code&gt;successor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Employee&lt;/code&gt;, &lt;code&gt;Supervisor&lt;/code&gt;, &lt;code&gt;GeneralManager&lt;/code&gt; try to approve a &lt;code&gt;PurchaseOrder&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Each handler checks rules (your &lt;code&gt;Specification&amp;lt;T&amp;gt;&lt;/code&gt;), and if it cannot approve, it forwards to the next handler&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the object-oriented alternative to the “if ladder”, but with better structure and easier extension. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/ChainOfResponsibility-&quot; title=&quot;GitHub - stevsharp/ChainOfResponsibility-&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;h4 id=&quot;the-base-building-blocks&quot;&gt;The base building blocks&lt;/h4&gt;
&lt;p&gt;This is the base handler that wires the chain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public abstract class Handler
{
    protected Handler successor;

    public void SetSuccessor(Handler purchaseOrder)
    {
        this.successor = purchaseOrder;
    }

    public abstract void HandleRequest(PurchaseOrder purchase);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And this is the small &lt;code&gt;Specification&amp;lt;T&amp;gt;&lt;/code&gt; concept used to keep rules readable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public interface ISpecification&amp;lt;T&amp;gt;
{
    bool IsSatisfiedBy(T o);
}

public class Specification&amp;lt;T&amp;gt; : ISpecification&amp;lt;T&amp;gt;
{
    private Func&amp;lt;T, bool&amp;gt; expression;

    public Specification(Func&amp;lt;T, bool&amp;gt; expression)
    {
        if (expression == null)
            throw new ArgumentNullException();
        else
            this.expression = expression;
    }

    public bool IsSatisfiedBy(T o)
    {
        return this.expression(o);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example handler, &lt;code&gt;Employee&lt;/code&gt; approves only small purchase orders. If it cannot approve, it forwards to the next handler:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class Employee : Handler
{
    public override void HandleRequest(PurchaseOrder purchase)
    {
        ISpecification&amp;lt;PurchaseOrder&amp;gt; specification =
            new Specification&amp;lt;PurchaseOrder&amp;gt;(x =&amp;gt; x.Price &amp;lt; 100);

        specification.And(new Specification&amp;lt;PurchaseOrder&amp;gt;(x =&amp;gt; x.Amount == 1));

        if (specification.IsSatisfiedBy(purchase))
            Console.WriteLine(&quot;{0} approved purchase request #{1}&quot;,
                this.GetType().Name, purchase.RequestNumber);
        else if (successor != null)
            successor.HandleRequest(purchase);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;a-quick-example-building-the-chain&quot;&gt;A quick example, building the chain&lt;/h4&gt;
&lt;p&gt;This is the part that made the pattern “click” for me. You create the handlers, link them together, and then you send requests to the first handler:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var emp = new Employee();
var superVisor = new Supervisor();
var generalManager = new GeneralManager();

emp.SetSuccessor(superVisor);
superVisor.SetSuccessor(generalManager);

List&amp;lt;PurchaseOrder&amp;gt; purchaseOrders = new List&amp;lt;PurchaseOrder&amp;gt;
{
    new PurchaseOrder { RequestNumber = 1, Amount = 1, Price = 50, Name = &quot;Test Room 1&quot; },
    new PurchaseOrder { RequestNumber = 1, Amount = 1, Price = 150, Name = &quot;Test Room 2&quot; },
    new PurchaseOrder { RequestNumber = 1, Amount = 1, Price = 200, Name = &quot;Test Room 3&quot; },
};

purchaseOrders.ForEach(x =&amp;gt; emp.HandleRequest(x));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The caller doesn’t need to know who will approve. It only knows the start of the chain. Adding a new role later is just inserting one more handler, without rewriting the calling code.&lt;/p&gt;
&lt;h4 id=&quot;how-this-shows-up-today-with-mediatr-behaviors&quot;&gt;How this shows up today with MediatR behaviors&lt;/h4&gt;
&lt;p&gt;What I like is that this pattern is not old school. You see the same idea a lot in &lt;strong&gt;MediatR pipeline behaviors&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A MediatR request goes through a chain of behaviors (validation, logging, performance, transaction, etc.). Each behavior can run logic before and after calling the next step in the pipeline, very similar to middleware. In practice, this is a Chain of Responsibility around your handler, used in a modern CQRS way. &lt;/p&gt;
&lt;p&gt;In my projects, the most common example is &lt;code&gt;TransactionBehavior&lt;/code&gt;. I run a database transaction only for &lt;strong&gt;commands&lt;/strong&gt; (write operations). Queries skip it. That keeps reads lightweight, while writes stay consistent. The chain idea is the same, each step has one job, and the next step continues the flow.&lt;/p&gt;
&lt;p&gt;Typical chain steps in CQRS are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Validation&lt;/strong&gt; (FluentValidation), stop early if invalid&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logging&lt;/strong&gt; (request, response, timing)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authorization&lt;/strong&gt; (permissions, policies)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Caching&lt;/strong&gt; (usually queries only)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transactions&lt;/strong&gt; (usually commands only, begin, commit, rollback)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retries / idempotency&lt;/strong&gt; (depending on the system)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the “chain” is not only about deciding who approves a purchase order. It’s also about running a request through cross-cutting concerns, without bloating controllers or handlers.&lt;/p&gt;
&lt;h4 id=&quot;why-i-still-like-it&quot;&gt;Why I still like it&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;You can add a new rule without editing a giant method&lt;/li&gt;
&lt;li&gt;You can reorder behavior, or swap parts, without rewriting everything&lt;/li&gt;
&lt;li&gt;It fits naturally with CQRS, because commands and queries are already separated by intent&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;references-1&quot;&gt;References&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Wikipedia, Chain-of-responsibility pattern (&lt;a rel=&quot;nofollow&quot; href=&quot;https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern&quot; title=&quot;Chain-of-responsibility pattern - Wikipedia&quot;&gt;Wikipedia&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Repo example, &lt;code&gt;ChainOfResponsibility-&lt;/code&gt; (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/ChainOfResponsibility-&quot; title=&quot;GitHub - stevsharp/ChainOfResponsibility-&quot;&gt;GitHub&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want, in the next part we can break it down with a concrete CQRS flow, like &lt;code&gt;CreatePurchaseOrderCommand&lt;/code&gt; with &lt;code&gt;ValidationBehavior&lt;/code&gt;, &lt;code&gt;TransactionBehavior&lt;/code&gt;, and an approval chain implemented as a pipeline (or inside the command handler, depending on what you prefer).&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/9988/chain-of-responsibility-pattern</guid>
<pubDate>Mon, 19 Jan 2026 09:25:06 +0000</pubDate>
</item>
<item>
<title>Carter in a CQRS API, advantages and alternatives.</title>
<link>https://coderlegion.com/9342/carter-in-a-cqrs-api-advantages-and-alternatives</link>
<description>&lt;h3 id=&quot;carter-in-a-cqrs-api-advantages-and-alternatives-&quot;&gt;Carter in a CQRS API, advantages and alternatives.&lt;/h3&gt;
&lt;p&gt;In this project I wanted the API layer to stay small and structured. I like minimal APIs, but I don’t like having 200 endpoints inside &lt;code&gt;Program.cs&lt;/code&gt;. That’s why I used &lt;strong&gt;Carter&lt;/strong&gt;, it keeps the minimal API style and gives you a module system to organize endpoints by feature.&lt;/p&gt;
&lt;p&gt;Repo used in this article:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://github.com/stevsharp/ChainOfResponsibilityWithMediatR
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;advantages-of-carter&quot;&gt;Advantages of Carter&lt;/h3&gt;
&lt;h4 id=&quot;1-feature-based-organization-without-controllers&quot;&gt;1) Feature-based organization without controllers&lt;/h4&gt;
&lt;p&gt;Carter pushes you to group endpoints in modules, for example &lt;code&gt;PurchaseOrdersModule&lt;/code&gt;. This fits well with CQRS because you already think in features (Create, Approve, Reject, Get, List).&lt;/p&gt;
&lt;h4 id=&quot;2-minimal-api-feeling-but-more-maintainable&quot;&gt;2) Minimal API feeling, but more maintainable&lt;/h4&gt;
&lt;p&gt;You still write endpoints in a minimal way (no heavy controller attributes), but you don’t end up with a giant &lt;code&gt;Program.cs&lt;/code&gt;. Microsoft itself suggests structuring and organizing minimal APIs once they grow, Carter just gives a ready approach for it.&lt;/p&gt;
&lt;h4 id=&quot;3-cleaner-api-layer-when-using-mediatr&quot;&gt;3) Cleaner API layer when using MediatR&lt;/h4&gt;
&lt;p&gt;When you combine Carter + MediatR, the endpoint usually becomes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;map route&lt;/li&gt;
&lt;li&gt;bind request&lt;/li&gt;
&lt;li&gt;call &lt;code&gt;Send&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;return result&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So your API layer stays thin and does not become “business logic + validation + database code”.&lt;/p&gt;
&lt;h4 id=&quot;4-testing-and-readability&quot;&gt;4) Testing and readability&lt;/h4&gt;
&lt;p&gt;Modules are easy to scan. If you want to see all Purchase Orders endpoints, you open one file. That’s a small thing, but in real teams it saves time.&lt;/p&gt;
&lt;h4 id=&quot;5-works-nicely-with-swagger-and-standard-middleware&quot;&gt;5) Works nicely with Swagger and standard middleware&lt;/h4&gt;
&lt;p&gt;Carter is still based on ASP.NET Core endpoints, so you can keep standard middleware, and Swagger still works (with the normal setup).&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;alternatives&quot;&gt;Alternatives&lt;/h3&gt;
&lt;h4 id=&quot;alternative-1-controllers&quot;&gt;Alternative 1, Controllers&lt;/h4&gt;
&lt;p&gt;Controllers (heavier): &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Good when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you want strong conventions and attributes everywhere&lt;/li&gt;
&lt;li&gt;you already have a big MVC style codebase&lt;/li&gt;
&lt;li&gt;your team prefers the familiar controller structure&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Less good when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you want very thin endpoints and feature modules&lt;/li&gt;
&lt;li&gt;you end up with “controller bloat” and many attributes&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;alternative-2-pure-minimal-apis-no-carter-&quot;&gt;Alternative 2, Pure Minimal APIs (no Carter)&lt;/h4&gt;
&lt;p&gt;Minimal APIs are great, and they are official. If your API is small, this is the simplest approach.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Good when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;small service with few endpoints&lt;/li&gt;
&lt;li&gt;you want the simplest possible setup&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Less good when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;endpoints grow and you need structure&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Program.cs&lt;/code&gt; becomes huge&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Microsoft has guidance on organizing minimal APIs because this is a real pain point once the API gets bigger.&lt;/p&gt;
&lt;h4 id=&quot;alternative-3-fastendpoints-another-popular-option-&quot;&gt;Alternative 3, FastEndpoints (another popular option)&lt;/h4&gt;
&lt;p&gt;FastEndpoints is another library with a very feature oriented style, it also keeps endpoints separated and organized.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Good when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you want a strong endpoint-per-class structure&lt;/li&gt;
&lt;li&gt;you like conventions for validation, mapping, and request/response patterns&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Less good when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you want the thinnest layer possible over native ASP.NET Core endpoints&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;alternative-4-minimal-apis-with-endpoint-groups-manual-modules-&quot;&gt;Alternative 4, Minimal APIs with Endpoint Groups (manual modules)&lt;/h4&gt;
&lt;p&gt;You can also do your own “module system” without extra libraries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;extension methods like &lt;code&gt;app.MapPurchaseOrderEndpoints()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;group routes with &lt;code&gt;MapGroup(&quot;/api/purchase-orders&quot;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a valid approach and stays 100% framework-only.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Good when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you want no external dependency&lt;/li&gt;
&lt;li&gt;you like simple grouping patterns&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Less good when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you still need to enforce structure across a team&lt;/li&gt;
&lt;li&gt;endpoint definitions become scattered if not disciplined&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-i-still-chose-carter-here&quot;&gt;Why I still chose Carter here&lt;/h3&gt;
&lt;p&gt;For this project size, Carter gave me the best balance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;minimal code style&lt;/li&gt;
&lt;li&gt;good organization&lt;/li&gt;
&lt;li&gt;no heavy framework feeling&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And with CQRS + MediatR, it keeps the flow very clean: endpoint → mediator → handler.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;references-2&quot;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/CarterCommunity/Carter&quot;&gt;Carter official repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis?view=aspnetcore-10.0&quot;&gt;Microsoft guidance on organizing minimal APIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-8.0&amp;amp;tabs=visual-studio&quot;&gt;Swagger for ASP.NET Core (Swashbuckle, &lt;code&gt;AddEndpointsApiExplorer&lt;/code&gt;)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/ChainOfResponsibilityWithMediatR&quot;&gt;Project repo used in this article&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/9342/carter-in-a-cqrs-api-advantages-and-alternatives</guid>
<pubDate>Wed, 07 Jan 2026 11:35:52 +0000</pubDate>
</item>
<item>
<title>Adaptability Over Cleverness: What Makes Code Actually Good</title>
<link>https://coderlegion.com/9276/adaptability-over-cleverness-what-makes-code-actually-good</link>
<description>&lt;p&gt;Systems that stay cheap to change for years aren't the ones built to fit today's requirements perfectly. They're the ones that bend without breaking when requirements shift, technologies evolve, and teams discover what they didn't know upfront. Building for change beats chasing premature perfection.&lt;/p&gt;
&lt;p&gt;You will rarely get it right the first time. That's not a failure; it's how software development works. Requirements clarify through building, edge cases emerge through usage, and performance issues surface under real load. Teams that treat first attempts as gospel can spend months polishing solutions to the wrong problem.&lt;/p&gt;
&lt;p&gt;Instead, build systems that can evolve. Give yourself room to deliver, learn, and adapt as reality proves what matters and what doesn't. Adaptable code is code where a likely change touches few places, can be made safely by someone other than its author, and shows quickly when it breaks something. Readability and simplicity matter for the same reason, since a change starts with someone understanding the code.&lt;/p&gt;
&lt;p&gt;Correctness and speed aren't the only requirements code has to meet. Security, operability, and run cost are requirements too. Adaptability decides how much it costs to keep meeting all of them as the system changes, and that cost includes reading the code to diagnose an incident or pass an audit.&lt;/p&gt;
&lt;p&gt;The exception is code that will never change or be read again, like a one-off migration script. That code only has to be correct.&lt;/p&gt;
&lt;h3 id=&quot;cleverness-and-speed-make-change-expensive&quot;&gt;Cleverness and Speed Make Change Expensive&lt;/h3&gt;
&lt;p&gt;Brian Kernighan and P. J. Plauger's &lt;em&gt;The Elements of Programming Style&lt;/em&gt; put this in two maxims: &quot;Write clearly – don't be too clever&quot; and &quot;Write clearly – don't sacrifice clarity for efficiency.&quot; Both describe the same cost. Clever code packs several decisions into one construct, like one cached value that quietly serves both a pricing rule and a fraud check. To change one of those policies, the next developer has to unpack both and work out which behavior the author intended and which is incidental.&lt;/p&gt;
&lt;p&gt;Optimized code carries the same cost in a different form. An optimization usually rests on an assumption about today's workload, such as the data fitting in memory, records arriving sorted, or one query pattern dominating. The code around it comes to depend on that assumption too, so when the workload changes, the change has to reach every place that relied on it. Donald Knuth's 1974 paper &quot;Structured Programming with go to Statements&quot; argued that programmers' attempts at efficiency in noncritical code &quot;have a strong negative impact when debugging and maintenance are considered.&quot; It's the same paper that calls premature optimization the root of all evil.&lt;/p&gt;
&lt;p&gt;None of this argues against performance. When speed is a requirement, measure where it's needed and put the optimization behind a boundary, so the rest of the system doesn't depend on how it works. The fast path then becomes one more thing that can change in one place. Some optimizations can't hide, because they shape the contract itself, like a bulk API in place of per-item calls, a streaming response in place of a full list, or a denormalized table every report reads. Those are decided deliberately up front.&lt;/p&gt;
&lt;h3 id=&quot;adaptability-comes-from-keeping-change-local&quot;&gt;Adaptability Comes From Keeping Change Local&lt;/h3&gt;
&lt;p&gt;Adaptable code isn't magic. It follows principles that reduce coupling, isolate change, and make breakage obvious.&lt;/p&gt;
&lt;h4 id=&quot;boundaries-keep-each-change-in-one-place&quot;&gt;Boundaries Keep Each Change in One Place&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Single Responsibility Principle&lt;/strong&gt; is about change, not tasks. Robert C. Martin states it as &quot;Each software module should have one and only one reason to change.&quot; Whether that module is a microservice owning a bounded context or a class handling a single concern, a shifted requirement lands in the piece responsible for it without cascading edits across the system. At service scale, a wrongly drawn boundary turns into a contract change between teams. That's why service boundaries deserve more care up front than class boundaries do.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clean interfaces and separation of concerns&lt;/strong&gt; apply at both macro and micro levels. Services communicate through contracts, not implementation details. Business logic doesn't know about HTTP; database layers don't make authorization decisions. Abstractions placed where implementations are likely to change let you pivot as understanding evolves without rewriting everything upstream.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Externalize what changes&lt;/strong&gt; by making variable behavior configurable. Environment-specific settings keep the same code deployable to dev, staging, and production. Tunable timeouts, batch sizes, and retry policies let operations adjust the system's behavior without a code change.&lt;/p&gt;
&lt;p&gt;Externalize only what is likely to vary, because each setting is flexibility with a carrying cost. It adds combinations to test and can drift between environments, so it needs the same likely-to-change test. A bad value can also take a system down, so validate configuration at startup like any other input.&lt;/p&gt;
&lt;h4 id=&quot;fast-failures-and-tests-make-breakage-visible&quot;&gt;Fast Failures and Tests Make Breakage Visible&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Fail fast and loud&lt;/strong&gt; surfaces problems immediately. Silent failures cascade into confusing bugs far from their source. Explicit validation at system boundaries, defensive assertions in critical paths, and structured logging create clear signals when something breaks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tests that give you confidence&lt;/strong&gt; let you refactor without guessing what you broke. Unit tests verify component behavior. Integration tests catch interface mismatches. End-to-end tests confirm critical workflows still work. Tests help most when they check behavior through a component's public boundary, because a test coupled to internal details becomes one more place every change has to touch.&lt;/p&gt;
&lt;h4 id=&quot;consistency-shows-where-a-change-belongs&quot;&gt;Consistency Shows Where a Change Belongs&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Consistent naming and structure&lt;/strong&gt; reduce cognitive load. Developers understand the codebase faster when patterns repeat. Services follow the same lifecycle. Features share the same folder layout and the same error-handling convention. When the structure is predictable, a developer can tell where a change belongs and what else it touches before opening a file, which is what lets someone other than the original author make it safely.&lt;/p&gt;
&lt;p&gt;Trace one requirement through these habits. Suppose refunds above a threshold now need a manager's approval. If one module owns refunds, the rule lands there and nowhere else. If the same approval rule also governs discounts and credits, it changes for reasons refunds don't share. By the same one-reason-to-change test, it belongs in an approvals module that refunds call. A developer who didn't write that module finds it because every feature follows the same layout, and a test that issues a refund through the module's public interface fails if the approval check is missing.&lt;/p&gt;
&lt;p&gt;If refund logic were instead copied into three controllers, the same rule means three edits, and a fourth copy nobody remembered keeps approving refunds without a word.&lt;/p&gt;
&lt;h3 id=&quot;flexibility-belongs-where-change-is-likely&quot;&gt;Flexibility Belongs Where Change Is Likely&lt;/h3&gt;
&lt;p&gt;The goal isn't maximum flexibility; it's appropriate flexibility. Rarely getting the design right the first time doesn't rule out appropriate flexibility. You can't predict what a requirement will become, but you can often predict where requirements will move. David Parnas made this the criterion for drawing module boundaries in his 1972 paper &quot;On the Criteria To Be Used in Decomposing Systems into Modules.&quot; The designer starts from the design decisions that are difficult or likely to change and gives each module one of them to hide from the others.&lt;/p&gt;
&lt;h4 id=&quot;domain-knowledge-and-change-history-locate-the-seams&quot;&gt;Domain Knowledge and Change History Locate the Seams&lt;/h4&gt;
&lt;p&gt;Domain knowledge tells you where those decisions are, and version control history checks the guess. Files with high commit churn show where change has actually landed so far. Churn comes in two patterns that mean different things. A file that changes for many different tickets marks a place where requirements move, so an interface belongs there. A class and its tests changing together is expected, but files in separate modules that always change together for the same reason mark the opposite problem, a boundary drawn in the wrong place. Before the first release there is no history, and domain knowledge alone decides.&lt;/p&gt;
&lt;p&gt;A payments system will need to support new payment providers, so the provider belongs behind an interface from the first release. Shape that interface around the domain's operations, like authorize, capture, and refund, rather than around the first vendor's API. That domain-shaped interface is a seam. It will still change when the second provider arrives with redirects, asynchronous confirmations, or a single charge step. But callers already depend on payment operations the domain owns rather than on one vendor's types, so the reshape is one contract change instead of a hunt for vendor types across the codebase.&lt;/p&gt;
&lt;p&gt;Domain knowledge also shows where change is unlikely. An internal admin tool probably won't need a plugin architecture.&lt;/p&gt;
&lt;p&gt;Decisions that other systems or stored data depend on follow a different rule. A published API contract, a persisted data format, or an event schema can't be fixed with a refactor later, because changing it means migrating data and coordinating every consumer. These deserve the same up-front care as the payment provider even without a change history. Service boundaries and the optimizations that shape a contract, like the bulk API earlier, belong in this group.&lt;/p&gt;
&lt;h4 id=&quot;contain-a-dependency-until-its-shape-is-clear&quot;&gt;Contain a Dependency Until Its Shape Is Clear&lt;/h4&gt;
&lt;p&gt;Inside the codebase, a seam isn't the only option. When change is likely but its shape isn't clear, or when change arrives somewhere you kept simple, contain the dependency and add the abstraction later. Tests and consistent structure, the habits from the first half of this post, let you add the boundary when the second case actually shows up. That costs one refactor, instead of years maintaining an abstraction that nothing used.&lt;/p&gt;
&lt;p&gt;That price stays low only if the concrete dependency is used from one place. Containing it takes no abstraction layer, just a rule that every call goes through one module or wrapper you own, and that callers never see vendor types or vendor error codes. Make the rule mechanical, such as keeping the vendor package a dependency of that one project, so a hurried direct call fails the build.&lt;/p&gt;
&lt;p&gt;The wrapper differs from a seam in what it promises, not in whether it uses an interface. The wrapper offers only the operations today's callers use. A seam commits to the operations the domain needs from every implementation, and that commitment is the part that goes wrong when you guess. Either way, the later change replaces one file rather than forty call sites.&lt;/p&gt;
&lt;p&gt;The tiebreaker follows from this. Add the seam up front when change is likely and you know the operations it must support, the way authorize, capture, and refund are known for payments even when each provider's mechanics differ. When the shape isn't clear yet, contain the dependency and wait for the second case to show it. Before adding a layer of flexibility, check:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Has this part of the system changed before, or does the domain make change routine, the way new providers are routine for payments?&lt;/li&gt;
&lt;li&gt;If you wait, what would the change cost? When tests cover the code and the change would stay inside one module, adding the boundary later is cheap.&lt;/li&gt;
&lt;li&gt;Who pays for the abstraction in the meantime? Every developer who reads the code pays for each layer of indirection, whether or not the change ever comes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Perfect code written for yesterday's requirements fails when reality shifts. Over-engineered code taxes every change with layers nobody needed. Adaptable code finds the balance: flexible where change is likely, simple where it isn't.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/9276/adaptability-over-cleverness-what-makes-code-actually-good</guid>
<pubDate>Mon, 05 Jan 2026 15:54:14 +0000</pubDate>
</item>
<item>
<title>How I Built a CQRS Approval Flow with MediatR, Carter, FluentValidation, and SQLite</title>
<link>https://coderlegion.com/9090/how-i-built-a-cqrs-approval-flow-with-mediatr-carter-fluentvalidation-and-sqlite</link>
<description>&lt;h3 id=&quot;the-tech-stack-i-used-and-why-updated-with-repository-and-unit-of-work-&quot;&gt;The tech stack I used and why (updated, with Repository and Unit of Work)&lt;/h3&gt;
&lt;p&gt;This project is small, but I wanted it to feel like a real API you can extend. The repository is here:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://github.com/stevsharp/ChainOfResponsibilityWithMediatR
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;carter-for-endpoints&quot;&gt;Carter for endpoints&lt;/h4&gt;
&lt;p&gt;I used &lt;strong&gt;Carter&lt;/strong&gt; because I like minimal APIs, but I still want structure. Carter lets you group endpoints by feature (modules) without big controllers, so the API layer stays clean. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application?utm_source=chatgpt.com&quot; title=&quot;Implementing the Repository and Unit of Work Patterns in ...&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
&lt;h4 id=&quot;cqrs-with-mediatr&quot;&gt;CQRS with MediatR&lt;/h4&gt;
&lt;p&gt;CQRS is the separation of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Commands&lt;/strong&gt; (write, change state)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Queries&lt;/strong&gt; (read, return data)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With &lt;strong&gt;MediatR&lt;/strong&gt;, the endpoint just does &lt;code&gt;mediator.Send(...)&lt;/code&gt; and the logic lives in a handler. This keeps endpoints thin and makes features easy to follow. &lt;/p&gt;
&lt;h4 id=&quot;icommand-and-iquery-marker-interfaces&quot;&gt;ICommand and IQuery marker interfaces&lt;/h4&gt;
&lt;p&gt;I added &lt;code&gt;ICommand&lt;/code&gt; and &lt;code&gt;IQuery&lt;/code&gt; as simple marker interfaces on top of MediatR requests. This is not a “framework”, it is only a clean way to tag what is a write and what is a read.&lt;/p&gt;
&lt;p&gt;The reason is pipeline behaviors. With these markers, I can run transactions only for commands, and avoid wrapping queries. &lt;/p&gt;
&lt;h4 id=&quot;mediatr-pipeline-behaviors&quot;&gt;MediatR pipeline behaviors&lt;/h4&gt;
&lt;p&gt;Pipeline behaviors are like middleware around every handler (&lt;code&gt;IPipelineBehavior&amp;lt;TRequest, TResponse&amp;gt;&lt;/code&gt;). (&lt;/p&gt;
&lt;p&gt;In this project I use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LoggingBehavior&lt;/strong&gt;, so I see execution time and request names&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ValidationBehavior&lt;/strong&gt;, so invalid requests never reach business logic&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TransactionBehavior&lt;/strong&gt;, so commands run inside a transaction&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This keeps handlers focused, no repeated boilerplate.&lt;/p&gt;
&lt;h4 id=&quot;fluentvalidation&quot;&gt;FluentValidation&lt;/h4&gt;
&lt;p&gt;I used &lt;strong&gt;FluentValidation&lt;/strong&gt; because it keeps validation clean and testable. It also supports assembly scanning registration, so validators are discovered automatically. &lt;/p&gt;
&lt;h4 id=&quot;repository-pattern-why-it-exists-here-&quot;&gt;Repository pattern (why it exists here)&lt;/h4&gt;
&lt;p&gt;The &lt;strong&gt;Repository&lt;/strong&gt; is used as a boundary between the application and persistence. In simple words, the application talks to an interface like &lt;code&gt;IPurchaseOrderRepository&lt;/code&gt;, and Infrastructure decides how the data is stored (EF Core, SQLite, later maybe something else).&lt;/p&gt;
&lt;p&gt;Conceptually, a repository “looks like” an in-memory collection for your domain objects, but it hides the database mapping details. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is the classic definition from Fowler&lt;/strong&gt;. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://martinfowler.com/eaaCatalog/repository.html?utm_source=chatgpt.com&quot; title=&quot;Repository&quot;&gt;martinfowler.com&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;In this project, repositories help me keep handlers free from EF Core code, so handlers read more like business use cases.&lt;/p&gt;
&lt;h4 id=&quot;unit-of-work-and-how-it-connects-to-dbcontext-&quot;&gt;Unit of Work (and how it connects to DbContext)&lt;/h4&gt;
&lt;p&gt;A &lt;strong&gt;Unit of Work&lt;/strong&gt; groups changes and commits them together. Fowler describes it as tracking everything you do in a business transaction and coordinating writing changes to the database. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://martinfowler.com/eaaCatalog/unitOfWork.html?utm_source=chatgpt.com&quot; title=&quot;Unit of Work&quot;&gt;martinfowler.com&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Important detail: in EF Core, &lt;code&gt;DbContext&lt;/code&gt; is already designed to be used as a unit of work. Microsoft docs even explain that a &lt;code&gt;DbContext&lt;/code&gt; instance is typically used for a single unit of work, then you call &lt;code&gt;SaveChanges&lt;/code&gt; and dispose it. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/&quot; title=&quot;DbContext Lifetime, Configuration, and Initialization - EF Core | Microsoft Learn&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;So why still have &lt;code&gt;IUnitOfWork&lt;/code&gt; in the Application layer?&lt;/p&gt;
&lt;p&gt;Because it is not only about “needing” it, it is about controlling dependencies. Sometimes you want:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;handlers to never call EF directly&lt;/li&gt;
&lt;li&gt;one &lt;code&gt;SaveChangesAsync&lt;/code&gt; per command (enforced in one place)&lt;/li&gt;
&lt;li&gt;the possibility to swap persistence later without touching application code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So &lt;code&gt;IUnitOfWork&lt;/code&gt; is an Application abstraction, Infrastructure implements it by calling &lt;code&gt;DbContext.SaveChangesAsync&lt;/code&gt;. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/&quot; title=&quot;DbContext Lifetime, Configuration, and Initialization - EF Core | Microsoft Learn&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
&lt;h4 id=&quot;ef-core-sqlite&quot;&gt;EF Core + SQLite&lt;/h4&gt;
&lt;p&gt;EF Core is the persistence technology, SQLite is the lightweight database so the project runs locally with minimal setup. For a demo project, it is perfect.&lt;/p&gt;
&lt;p&gt;Also, EF Core saving is straightforward: you change tracked entities, then call &lt;code&gt;SaveChanges&lt;/code&gt; or &lt;code&gt;SaveChangesAsync&lt;/code&gt; to persist. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/saving/?utm_source=chatgpt.com&quot; title=&quot;Saving Data - EF Core&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
&lt;h4 id=&quot;swagger-swashbuckle-&quot;&gt;Swagger (Swashbuckle)&lt;/h4&gt;
&lt;p&gt;Swagger is here so you can test endpoints fast without Postman. It gives visibility to request and response contracts, which is very useful in demos and early development.&lt;/p&gt;
&lt;h4 id=&quot;domain-patterns-chain-of-responsibility-and-specification&quot;&gt;Domain patterns, Chain of Responsibility and Specification&lt;/h4&gt;
&lt;p&gt;The business part is the approval flow. I used &lt;strong&gt;Chain of Responsibility&lt;/strong&gt; to avoid one big &lt;code&gt;if/else if&lt;/code&gt; method, and I used &lt;strong&gt;Specification&lt;/strong&gt; style rules so conditions stay readable and composable. This makes it easy to add new approval levels without rewriting old logic. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/8906/chain-of-responsibility-inside-cqrs-with-mediatr-a-small-practical-example&quot; title=&quot;Chain of Responsibility inside CQRS (with MediatR), a small practical example - Coder Legion&quot;&gt;Coder Legion&lt;/a&gt;)&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;references-3&quot;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;GitHub repository:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://github.com/stevsharp/ChainOfResponsibilityWithMediatR
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Original article on CoderLegion:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://coderlegion.com/8906/chain-of-responsibility-inside-cqrs-with-mediatr-a-small-practical-example
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;How to use Carter in a CQRS API, advantages and alternatives : &lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://coderlegion.com/9342/carter-in-a-cqrs-api-advantages-and-alternatives
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;MediatR pipeline behaviors (&lt;code&gt;IPipelineBehavior&lt;/code&gt;): &lt;/li&gt;
&lt;li&gt;FluentValidation DI scanning: &lt;/li&gt;
&lt;li&gt;Repository pattern (Fowler): (&lt;a rel=&quot;nofollow&quot; href=&quot;https://martinfowler.com/eaaCatalog/repository.html?utm_source=chatgpt.com&quot; title=&quot;Repository&quot;&gt;martinfowler.com&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Unit of Work pattern (Fowler): (&lt;a rel=&quot;nofollow&quot; href=&quot;https://martinfowler.com/eaaCatalog/unitOfWork.html?utm_source=chatgpt.com&quot; title=&quot;Unit of Work&quot;&gt;martinfowler.com&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;EF Core: DbContext as unit of work + SaveChanges lifecycle: (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/&quot; title=&quot;DbContext Lifetime, Configuration, and Initialization - EF Core | Microsoft Learn&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/9090/how-i-built-a-cqrs-approval-flow-with-mediatr-carter-fluentvalidation-and-sqlite</guid>
<pubDate>Tue, 30 Dec 2025 10:40:18 +0000</pubDate>
</item>
<item>
<title>Chain of Responsibility inside CQRS (with MediatR), a small practical example</title>
<link>https://coderlegion.com/8906/chain-of-responsibility-inside-cqrs-with-mediatr-a-small-practical-example</link>
<description>&lt;h3 id=&quot;chain-of-responsibility-inside-cqrs-with-mediatr-a-small-practical-example&quot;&gt;Chain of Responsibility inside CQRS (with MediatR), a small practical example&lt;/h3&gt;
&lt;p&gt;When we talk about “clean architecture” in ASP.NET Core, it is easy to end up with too many layers and too many rules, and the simple business logic becomes hard to read. This is exactly where &lt;strong&gt;Chain of Responsibility&lt;/strong&gt; can help, especially when the business flow is something like approvals.&lt;/p&gt;
&lt;p&gt;The chain-of-responsibility pattern is basically a smarter version of the classic &lt;code&gt;if / else if / else&lt;/code&gt; tree. Instead of one method that knows everything, you pass the request through a list of handlers. Each handler decides, “can I handle this?”, if yes, it stops, if not, it forwards to the next one. The pattern is described as a chain of processing objects where each one handles certain commands and passes the rest forward. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern?utm_source=chatgpt.com&quot; title=&quot;Chain-of-responsibility pattern&quot;&gt;Wikipedia&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;In this repo, the “request” is a &lt;strong&gt;Purchase Order&lt;/strong&gt; and the “handlers” are approvers (Employee, Supervisor, General Manager). (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/ChainOfResponsibilityWithMediatR&quot; title=&quot;GitHub - stevsharp/ChainOfResponsibilityWithMediatR&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-cqrs-mediatr-is-a-good-match&quot;&gt;Why CQRS + MediatR is a good match&lt;/h3&gt;
&lt;p&gt;With CQRS, we already separate the world into:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Commands&lt;/strong&gt;: change state (Create, Approve, Reject)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Queries&lt;/strong&gt;: read state (GetById, List)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MediatR fits nicely because each command/query becomes a single request with a handler. What I like here is that the approval chain stays a &lt;strong&gt;domain concern&lt;/strong&gt;, not something mixed inside controllers or endpoints.&lt;/p&gt;
&lt;p&gt;Also, MediatR supports “pipeline behaviors”, which are like middleware around handlers (logging, validation, transactions). The base interface is &lt;code&gt;IPipelineBehavior&amp;lt;,&amp;gt;&lt;/code&gt;. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/jbogard/MediatR/blob/master/src/MediatR/IPipelineBehavior.cs?utm_source=chatgpt.com&quot; title=&quot;MediatR/src/MediatR/IPipelineBehavior.cs at main&quot;&gt;GitHub&lt;/a&gt;)&lt;br&gt;
So you get two “chains” in the project, but for different responsibilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pipeline behaviors: cross-cutting technical concerns&lt;/li&gt;
&lt;li&gt;ApprovalChain: business decision flow&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-the-project-does-in-simple-words-&quot;&gt;What the project does (in simple words)&lt;/h3&gt;
&lt;p&gt;This project shows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Carter endpoints (instead of controllers) for a clean API surface (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/CarterCommunity/Carter?utm_source=chatgpt.com&quot; title=&quot;CarterCommunity/Carter&quot;&gt;GitHub&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;CQRS with MediatR handlers (one handler per feature) (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/ChainOfResponsibilityWithMediatR&quot; title=&quot;GitHub - stevsharp/ChainOfResponsibilityWithMediatR&quot;&gt;GitHub&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Behaviors for logging, validation, transactions (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/jbogard/MediatR/blob/master/src/MediatR/IPipelineBehavior.cs?utm_source=chatgpt.com&quot; title=&quot;MediatR/src/MediatR/IPipelineBehavior.cs at main&quot;&gt;GitHub&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;A domain ApprovalChain using specifications to keep rules readable (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/ChainOfResponsibilityWithMediatR&quot; title=&quot;GitHub - stevsharp/ChainOfResponsibilityWithMediatR&quot;&gt;GitHub&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Swagger/OpenAPI so you can test fast from the browser (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-8.0&amp;amp;utm_source=chatgpt.com&quot; title=&quot;Get started with Swashbuckle and ASP.NET Core&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Repo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://github.com/stevsharp/ChainOfResponsibilityWithMediatR
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/ChainOfResponsibilityWithMediatR&quot; title=&quot;GitHub - stevsharp/ChainOfResponsibilityWithMediatR&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-approval-chain-idea-how-to-think-about-it-&quot;&gt;The approval chain idea (how to think about it)&lt;/h3&gt;
&lt;p&gt;Instead of writing something like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if price &amp;lt; 100 and amount == 1, employee approves&lt;/li&gt;
&lt;li&gt;else if price &amp;lt; 200 and amount &amp;lt;= 2, supervisor approves&lt;/li&gt;
&lt;li&gt;else GM approves&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You create small classes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;EmployeeApprover&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SupervisorApprover&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GeneralManagerApprover&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And put them in an ordered list inside &lt;code&gt;ApprovalChain&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Each approver checks rules using a &lt;strong&gt;Specification&lt;/strong&gt;, so the rule becomes easy to reuse and test. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/ChainOfResponsibilityWithMediatR&quot; title=&quot;GitHub - stevsharp/ChainOfResponsibilityWithMediatR&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;The big win for me is that later, when the company says “we need 2 more approval levels”, you add 2 new approvers, you do not rewrite one huge method and risk breaking everything.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;validation-swagger-and-why-they-are-here&quot;&gt;Validation, Swagger, and why they are here&lt;/h3&gt;
&lt;p&gt;Validation is done with FluentValidation, and validators are auto-registered from an assembly (so you do not manually register 10 validators one by one). (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.fluentvalidation.net/en/latest/di.html?utm_source=chatgpt.com&quot; title=&quot;Dependency Injection — FluentValidation documentation&quot;&gt;docs.fluentvalidation.net&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Swagger is there for a simple reason, you can test your endpoints instantly and see the contracts. Swashbuckle is the most common way to do that in ASP.NET Core. (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-8.0&amp;amp;utm_source=chatgpt.com&quot; title=&quot;Get started with Swashbuckle and ASP.NET Core&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;In this article I focused on the idea and why this pattern fits very well with CQRS. &lt;br&gt;
In the next article, I will go deeper and show the actual implementation step by step, how the Carter endpoints connect to MediatR, how the commands and queries are structured, how the pipeline behaviors are added (logging, validation, transaction), and finally how the ApprovalChain and Specifications work together in the domain. &lt;/p&gt;
&lt;p&gt;The goal is to make it a real “breakthrough” article where you can copy the structure and apply it to your own projects with minimal changes.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How I Built a CQRS Approval Flow with MediatR, Carter, FluentValidation, and SQLite:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://coderlegion.com/8906/chain-of-responsibility-inside-cqrs-with-mediatr-a-small-practical-example
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;How to use Carter in a CQRS API, advantages and alternatives : &lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://coderlegion.com/9342/carter-in-a-cqrs-api-advantages-and-alternatives
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;references-4&quot;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;GitHub repo: &lt;code&gt;ChainOfResponsibilityWithMediatR&lt;/code&gt; (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/ChainOfResponsibilityWithMediatR&quot; title=&quot;GitHub - stevsharp/ChainOfResponsibilityWithMediatR&quot;&gt;GitHub&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Chain-of-Responsibility pattern definition and overview (&lt;a rel=&quot;nofollow&quot; href=&quot;https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern?utm_source=chatgpt.com&quot; title=&quot;Chain-of-responsibility pattern&quot;&gt;Wikipedia&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;MediatR &lt;code&gt;IPipelineBehavior&amp;lt;,&amp;gt;&lt;/code&gt; interface (pipeline behaviors concept) (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/jbogard/MediatR/blob/master/src/MediatR/IPipelineBehavior.cs?utm_source=chatgpt.com&quot; title=&quot;MediatR/src/MediatR/IPipelineBehavior.cs at main&quot;&gt;GitHub&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Carter framework (modular endpoints on top of ASP.NET Core) (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/CarterCommunity/Carter?utm_source=chatgpt.com&quot; title=&quot;CarterCommunity/Carter&quot;&gt;GitHub&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;FluentValidation DI registration (&lt;code&gt;AddValidatorsFromAssembly...&lt;/code&gt;) (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.fluentvalidation.net/en/latest/di.html?utm_source=chatgpt.com&quot; title=&quot;Dependency Injection — FluentValidation documentation&quot;&gt;docs.fluentvalidation.net&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Microsoft docs, Swashbuckle and Swagger/OpenAPI setup (&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-8.0&amp;amp;utm_source=chatgpt.com&quot; title=&quot;Get started with Swashbuckle and ASP.NET Core&quot;&gt;Microsoft Learn&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/8906/chain-of-responsibility-inside-cqrs-with-mediatr-a-small-practical-example</guid>
<pubDate>Thu, 25 Dec 2025 18:12:11 +0000</pubDate>
</item>
<item>
<title>Why Configuration Files Don't Belong With Your Code</title>
<link>https://coderlegion.com/8526/why-configuration-files-dont-belong-with-your-code</link>
<description>&lt;p&gt;When you first create that new shiny code project, your configuration requirements seem so obvious and straightforward. You think: &quot;I have a single behavior or feature that just needs this one setting or I am just calling this one external resource which just needs this one API URL&quot;. Often, your needs are indeed simple, but simple does not translate directly to easy. And even before the future flood of settings and complex use cases arrive, that setting beside the code is already a problem. The local approach feels intuitive because config stays close to the code that uses it, but real business use cases, daily team dynamics, production deployment pipelines, and distributed architectures are just not that simple.&lt;/p&gt;
&lt;p&gt;This localized intuition leads to predictable problems: secrets leak into Git history and persist indefinitely, environment-specific values multiply into templating systems that nobody fully understands, and configuration changes require full application redeployments. These aren't edge cases or signs of poor discipline. They follow from where the config lives. A secret in a file beside the code is one mistaken commit away from Git history, and a value shipped with the code changes only when the code ships.&lt;/p&gt;
&lt;h3 id=&quot;the-hidden-costs-of-config-in-code&quot;&gt;The Hidden Costs of Config-in-Code&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Secrets leak despite good intentions.&lt;/strong&gt; Teams start with &lt;code&gt;.gitignore&lt;/code&gt; files and environment variables, but secrets find their way into version control anyway. A developer copies a working config to test something, a merge conflict gets resolved wrong, or someone commits from a branch that predates the &lt;code&gt;.gitignore&lt;/code&gt; update. GitGuardian's State of Secrets Sprawl 2025 report counted 23.8 million secrets leaked in public GitHub commits in 2024, a count that covers hardcoded source as well as config files. Once a secret hits Git history, every clone and fork carries it. GitHub's own guidance on removing sensitive data says to revoke or rotate the secret first, because rewriting history can't reach copies already pulled. Rotating means tracking down every service that uses it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Environment parity erodes.&lt;/strong&gt; Each environment needs different database hosts, API endpoints, feature flags, and connection pool sizes. Teams respond with templating systems that substitute values at build time, or multiple config files with naming conventions like &lt;code&gt;config.dev.json&lt;/code&gt; and &lt;code&gt;config.prod.json&lt;/code&gt;, or environment variables that multiply until nobody remembers what &lt;code&gt;SERVICE_ENDPOINT_OVERRIDE_V2&lt;/code&gt; was supposed to do. When values are substituted at build time, staging and production don't even run the same image. Debugging then requires understanding not just the code but the entire config transformation pipeline.&lt;/p&gt;
&lt;p&gt;Runtime selection, like ASP.NET Core's &lt;code&gt;appsettings.{Environment}.json&lt;/code&gt; with environment-variable overrides, fixes the image problem but not the tie to releases. It keeps one image, but production values still ship in every build and change only with a release. With several override layers, the running value depends on which layer applied last.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Deployments couple to configuration.&lt;/strong&gt; When config lives with code, changing a timeout value means rebuilding and redeploying the application. When five services share a database connection string, each holds its own copy in its own repository. Changing it means five pull requests and five deployments, ordered so no service still uses the old credential when it's revoked.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Configuration fragments across components instead of cohering around domains.&lt;/strong&gt; In a multi-tenant system, a tenant's feature flags, rate limits, and credentials end up copied into every service that handles its requests, so one change for that tenant means editing each copy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Config drift becomes invisible.&lt;/strong&gt; A value gets changed manually in production to fix an urgent issue and never makes it back to staging, or a developer updates the dev config but not the template. Over time, debugging requires archaeology rather than analysis.&lt;/p&gt;
&lt;h3 id=&quot;homegrown-config-stores-recreate-the-bootstrap-problem&quot;&gt;Homegrown Config Stores Recreate the Bootstrap Problem&lt;/h3&gt;
&lt;p&gt;The natural engineering instinct is to solve this problem yourself by storing configs in a database or building a config API.&lt;/p&gt;
&lt;p&gt;Config kept in the application's own database creates a circular dependency: your service needs the database connection string to start, but the connection string is in the database. You end up hardcoding bootstrap credentials anyway, which defeats the purpose.&lt;/p&gt;
&lt;p&gt;A custom config API sounds cleaner until you realize the config service itself needs configuration, deployment sequencing, and high availability. If the config API validates values against schemas in its own code, a new payment setting means deploying the config API first, then the payment service. You've traded one problem for a more complex one.&lt;/p&gt;
&lt;p&gt;The fundamental issue is bootstrapping: how does the application know where to find its configuration before it has any configuration? Cloud-native config stores solve this by using identity that's already present in the runtime environment, such as IAM roles in AWS, managed identities in Azure, or service accounts in GCP. The application authenticates using credentials the platform provides automatically, which means no bootstrap secrets to manage. A homegrown store can authenticate the same way, but at that point you're rebuilding, operating, and keeping highly available what the managed store already provides.&lt;/p&gt;
&lt;h3 id=&quot;what-external-config-stores-provide&quot;&gt;What External Config Stores Provide&lt;/h3&gt;
&lt;p&gt;AWS has Parameter Store and Secrets Manager, Azure has Key Vault and App Configuration, GCP has Secret Manager, and HashiCorp Vault works across all of them.&lt;/p&gt;
&lt;p&gt;Sensitive data never touches the codebase because it never needs to. Credentials live in the config store, encrypted at rest, with access controlled through the same IAM policies that govern your other cloud resources. When someone leaves the team, you revoke their access in one place rather than hoping they don't have local copies of config files. Audit trails show who accessed what and when, which proves invaluable when security asks &quot;who has seen the production database password in the last 90 days?&quot;&lt;/p&gt;
&lt;p&gt;Applications become portable because the same artifact runs everywhere. The application asks &quot;what environment am I in?&quot; at startup and fetches the appropriate config, eliminating templating systems and environment-specific builds.&lt;/p&gt;
&lt;h3 id=&quot;organize-config-around-domains-not-components&quot;&gt;Organize Config Around Domains, Not Components&lt;/h3&gt;
&lt;p&gt;When configuration lives with code, it naturally organizes around components: the payment API has its config, the notification service has its config, and the reporting batch job has its config. This feels logical until you realize that configuration often cuts across components. A domain like &quot;payments&quot; might span three APIs, two background processors, and a gateway configuration.&lt;/p&gt;
&lt;p&gt;With 50 APIs and 100 tenants, changing one tenant's settings starts with working out which services handle that tenant. When the payment domain gets restructured into separate authorization and settlement services, all the tenant configs need redistribution.&lt;/p&gt;
&lt;p&gt;Domain-oriented config inverts this relationship. The same tenant configuration applies to whichever services handle that tenant's requests. When the payment domain splits, the new authorization and settlement services both read the same &lt;code&gt;/production/tenants/&amp;lt;tenant&amp;gt;/payments&lt;/code&gt; path, so no tenant config moves and only their read permissions change.&lt;/p&gt;
&lt;p&gt;The tradeoff is that shared domain keys become a contract across their consumers, so like a shared schema they evolve additively. Before changing a key, check the IAM read grants on its path, which list those consumers. Sharing a domain doesn't mean sharing every secret, though, because credentials can still sit in per-consumer sub-paths.&lt;/p&gt;
&lt;p&gt;Component-specific configuration still exists, but only for component-specific concerns: performance tuning, networking behavior, resource limits, internal timeouts. Domain config answers &quot;what does this tenant need?&quot; while component config answers &quot;how does this service behave?&quot;&lt;/p&gt;
&lt;h3 id=&quot;hierarchical-config-in-practice&quot;&gt;Hierarchical Config in Practice&lt;/h3&gt;
&lt;p&gt;External config stores enable domain-oriented organization through hierarchy. AWS Parameter Store shows the pattern, and other platforms work similarly.&lt;/p&gt;
&lt;p&gt;Parameters follow a path structure like &lt;code&gt;/production/tenants/acme-corp/feature-flags&lt;/code&gt; or &lt;code&gt;/production/domains/payments/integration-credentials&lt;/code&gt;. The hierarchy carries access and ownership. Developers can read &lt;code&gt;/dev/*&lt;/code&gt; while &lt;code&gt;/production/*&lt;/code&gt; stays restricted to deployment pipelines. A change to &lt;code&gt;/production/shared/*&lt;/code&gt; reaches every service in an environment on its next refresh or restart. Domain teams own their domain's subtree while platform teams manage cross-cutting infrastructure config.&lt;/p&gt;
&lt;p&gt;The cost is small in practice. According to AWS Systems Manager's pricing page, standard-tier Parameter Store parameters (up to 10,000 per account and region) carry no storage charge and no API charge at default throughput. Secrets Manager costs more, at $0.40 per secret per month plus API calls, so a few hundred secrets run about $100 a month.&lt;/p&gt;
&lt;p&gt;Integration with compute services removes friction. ECS task definitions can reference parameters directly, and the platform injects the values at startup. Lambda functions can read them through AWS's Parameters and Secrets Lambda Extension. Kubernetes pods can receive them as native secrets through the External Secrets Operator.&lt;/p&gt;
&lt;h3 id=&quot;config-changes-get-heavier-and-they-should&quot;&gt;Config Changes Get Heavier, and They Should&lt;/h3&gt;
&lt;p&gt;A distributed config store removes the friction that adds nothing, like rebuilding artifacts and redeploying every consumer of a shared value. It keeps the friction that protects production. Configuration updates feel heavier because they are heavier, more like database migrations than editing a text file. You'll write change requests, get approvals, coordinate timing with deployments, and verify rollback procedures work.&lt;/p&gt;
&lt;p&gt;Configuration changes in production systems &lt;em&gt;should&lt;/em&gt; be intentional and controlled because a misconfigured timeout can cascade into an outage and a wrong feature flag can expose unfinished functionality to customers. Google's SRE book reports that roughly 70% of outages are due to changes in a live system, and a config change is exactly that kind of change. The ceremony forces you to think about backwards compatibility (will existing instances handle this new format?), rollback procedures (how do we revert if this breaks something?), and dependency ordering (which services need to restart first?).&lt;/p&gt;
&lt;p&gt;Backwards compatibility matters more than it did, because config and code now roll back independently. A new key has to be additive, and the code has to tolerate its absence. It's the same expand-then-contract discipline a schema migration uses.&lt;/p&gt;
&lt;p&gt;Approval alone isn't enough, because a change to &lt;code&gt;/production/shared/*&lt;/code&gt; reaches every service. Changes with that blast radius should roll out in stages the way code does. AWS AppConfig, for example, first checks a change with validators against a JSON schema or a Lambda function. Its deployment strategies then release the change to a growing percentage of targets over a bake time and roll it back automatically when a CloudWatch alarm fires.&lt;/p&gt;
&lt;p&gt;The one exception is the kill switch, because an incident can't wait for a change request. One can live under its own path, writable by on-call through a narrow IAM role.&lt;/p&gt;
&lt;h3 id=&quot;what-still-belongs-in-local-config&quot;&gt;What Still Belongs in Local Config&lt;/h3&gt;
&lt;p&gt;The goal isn't to externalize &lt;em&gt;everything&lt;/em&gt; since that would create unnecessary dependencies and slow down development.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Application defaults&lt;/strong&gt; like log formats, default retry counts, or internal timeout values that don't change between environments MIGHT belong in code. These define how the application behaves, not how it connects to external systems. I would still hesitate before coupling these to your code, however, because a constant default can become the value someone must change during an incident.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Framework configuration&lt;/strong&gt; for HTTP server settings, thread pool sizes, or middleware ordering rarely varies by environment. When it does, you can override specific values from the external store while keeping sensible defaults local.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Development shortcuts&lt;/strong&gt; that let engineers run the application locally without network dependencies make sense for rapid iteration. The key is ensuring the code path to fetch external config exists and works.&lt;/p&gt;
&lt;p&gt;The decision rule is straightforward:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Externalize it&lt;/strong&gt; if it's sensitive, varies by environment, or is shared across services.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep it local&lt;/strong&gt; if it describes internal application behavior that stays constant everywhere.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This rule sits close to the line The Twelve-Factor App draws, where config is &quot;everything that is likely to vary between deploys&quot; and internal config that doesn't vary &quot;is best done in the code.&quot; The difference is where the values come from. Environment variables can stay the interface, as when ECS injects parameters into them. But a flat set of variables managed per deployment has no hierarchy to assign ownership by path and no version history, so a versioned store makes the better source.&lt;/p&gt;
&lt;p&gt;For prototypes and throwaway code, do whatever gets you moving fastest, but establish the external config discipline before real users or sensitive data enter the picture.&lt;/p&gt;
&lt;h3 id=&quot;where-a-store-beats-a-config-repository&quot;&gt;Where a Store Beats a Config Repository&lt;/h3&gt;
&lt;p&gt;An external store versions the values themselves, per environment, and its audit trail records who changed each one.&lt;/p&gt;
&lt;p&gt;A separate config repository is the other way to keep config out of the code, and it versions config too. A GitOps pipeline injects it at deploy time as mounted files or a ConfigMap. That also keeps one image, and it adds reviewed diffs and atomic multi-key commits. For non-secret values that one service owns and changes only on release, it is a sound home. The store wins where the repository can't follow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Secrets&lt;/strong&gt;, where even encrypted-in-repo tools like SOPS leave old ciphertext in history for anyone who once held the key, while the store audits every read and rotates without a commit&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shared values&lt;/strong&gt;, where one write replaces a config deploy to every consumer&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Incident-time changes&lt;/strong&gt; like a kill switch, which can't wait for a deploy&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Per-path permissions&lt;/strong&gt;, where a repository exposes everything in it to anyone who can read it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Versioning in the store also changes rollback. Parameter Store versions each parameter separately, so you roll back one value at a time. When several values must revert together, AWS AppConfig versions them as one configuration profile. Bad deploy? Roll back the config without touching application code. Services that poll for changes pick up the old value within their refresh interval, and services that read config at startup need a restart, not a rebuild and redeploy.&lt;/p&gt;
&lt;p&gt;A writable store moves drift risk rather than removing it. The drift described earlier becomes detectable, because parallel paths like &lt;code&gt;/staging/...&lt;/code&gt; and &lt;code&gt;/production/...&lt;/code&gt; can be compared for missing or extra keys. It becomes preventable once write access to &lt;code&gt;/production/*&lt;/code&gt; is limited to the deployment pipeline. Where a path must stay writable, like the kill switch, drift becomes attributable, because every write lands in the audit trail with the caller's identity.&lt;/p&gt;
&lt;h3 id=&quot;preventing-configs-from-creeping-back-into-code&quot;&gt;Preventing Configs from Creeping Back into Code&lt;/h3&gt;
&lt;p&gt;Moving to external config stores doesn't automatically stop developers from committing secrets. Without active governance, you'll find credentials in code reviews months after &quot;completing&quot; the migration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Code review culture&lt;/strong&gt; matters more than tooling once the store exists. With no config file left for a secret to slip into, what remains takes judgment. Team values over process! Scanners catch values that look like secrets, while a reviewer catches the hardcoded endpoint or tenant flag no pattern recognizes. After the migration the rule is simple: any credential or environment-specific value in the repository is wrong. Make it part of your definition of done: &quot;configuration fetched from external store, no hardcoded environment-specific values.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pre-commit hooks&lt;/strong&gt; can run secret scanners that check each commit for patterns that look like secrets, including API keys, connection strings, and private keys. Teams should be able to govern themselves, but for high risk products this might be needed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Repository templates&lt;/strong&gt; ensure new projects start with proper &lt;code&gt;.gitignore&lt;/code&gt; files excluding &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;config.local.*&lt;/code&gt;, and &lt;code&gt;secrets.*&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CI/CD validation&lt;/strong&gt; catches anything that slips past local checks. Pipelines can scan for hardcoded values, verify that config references resolve to external stores, and fail builds that contain suspicious patterns. GitHub's push protection goes further, rejecting a recognized secret before it reaches the remote.&lt;/p&gt;
&lt;h3 id=&quot;practical-concerns-and-how-to-handle-them&quot;&gt;Practical Concerns and How to Handle Them&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&quot;What happens when the config store is unavailable?&quot;&lt;/strong&gt; A managed config store is still a dependency, and the more likely problem is throttling, not an outage. AWS documents Parameter Store's default throughput as 40 reads per second shared across an account and region, so a fleet scaling out or an ECS deployment replacing many tasks at once can hit &lt;code&gt;ThrottlingException&lt;/code&gt; at startup. Cache config after the first fetch, spread reads across startup, enable the paid higher-throughput setting when the fleet needs it, and serve stale cached values during brief outages.&lt;/p&gt;
&lt;p&gt;A new instance has no cache, though, so a throttle or outage during scale-out blocks exactly the instances you need. A local fallback copy could cover that gap, but a copy holding secrets must be encrypted, and getting its key to the instance is the bootstrap problem again. So most services should accept the startup dependency and size throughput for scale-out.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;Startup is slower now.&quot;&lt;/strong&gt; True. Each fetch is a network round trip, usually small next to connection pool and dependency injection setup. Fetch at startup and cache with a refresh interval rather than forever, so a rotated credential still reaches running services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;Local development gets harder.&quot;&lt;/strong&gt; It gets &lt;em&gt;different&lt;/em&gt;, not harder. SDKs and credential chains make fetching config from external stores transparent once each developer (and CI) has a scoped cloud role. The default is reading from a dev path, with each developer's writes confined to a personal sub-path. Local overrides are the shortcut for offline or isolated work. The one-time setup pays for itself in fewer &quot;works locally, fails in production&quot; bugs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;We can't work offline.&quot;&lt;/strong&gt; How often do you actually develop offline? Pulling dependencies, accessing Jira, talking to teammates, running CI/CD, and pushing code all require connectivity. When it does matter, keep a local-only setup that needs no store, such as a .NET Aspire app host that defines local resources and configuration in source control.&lt;/p&gt;
&lt;h3 id=&quot;the-daily-experience-changes&quot;&gt;The Daily Experience Changes&lt;/h3&gt;
&lt;p&gt;Testing becomes more realistic because engineers can run local code against the non-secret values in remote staging or dev configs, with read access scoped to those paths, rather than maintaining local approximations that drift from reality.&lt;/p&gt;
&lt;p&gt;Committed config files stop being a path for leaked credentials, and when developers never get read access to &lt;code&gt;/production&lt;/code&gt;, production secrets stop passing through people at all. When you need to rotate a credential, you update one place. With a dual-credential scheme like Secrets Manager's alternating-users rotation, the old credential stays valid while every service picks up the new one on its next fetch or restart. The question &quot;did we rotate that key in all environments?&quot; finally has a definitive answer.&lt;/p&gt;
&lt;h3 id=&quot;making-the-shift&quot;&gt;Making the Shift&lt;/h3&gt;
&lt;p&gt;Local config files feel simple and convenient right up until they aren't. External config stores trade familiar convenience for operational discipline, and that trade-off is sound.&lt;/p&gt;
&lt;p&gt;Start with secrets by moving credentials out of the codebase and into a config store. Once that discipline is established, expand to environment-specific values, and eventually the pattern becomes natural: config comes from the environment, not from files.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/8526/why-configuration-files-dont-belong-with-your-code</guid>
<pubDate>Tue, 16 Dec 2025 17:25:52 +0000</pubDate>
</item>
<item>
<title>Avoid Forcing REST onto Domain-Driven Architectures</title>
<link>https://coderlegion.com/8304/avoid-forcing-rest-onto-domain-driven-architectures</link>
<description>&lt;p&gt;For years I have seen teams wrestle with REST in domain-driven systems. They start with clean REST endpoints then gradually compromise as business operations don't map to resource CRUD. After years or just months, they've abandoned most of what made REST attractive anyway. They invent phantom resources, hide operations in request bodies, and add gateway routing layers, all while gaining few of the architectural benefits REST was supposed to provide.&lt;/p&gt;
&lt;blockquote class=&quot;pull-quote&quot;&gt;
&lt;p&gt;Nested, navigable resource URLs assume one shared model of each entity. Domain-driven design deliberately rejects that assumption.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The mismatch isn't accidental. When you build systems around bounded contexts and business capabilities, RPC-style APIs provide better alignment without the architectural contortions. By REST I mean REST as most teams practice it, where resources with standard methods are the write path.&lt;/p&gt;
&lt;h3 id=&quot;bounded-contexts-break-the-shared-resource-graph&quot;&gt;Bounded Contexts Break the Shared Resource Graph&lt;/h3&gt;
&lt;p&gt;Domain-driven design creates different models of the same entity within different contexts. In the Orders context, a &quot;Customer&quot; might be &lt;code&gt;{id, shippingAddress, paymentMethod}&lt;/code&gt;. In the Identity context, that same person is a &quot;User&quot; with &lt;code&gt;{id, email, authProvider, preferences}&lt;/code&gt;. Each context owns its own model because each serves different business capabilities.&lt;/p&gt;
&lt;p&gt;Resource-centric API design, as most teams practice it, assumes you can navigate a graph of shared resources. The &lt;code&gt;/users/{userId}/orders&lt;/code&gt; pattern looks clean until you ask: which service owns &quot;users&quot;? The Orders service needs customer information, but it doesn't own the canonical user representation. The Identity service owns users but knows nothing about orders. Serving &lt;code&gt;/users/123/orders&lt;/code&gt; as a step in one graph, where a user resource leads to its orders, means either the Identity service learns about orders or a gateway stitches two contexts' models behind one URL. Either way you've coupled services that should remain independent.&lt;/p&gt;
&lt;p&gt;Teams resolve this tension by giving up the shared graph while keeping REST-like syntax:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add context prefixes like &lt;code&gt;/orders/api/customers/{customerId}/orders&lt;/code&gt;, acknowledging that &quot;customers&quot; in the Orders context aren't the same as &quot;users&quot; in Identity&lt;/li&gt;
&lt;li&gt;Map external-facing &lt;code&gt;/users/{id}/orders&lt;/code&gt; to internal &lt;code&gt;/orders-service/customers/{id}/orders&lt;/code&gt; through gateway routing&lt;/li&gt;
&lt;li&gt;Flatten to search-style endpoints like &lt;code&gt;/orders?customerId={id}&lt;/code&gt;, dropping the navigational hierarchy entirely&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each approach acknowledges the same reality: there is no single &quot;user&quot; resource to navigate from. What all three give up is the navigable graph of shared entities, which is the part of resource design that made &lt;code&gt;/users/{userId}/orders&lt;/code&gt; look clean in the first place. Each context ends up publishing its own resources in its own vocabulary.&lt;/p&gt;
&lt;p&gt;None of this is a case against REST's actual constraints. Roy Fielding wrote in &quot;REST APIs must be hypertext-driven&quot; that a REST API must not define fixed resource hierarchies at all, and a filtered collection is a perfectly good resource. Nor does RPC solve ownership, since an RPC call that lists a customer's orders has to live in one context too. What splitting into contexts removes is the graph that made a resource model look like the whole design. Each context is left with its own small set of resources. Whether those resources can also carry the context's writes depends on what those writes are.&lt;/p&gt;
&lt;h3 id=&quot;business-operations-don-t-map-to-resource-crud&quot;&gt;Business Operations Don't Map to Resource CRUD&lt;/h3&gt;
&lt;p&gt;REST works well when business operations map cleanly to create, read, update, and delete. Even simple operations can break down when they carry business meaning beyond field changes. Domain-driven design makes those operations the norm. An aggregate changes only through its root, and the root enforces the aggregate's invariants. Its write path is already a set of commands like cancel or ship.&lt;/p&gt;
&lt;p&gt;Consider &quot;cancel an order.&quot; In REST terms, this looks like updating the order's status field: &lt;code&gt;PATCH /orders/{id}&lt;/code&gt; with &lt;code&gt;{&quot;status&quot;: &quot;cancelled&quot;}&lt;/code&gt;. But cancellation isn't just a field update. It triggers refund processing, releases reserved inventory, sends customer notifications, and updates analytics. The operation has validation rules (can't cancel shipped orders) and side effects that don't belong in a generic resource update.&lt;/p&gt;
&lt;h4 id=&quot;forcing-operations-into-resources-hides-them&quot;&gt;Forcing Operations into Resources Hides Them&lt;/h4&gt;
&lt;p&gt;Teams force this into REST through increasingly awkward patterns:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hiding operations in PATCH bodies&lt;/strong&gt;: A &lt;code&gt;PATCH /orders/{id}&lt;/code&gt; request with &lt;code&gt;{&quot;status&quot;: &quot;cancelled&quot;}&lt;/code&gt; technically updates the resource, but the server must detect this specific status change and trigger business logic. The handler becomes a switch statement checking what changed. Status changed to &quot;cancelled&quot;? Run the cancellation workflow. Status changed to &quot;shipped&quot;? Run the shipping workflow. Only the address changed? Just update the field. Clients can't tell from the API which field changes are simple updates and which trigger complex workflows. Error handling becomes inconsistent because a failed refund during cancellation behaves nothing like a failed field validation during an address update.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Breaking transitions into sub-resources&lt;/strong&gt;: A &lt;code&gt;POST /orders/{id}/cancellations&lt;/code&gt; creates a &quot;cancellation&quot; resource, but cancellations aren't independent entities. They're events that happen to orders. Now you have &lt;code&gt;/orders/{id}/cancellations/{cancellationId}&lt;/code&gt; for something that has no meaningful lifecycle of its own.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inventing workflow resources&lt;/strong&gt;: A &lt;code&gt;POST /orders/{id}/cancel-requests&lt;/code&gt; creates a &quot;request&quot; resource that models a workflow state. The order itself is the real entity; the request is a phantom abstraction invented to maintain REST's resource-centric appearance.&lt;/p&gt;
&lt;p&gt;Each of the three patterns either hides the operation behind a standard verb or invents a resource to hang it on, while preserving REST-like URL structures. If even &quot;cancel an order&quot; doesn't fit the standard verbs cleanly, more complex operations like splitting a shipment fit them worse.&lt;/p&gt;
&lt;h4 id=&quot;sub-resources-fit-identities-not-transitions&quot;&gt;Sub-Resources Fit Identities, Not Transitions&lt;/h4&gt;
&lt;p&gt;The sub-resource itself isn't what fails in the last two patterns. A sub-resource earns its URL when the element has its own identity and its own lifecycle, like a label on an issue, a collaborator on a repository, or an item on a subscription. Each of those can be created, addressed, and removed on its own, and giving it an address takes an entire category of ambiguity out of the parent's update body.&lt;/p&gt;
&lt;p&gt;A transition earns no such URL. A cancellation or an email verification is something the order or the customer goes through, not something that lives beside it. Turning it into a stored collection to satisfy a URL convention invents an entity the domain doesn't have. Independent identity gets a sub-resource. A transition gets a named operation.&lt;/p&gt;
&lt;p&gt;A singular &lt;code&gt;PUT /orders/{id}/cancellation&lt;/code&gt; looks like a way around this rule. It avoids the stored collection, and PUT tells clients and proxies that a retry is safe. But a retried cancel must still refund only once, so the promise holds only if the server deduplicates the refund and the inventory release, the same deduplication a named operation needs. The URL still presents a transition as a resource, and DELETE on it reads as un-cancel, which the domain may not allow.&lt;/p&gt;
&lt;p&gt;A few cases look like exceptions to the rule, but none of them turns the transition itself into a resource:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The order's history or an audit log can expose cancellations for support and finance to query, while the write stays a named cancel operation.&lt;/li&gt;
&lt;li&gt;A cancellation earns its own sub-resource only when clients create and manage it as a thing in its own right, like an approval request that someone else accepts or rejects.&lt;/li&gt;
&lt;li&gt;A transition that runs long enough to have a status of its own, like a refund that can fail partway, does need something clients can check. That is a handle to the running operation, which the named operation can return, not a new entity stored beside the order.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;an-api-made-of-custom-methods-is-rpc&quot;&gt;An API Made of Custom Methods Is RPC&lt;/h4&gt;
&lt;p&gt;REST itself allows named operations, up to a point. Roy Fielding's &quot;It is okay to use POST&quot; says REST never required PUT for every state change, and Google's AIP-136 defines custom methods such as &lt;code&gt;POST /v1/{book}:archive&lt;/code&gt; for operations the standard verbs don't cover. But when every meaningful business operation becomes a POST to a named verb, the API is RPC with resource-shaped URLs. Google's guidance makes resources the default and custom methods the exception. That default pays off when most of an API is standard methods, because uniform create, update, and delete conventions cover most of what clients change.&lt;/p&gt;
&lt;h4 id=&quot;count-the-state-changes-that-carry-rules&quot;&gt;Count the State Changes That Carry Rules&lt;/h4&gt;
&lt;p&gt;To tell which kind of API a context needs, apply this test:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;List its state changes.&lt;/li&gt;
&lt;li&gt;Mark each one that has preconditions or side effects beyond saving the field. Editing a shipping address before fulfillment passes as an update, while cancel, ship, and refund don't.&lt;/li&gt;
&lt;li&gt;Move every marked change out of the generic update and into a named operation.&lt;/li&gt;
&lt;li&gt;If the marked changes are most of the list, the exceptions become the context's write API, with resource reads beside it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The count in step 4 doesn't change how any single endpoint is designed. It decides whether the context can lean on a resource API's conventions for naming, errors, and retries, or has to set its own. A few custom methods can follow the conventions their resources set, but an API made mostly of them has to define its own.&lt;/p&gt;
&lt;p&gt;Designing for that on purpose means each operation gets its own request type, its own preconditions, and its own error contract. A client calling cancel then sees &quot;already shipped&quot; and &quot;refund failed&quot; as distinct, documented outcomes rather than generic update errors. It also means the team owns conventions that a resource model would have supplied, because an operations-first API without them sprawls into bespoke verbs that each behave differently. The minimum is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A verb naming rule&lt;/li&gt;
&lt;li&gt;One error envelope with per-operation codes&lt;/li&gt;
&lt;li&gt;Idempotency keys on every write, like the &lt;code&gt;Idempotency-Key&lt;/code&gt; header Stripe's API accepts on every POST&lt;/li&gt;
&lt;li&gt;One shape for the handle a long-running operation returns&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;rest-s-technical-benefits-rarely-apply&quot;&gt;REST's Technical Benefits Rarely Apply&lt;/h3&gt;
&lt;p&gt;REST's architectural constraints provide real benefits in certain contexts. HTTP caching through intermediary proxies can reduce server load, hypermedia enables clients to discover capabilities dynamically, and the uniform interface allows generic tooling to work across different APIs. Operation-heavy contexts rarely benefit from any of these.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Caching assumes stable representations.&lt;/strong&gt; Neither style can share-cache order reads, and staleness is why. A CDN caching &lt;code&gt;/orders/12345&lt;/code&gt; doesn't know that an order was just cancelled, shipped, or had items refunded. You can set &lt;code&gt;Cache-Control: max-age=60&lt;/code&gt;, but that means clients might see stale data for up to a minute after significant business events. For an order status page, showing &quot;Processing&quot; when the order already shipped erodes user trust. So these reads end up private, short-lived, or uncached.&lt;/p&gt;
&lt;p&gt;Conditional requests with ETags avoid serving stale data, but every read still reaches the origin to revalidate. They save bandwidth, and they save server work only where the server can check a version without loading the whole order. REST caching works well for static content or slowly-changing reference data, not for entities whose state changes through business operations. Reads can stay on GET, but caching gives no reason to model transitions as resources.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hypermedia assumes discoverable, stable relationships.&lt;/strong&gt; The idea is that clients navigate links in responses rather than hardcoding URLs. But in domain-driven systems, what operations are available depends on business rules, not just resource state. Can this order be cancelled? That depends on payment status, shipping status, time since placement, and customer tier. Encoding all that context in hypermedia links means the server must evaluate business rules on every response just to populate the &lt;code&gt;_links&lt;/code&gt; section.&lt;/p&gt;
&lt;p&gt;Hypermedia still rarely pays off here, but not because of that cost. The same rule evaluation is also hypermedia's strongest case in a domain system. The server is the one place that knows the rules, and a client that follows a &lt;code&gt;cancel&lt;/code&gt; link never duplicates them. A UI that shows a Cancel button on every order view pays for the same rule evaluation in either style. What decides it is whether clients follow links instead of hardcoding URLs. Internal service-to-service clients rarely do, and most teams never implement hypermedia controls for them because the complexity isn't worth it. Wherever clients hardcode URLs, in any kind of context, hypermedia is a weak reason to choose REST.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Uniform interface assumes generic operations.&lt;/strong&gt; REST's power comes from treating all resources the same way: GET retrieves, PUT replaces, DELETE removes. Generic tooling can work across APIs because the verbs are standardized. But domain operations aren't generic. &quot;Cancel order&quot; and &quot;cancel subscription&quot; may share a verb in English, but they have completely different validation rules, side effects, and error modes. Forcing them into the same &lt;code&gt;PATCH&lt;/code&gt; or &lt;code&gt;DELETE&lt;/code&gt; pattern hides these differences behind a uniform interface that clients must then learn to navigate through documentation and tribal knowledge.&lt;/p&gt;
&lt;p&gt;Uniformity's practical value sits mostly in the plumbing. Client retry rules key off the method, gateways treat GET as safe, and rate limits and dashboards key off method and path. An operations-first context keeps nearly all of it, because its reads stay GET. Its transitions would have been POSTs or status PATCHes in a resource design too, and a client can't safely retry either one blindly. What it does give up is tooling that assumes resource semantics, like generated admin screens and generic CRUD clients, and those matter least for writes that a generic client shouldn't be making.&lt;/p&gt;
&lt;p&gt;Few teams collect these benefits even where they would apply. Most learn &quot;REST&quot; from tutorials teaching HTTP + JSON + resource URLs, never encountering the actual constraints that make REST architecturally significant. They end up with HTTP-based RPC that pretends to be RESTful without gaining any of the benefits Fielding described in the REST chapter of his dissertation.&lt;/p&gt;
&lt;h3 id=&quot;choose-based-on-your-domain-model&quot;&gt;Choose Based on Your Domain Model&lt;/h3&gt;
&lt;p&gt;Neither approach is universally better. The choice depends on how your domain naturally models work. Most domain-driven systems contain both kinds of context, since supporting subdomains are often plain CRUD. So the choice is made per bounded context and, inside a context, per operation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;REST fits entity-driven systems.&lt;/strong&gt; Media libraries, inventory catalogs, and configuration management often align well with REST. Resources have stable identities, relationships are navigable, and standard CRUD operations match what the business actually does. A photo library really is a collection of photo resources that you create, read, update, and delete. REST's constraints provide genuine value here: caching works because photos don't change often, hypermedia can express album-to-photo relationships for clients that follow links, and generic tooling can operate across different media types.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RPC fits operation-driven systems.&lt;/strong&gt; Operation-heavy contexts are the ones where most state changes carry rules and side effects. They align better with explicit operations, meaning endpoints named after what the business does, whether they run over plain HTTP and JSON or a framework like gRPC. Endpoints like &lt;code&gt;/orders/cancel&lt;/code&gt;, &lt;code&gt;/orders/refund&lt;/code&gt;, and &lt;code&gt;/orders/split-shipment&lt;/code&gt; map directly to what the business actually does. The API's vocabulary is the domain's vocabulary. Each endpoint serves a single purpose, so the API's structure, not a handler's switch statement, keeps transitions apart, and each one's request type and errors can be generated and documented on their own.&lt;/p&gt;
&lt;p&gt;The URL form matters far less than that contrast. Whether it reads &lt;code&gt;POST /orders/cancel&lt;/code&gt; or the AIP-style &lt;code&gt;POST /orders/{id}:cancel&lt;/code&gt;, either beats a &lt;code&gt;PATCH /orders/{id}&lt;/code&gt; that might update the order status, add line items, change shipping addresses, or trigger cancellation depending on the request body. The AIP style does keep the order ID visible to gateway rules and logs keyed on path.&lt;/p&gt;
&lt;p&gt;Either form can be operations-first, and an AIP-style API built mostly from custom methods is an operations-first API with resource-style URLs. What makes a design operations-first is where it starts. It starts from the operations and their contracts, with resources added for reads. Starting there also means fields with workflows behind them, like status, can't be changed through a generic update at all.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;What the domain does &lt;/th&gt;&lt;th&gt;What fits&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Creates, reads, updates, and deletes entities with stable identities &lt;/td&gt;&lt;td&gt;REST resources&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Manages an element with its own identity and lifecycle, like a label on an issue &lt;/td&gt;&lt;td&gt;A sub-resource&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Moves an entity through a transition with rules and side effects, like cancelling an order &lt;/td&gt;&lt;td&gt;A named operation&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Serves data that changes slowly and is read widely &lt;/td&gt;&lt;td&gt;REST with HTTP caching&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Elegance doesn't matter here. What matters is which style matches how your domain actually works. Where a context manages entities with stable identities, use REST for them and for reads. Where it executes business operations with rules and side effects, stop forcing those operations into resource updates, even on an entity like an order.&lt;/p&gt;
&lt;blockquote class=&quot;pull-quote&quot;&gt;
&lt;p&gt;Match your API style to your domain model, not to industry conventions or resume-driven development.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/8304/avoid-forcing-rest-onto-domain-driven-architectures</guid>
<pubDate>Mon, 08 Dec 2025 17:33:24 +0000</pubDate>
</item>
<item>
<title>Architecture Pattern: &quot;Compound Components&quot; and Advanced Projection with `ngProjectAs`</title>
<link>https://coderlegion.com/7975/architecture-pattern-compound-components-and-advanced-projection-with-ngprojectas</link>
<description>&lt;p&gt;When building a Design System or a library of shared components, the most difficult challenge is &lt;strong&gt;balancing consistency with flexibility.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You want all your Modals to look the same (consistency), but some Modals contain forms, others contain text, and others contain complex grids (flexibility).&lt;/p&gt;
&lt;p&gt;The Architectural Pattern that solves this is &lt;strong&gt;Content Projection&lt;/strong&gt; (specifically Multi-Slot Projection).&lt;/p&gt;
&lt;h4 id=&quot;the-basics-the-select-api&quot;&gt;The Basics: The &quot;Select&quot; API&lt;/h4&gt;
&lt;p&gt;When you build a container, you define its API not by Inputs, but by &lt;strong&gt;Slots&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;@Component({
  selector: 'app-layout',
  template: `
    &amp;lt;header&amp;gt;
      &amp;lt;ng-content select=&quot;nav&quot;&amp;gt;&amp;lt;/ng-content&amp;gt;
    &amp;lt;/header&amp;gt;
    &amp;lt;main&amp;gt;
      &amp;lt;ng-content&amp;gt;&amp;lt;/ng-content&amp;gt;
    &amp;lt;/main&amp;gt;
  `
})
export class AppLayoutComponent {}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-senior-gotcha-wrapper-elements&quot;&gt;The Senior &quot;Gotcha&quot;: Wrapper Elements&lt;/h4&gt;
&lt;p&gt;Here is a problem 99% of devs hit.&lt;/p&gt;
&lt;p&gt;Imagine your &lt;code&gt;app-layout&lt;/code&gt; expects a &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; tag because of &lt;code&gt;select=&quot;nav&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But you want to project a custom component called &lt;code&gt;&amp;lt;app-top-bar&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;app-layout&amp;gt;
  &amp;lt;app-top-bar&amp;gt;&amp;lt;/app-top-bar&amp;gt;
&amp;lt;/app-layout&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your &lt;code&gt;app-top-bar&lt;/code&gt; will fall into the &lt;em&gt;default&lt;/em&gt; slot (Main), not the Header slot.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Junior Fix:&lt;/strong&gt;&lt;br&gt;
Wrap it in a nav tag.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;app-layout&amp;gt;
  &amp;lt;nav&amp;gt; &amp;lt;app-top-bar&amp;gt;&amp;lt;/app-top-bar&amp;gt;
  &amp;lt;/nav&amp;gt;
&amp;lt;/app-layout&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Problem:&lt;/em&gt; You just introduced an extra DOM node (&lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt;) that might mess up your Flexbox or Grid layout.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Senior Fix: &lt;code&gt;ngProjectAs&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
Angular has a special attribute that allows you to &quot;disguise&quot; an element so it matches a specific selector, without adding extra DOM nodes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;app-layout&amp;gt;
  &amp;lt;app-top-bar ngProjectAs=&quot;nav&quot;&amp;gt;&amp;lt;/app-top-bar&amp;gt;
&amp;lt;/app-layout&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tells Angular: &quot;Treat this component as if it were a &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; tag for the purposes of projection.&quot;&lt;/p&gt;
&lt;h4 id=&quot;the-projected-content-lifecycle-risk&quot;&gt;The &quot;Projected Content&quot; Lifecycle Risk&lt;/h4&gt;
&lt;p&gt;As a Senior Developer, you need to know the performance cost.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Projected content is instantiated EAGERLY.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you project a heavy component:&lt;br&gt;
&lt;code&gt;&amp;lt;app-modal&amp;gt;&amp;lt;app-heavy-chart&amp;gt;&amp;lt;/app-heavy-chart&amp;gt;&amp;lt;/app-modal&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;app-heavy-chart&lt;/code&gt; is created, its &lt;code&gt;constructor&lt;/code&gt; runs, and its &lt;code&gt;ngOnInit&lt;/code&gt; runs &lt;strong&gt;immediately&lt;/strong&gt;, even if the modal is hidden via &lt;code&gt;display: none&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Optimization:&lt;/strong&gt;&lt;br&gt;
If the content is extremely heavy, do not use basic Content Projection. Use &lt;strong&gt;Template Projection&lt;/strong&gt; (&lt;code&gt;ng-template&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;app-modal [contentTemplate]=&quot;myChart&quot;&amp;gt;&amp;lt;/app-modal&amp;gt;

&amp;lt;ng-template #myChart&amp;gt;
  &amp;lt;app-heavy-chart&amp;gt;&amp;lt;/app-heavy-chart&amp;gt;
&amp;lt;/ng-template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Child (Modal)
@Input() contentTemplate: TemplateRef&amp;lt;any&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;ng-container *ngTemplateOutlet=&quot;contentTemplate&quot;&amp;gt;&amp;lt;/ng-container&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This ensures the chart is not instantiated until the Modal actually renders the template outlet.&lt;/p&gt;
&lt;p&gt;Mastering these nuances—&lt;code&gt;ngProjectAs&lt;/code&gt; and &lt;code&gt;TemplateRef&lt;/code&gt;—is what separates a component library author from a component user.&lt;/p&gt;
</description>
<category>Components</category>
<guid isPermaLink="true">https://coderlegion.com/7975/architecture-pattern-compound-components-and-advanced-projection-with-ngprojectas</guid>
<pubDate>Sat, 29 Nov 2025 17:37:29 +0000</pubDate>
</item>
<item>
<title>The Senior Developer's Blueprint: Implementing Container vs. Presentational Architecture in Angular</title>
<link>https://coderlegion.com/7613/senior-developers-blueprint-implementing-container-presentational-architecture-angular</link>
<description>&lt;p&gt;In my 8 years as a frontend engineer, I’ve seen many trends come and go. But one architectural principle has remained the absolute bedrock of scalable application design: the separation of &lt;strong&gt;Container (Smart)&lt;/strong&gt; and &lt;strong&gt;Presentational (Dumb)&lt;/strong&gt; components.&lt;/p&gt;
&lt;p&gt;This isn't unique to Angular—it’s a core concept in React and Vue as well. But Angular's structured nature makes it particularly powerful to implement.&lt;/p&gt;
&lt;p&gt;If your codebase feels brittle, if you can't reuse UI widgets, or if your unit tests require endless mocking of services just to check if a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; exists, it's time to adopt this pattern.&lt;/p&gt;
&lt;h4 id=&quot;the-problem-coupled-chaos&quot;&gt;The Problem: Coupled Chaos&lt;/h4&gt;
&lt;p&gt;Let's look at a typical &quot;junior&quot; implementation of a Product List.&lt;/p&gt;
&lt;p&gt;You might have a &lt;code&gt;ProductListComponent&lt;/code&gt; that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Injects &lt;code&gt;ProductService&lt;/code&gt; and &lt;code&gt;CartService&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Injects &lt;code&gt;ActivatedRoute&lt;/code&gt; to read query params for filtering.&lt;/li&gt;
&lt;li&gt;Has an &lt;code&gt;ngOnInit&lt;/code&gt; that calls &lt;code&gt;this.productService.getProducts()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Has methods like &lt;code&gt;addToCart(id)&lt;/code&gt; that call the cart service.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Crucially, its template defines the exact HTML and CSS for how a product card looks.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You have tightly coupled your &lt;strong&gt;Business Logic&lt;/strong&gt; (fetching data, managing cart state) with your &lt;strong&gt;UI Concern&lt;/strong&gt; (rendering a product image and title).&lt;/p&gt;
&lt;p&gt;If you want to show a &quot;Recommended Products&quot; section on the cart page, you can't reuse this component. You'd have to copy-paste the HTML/CSS of the product card, leading to code duplication and UI inconsistencies.&lt;/p&gt;
&lt;h4 id=&quot;the-solution-the-smart-dumb-split&quot;&gt;The Solution: The Smart/Dumb Split&lt;/h4&gt;
&lt;p&gt;We need to take a machete to that component and split it into two distinct roles.&lt;/p&gt;
&lt;h5 id=&quot;1-the-presentational-component-dumb-&quot;&gt;1. The Presentational Component (&quot;Dumb&quot;)&lt;/h5&gt;
&lt;p&gt;This component’s sole responsibility is rendering UI. It should be pure and predictable.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Inputs:&lt;/strong&gt; It receives everything it needs to render via &lt;code&gt;@Input()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Outputs:&lt;/strong&gt; It communicates user interaction via &lt;code&gt;@Output()&lt;/code&gt; events.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dependencies:&lt;/strong&gt; NONE. It should not know that services exist.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's extract the product card UI.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// product-card.component.ts (Dumb)
@Component({
  selector: 'app-product-card',
  template: `
    &amp;lt;div class=&quot;card&quot;&amp;gt;
      &amp;lt;img [src]=&quot;product.image&quot; [alt]=&quot;product.name&quot;&amp;gt;
      &amp;lt;h3&amp;gt;{{ product.name }}&amp;lt;/h3&amp;gt;
      &amp;lt;p&amp;gt;{{ product.price | currency }}&amp;lt;/p&amp;gt;
      &amp;lt;button (click)=&quot;addToCartClicked.emit(product.id)&quot;&amp;gt;
        Add to Cart
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  `,
  // Pure UI components should always use OnPush for performance
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProductCardComponent {
  // The data required to render
  @Input({ required: true }) product!: Product;

  // The event to signal the parent
  @Output() addToCartClicked = new EventEmitter&amp;lt;string&amp;gt;();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This component is now a reusable UI asset.&lt;/p&gt;
&lt;h5 id=&quot;2-the-container-component-smart-&quot;&gt;2. The Container Component (&quot;Smart&quot;)&lt;/h5&gt;
&lt;p&gt;This component is the &quot;glue.&quot; It connects the application to the UI. It usually corresponds to a specific view or route.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Responsibilities:&lt;/strong&gt; State management, communicating with services, handling routing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Template:&lt;/strong&gt; Mostly contains other components, passing data down and listening for events up.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// product-catalog.page.ts (Smart)
@Component({
  template: `
    &amp;lt;div class=&quot;catalog-page&quot;&amp;gt;
      &amp;lt;h1&amp;gt;Our Products&amp;lt;/h1&amp;gt;
      &amp;lt;div class=&quot;grid&quot;&amp;gt;
        &amp;lt;app-product-card
          *ngFor=&quot;let p of products$ | async&quot;
          [product]=&quot;p&quot;
          (addToCartClicked)=&quot;handleAddToCart($event)&quot;&amp;gt;
        &amp;lt;/app-product-card&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  `
})
export class ProductCatalogPage {
  // Inject dependencies
  private productService = inject(ProductService);
  private cartService = inject(CartService);

  // Manage data streams
  products$ = this.productService.getAll();

  // Define business logic
  handleAddToCart(productId: string) {
    console.log('Smart component handling logic for ID:', productId);
    this.cartService.addItem(productId);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-transformative-impact&quot;&gt;The Transformative Impact&lt;/h4&gt;
&lt;p&gt;By adopting this pattern, you change how you work:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Parallel Development:&lt;/strong&gt; One developer can build the &quot;Dumb&quot; UI library using Storybook, focusing purely on CSS and visual states, while another developer builds the &quot;Smart&quot; containers and business logic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fearless Refactoring:&lt;/strong&gt; You can completely rewrite your &lt;code&gt;ProductService&lt;/code&gt; or change your state management library from NgRx to Elf, and you won't have to touch a single line of code in your &lt;code&gt;ProductCardComponent&lt;/code&gt;. The UI is isolated.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Design Consistency:&lt;/strong&gt; Since your UI components are reusable, you ensure that a Product Card looks the same everywhere in the app.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It requires discipline to not just inject a service into a component &quot;real quick&quot; because it's easier. But resisting that urge is what builds professional, scalable frontend architecture.&lt;/p&gt;
</description>
<category>Best Practices</category>
<guid isPermaLink="true">https://coderlegion.com/7613/senior-developers-blueprint-implementing-container-presentational-architecture-angular</guid>
<pubDate>Sat, 22 Nov 2025 08:47:37 +0000</pubDate>
</item>
<item>
<title>Monoliths for Discovery, Microservices for Optimization</title>
<link>https://coderlegion.com/7589/monoliths-for-discovery-microservices-for-optimization</link>
<description>&lt;p&gt;I know, I know. It is another post about microservices versus monoliths. The debate feels exhausted at this point. Yet every time I start a new project, I find myself weighing the same questions. Not because the answer is unclear, but because the answer genuinely depends on where you are and what you're trying to learn.&lt;/p&gt;
&lt;blockquote class=&quot;pull-quote&quot;&gt;
&lt;p&gt;The choice isn't about finding the &quot;right&quot; architecture in the abstract. It's about choosing what fits your context, your constraints, and most importantly, what you need to discover.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;principles-that-decide-the-choice&quot;&gt;Principles That Decide the Choice&lt;/h3&gt;
&lt;h4 id=&quot;microservices-are-optimizations-monoliths-enable-discovery&quot;&gt;Microservices Are Optimizations, Monoliths Enable Discovery&lt;/h4&gt;
&lt;p&gt;Microservices are optimizations for specific problems: team scaling, independent deployment, technology diversity. They're solutions to constraints you've already identified. Monoliths, on the other hand, accelerate discovery. They let you learn where boundaries should be, understand domains as they reveal themselves, and validate assumptions quickly without the overhead of distributed systems.&lt;/p&gt;
&lt;p&gt;That overhead is concrete. Once two parts of a system talk over the network, every call can time out, fail halfway, or arrive twice, and data that used to change in one transaction now needs a consistency strategy. Each service also needs its own deployment pipeline, monitoring, and on-call coverage. Martin Fowler calls this the microservice premium, and in his post of that name he advises against even considering microservices &quot;unless you have a system that's too complex to manage as a monolith.&quot;&lt;/p&gt;
&lt;p&gt;The premium grows with every service added. In &quot;Goodbye Microservices,&quot; Alexandra Noonan describes how Segment split its event delivery into one service per destination, for a sound reason. A slow destination's retries had flooded a shared queue and delayed all the others. The split ended at more than 140 services. Shared libraries drifted apart, and three full-time engineers spent most of their time keeping the system alive, until the team merged the services back into one. The signal justified separating slow destinations from the rest, and it didn't justify a service for every destination.&lt;/p&gt;
&lt;p&gt;The premium hurts most while the domain is still unclear, because early boundaries are mostly guesses. Inside one process, a wrong guess is fixed with a refactoring. You move a class or change a method signature, and the compiler finds every caller. Across services, the same correction means changing two APIs, coordinating two deployments, and migrating data between two databases. Fowler's companion post, &quot;MonolithFirst,&quot; makes this argument. It also reports that almost all the successful microservice stories he had heard of started with a monolith that grew too big and was broken up. He offers that as an informal observation, not a study.&lt;/p&gt;
&lt;p&gt;Start with what helps you learn fastest, then optimize when you understand what actually needs optimizing.&lt;/p&gt;
&lt;h4 id=&quot;monoliths-don-t-excuse-poor-code-quality&quot;&gt;Monoliths Don't Excuse Poor Code Quality&lt;/h4&gt;
&lt;p&gt;The stigma around monoliths comes from poor practices, not the architecture itself. Terrible monoliths exist, but so do terrible microservices. A monolith whose modules call each other's internals and write each other's tables is hard to change and harder to split. A set of microservices that share tables and must deploy together has the same coupling, with network calls added. What decides whether either system stays changeable is good design discipline, meaning SOLID principles applied at the module level and clear boundaries. Each module depends on the others' public interfaces and never on their internals.&lt;/p&gt;
&lt;p&gt;The deployment model does change one thing. A process boundary makes reaching into another module's internals impossible, while inside a monolith it takes one import. That enforcement is part of what the microservice premium buys, but during discovery it isn't worth the price, because the same process boundary makes every module boundary expensive to move.&lt;/p&gt;
&lt;p&gt;A monolith can get most of that enforcement more cheaply. It needs it, because good intentions lose to deadlines. Give each module its own database schema, and have CI load and test each module with only the modules it declares as dependencies. A shortcut then fails a build instead of slipping through review. Moving a boundary still means editing a dependency list and a schema in one repository rather than redeploying two services.&lt;/p&gt;
&lt;p&gt;Neither the per-module schemas nor the CI checks add a runtime component, a network hop, or a second deployment. Both need upkeep, such as test setups for each module, and they are far harder to retrofit than to start with.&lt;/p&gt;
&lt;h4 id=&quot;ship-before-the-architecture-is-perfect&quot;&gt;Ship Before the Architecture Is Perfect&lt;/h4&gt;
&lt;p&gt;Ship and learn. Every week spent settling the architecture before launch is a week without users to show which parts of the design matter. The better approach is to make reversible decisions, establish clear boundaries, and iterate based on what you learn from real usage. Perfection is a moving target. Momentum matters more.&lt;/p&gt;
&lt;p&gt;Shipping before the design settles works only if wrong guesses are cheap to undo, and the starting choice decides how often you pay for distribution. Extracting a module from a monolith still means moving its data and taking on partial failure for every call across the new boundary. A team that starts with a modular monolith pays that distributed cost once per boundary it has already validated. A team that starts with services pays it for every guess, and again for each guess it has to correct. Either way, a decision stays reversible when undoing it touches one module instead of every caller, which is why the next two principles are about boundaries.&lt;/p&gt;
&lt;h4 id=&quot;data-boundaries-matter-from-day-one&quot;&gt;Data Boundaries Matter From Day One&lt;/h4&gt;
&lt;p&gt;Define data ownership early, even if you're starting with a monolith. Sharing one database between modules, or even between early services, isn't inherently wrong when you're just getting started. It works as long as each table has one owning module, and everything else goes through that module's code instead of querying its tables directly.&lt;/p&gt;
&lt;p&gt;Without that rule, extraction gets expensive. When three modules write to the same orders table, pulling the order module out means finding and rewriting every one of those queries. Each cross-module join also becomes a network call or a copy of the data. Tangled data models trap teams in legacy architectures.&lt;/p&gt;
&lt;p&gt;The ownership rule keeps the cheap in-process refactoring from the first principle. When a table changes owner in a modular monolith, the compiler and the test suite find the code to change, and there are no dual writes between two databases. The rule makes an ownership change a schema migration inside one database and one deployment, rather than a data move between two services.&lt;/p&gt;
&lt;h4 id=&quot;abstract-infrastructure-early&quot;&gt;Abstract Infrastructure Early&lt;/h4&gt;
&lt;p&gt;Put one routing layer you control in front of the monolith early. An API gateway works, and so does the reverse proxy or ingress it already runs behind. Either gives you flexibility before you know what your final infrastructure will look like. When clients call that layer instead of the monolith's own address, extracting a service later can keep the address clients already use, because the route moves there. That holds when the extracted module owns its own URL paths and the new service keeps the old contract.&lt;/p&gt;
&lt;p&gt;The routing layer doesn't help with the harder server-side work of partial failure and consistency. A dedicated gateway is also one more component to run and one more network hop. It pays off most when you have clients you can't update all at once, such as mobile apps or partner integrations. For those systems, the routing layer keeps an extraction from turning into a coordinated client release.&lt;/p&gt;
&lt;h3 id=&quot;the-strongest-case-against-starting-with-a-monolith&quot;&gt;The Strongest Case Against Starting With a Monolith&lt;/h3&gt;
&lt;p&gt;Stefan Tilkov answered Fowler with &quot;Don't start with a monolith.&quot; He argues that a monolith's parts &quot;will become extremely tightly coupled to each other&quot; through shared libraries, in-process calls, and shared domain objects and persistence models, and that splitting one up afterward is extremely hard. He is right about what happens to a monolith built without discipline. That coupling is the reason the principles above exist.&lt;/p&gt;
&lt;p&gt;Module boundaries, one owner per table, and a routing layer between clients and the deployment don't make a later split cheap, but they make it a matter of cutting along seams that already exist. The queries to rewrite are the ones the owning module already exposes, instead of a search through every module for whoever touches the data. Public accounts that follow one system from monolith discovery through extraction along settled seams are rare, so this rebuttal rests on the cost mechanism above more than on a body of case studies.&lt;/p&gt;
&lt;p&gt;His argument also marks where this approach stops applying. When the boundaries are already known, as when a team rebuilds a system whose domain it has run for years, there is less left to discover. Building separately deployable parts from the start then gives up little. Discovery is the reason to start with a monolith, so the case for one weakens as the amount left to discover shrinks.&lt;/p&gt;
&lt;p&gt;Some constraints are known on day one even when the domain is new. An organization that starts a new product with several teams already has the coordination constraint. So does a component that must sit apart for compliance or security, such as card handling kept out of the rest of the system's PCI scope. A known constraint is what the optimization exists for, so a few coarse services along those lines, each a modular monolith inside, fit that case better than one shared deployment. Discovery then happens within each service, where moving a boundary is still cheap.&lt;/p&gt;
&lt;h3 id=&quot;my-approach-domain-based-modular-monoliths&quot;&gt;My Approach: Domain-Based Modular Monoliths&lt;/h3&gt;
&lt;p&gt;Start with &lt;strong&gt;domain-based modules&lt;/strong&gt;: one modular monolith organized along natural business boundaries, which Eric Evans's domain-driven design calls bounded contexts. This gives you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Clear separation of concerns from the beginning&lt;/li&gt;
&lt;li&gt;Teams that own their domain's code, even while they share a deployment&lt;/li&gt;
&lt;li&gt;A migration path to microservices that follows existing module boundaries instead of cutting new ones&lt;/li&gt;
&lt;li&gt;Faster initial development than building distributed systems from day one&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Shopify took this route with its main codebase. Kirsten Westeinde's account of the work describes reorganizing the code by business domain, such as orders, shipping, and billing, and building tooling to check that components talk only through their public interfaces. The goal, in her words, was more modularity &quot;without increasing the number of deployment units.&quot;&lt;/p&gt;
&lt;p&gt;Five years later, Gannon McGibbon and Chris Salzberg's &quot;A Packwerk Retrospective&quot; reported that static checks from Packwerk, the boundary-checking tool Shopify later built and open-sourced, hadn't been enough. A package with all its recorded violations resolved still failed to boot when loaded on its own, because of dependencies the static analysis couldn't see. What they found trustworthy was running code, so once they got that package to boot with only its own code loaded, they added a CI step that runs its tests in isolation. Packwerk 3.0 also dropped its privacy checks, the static rule meant to keep other packages out of a package's internals.&lt;/p&gt;
&lt;p&gt;Boundaries hold when something executes them, and a folder structure or a lint rule alone tends to erode one convenient shortcut at a time. Where only static checks guard the boundaries, Tilkov's coupling warning holds. Shopify's answer was a stronger check inside the monolith, not a move to services.&lt;/p&gt;
&lt;h3 id=&quot;split-when-the-evidence-shows-up&quot;&gt;Split When the Evidence Shows Up&lt;/h3&gt;
&lt;blockquote class=&quot;pull-quote&quot;&gt;
&lt;p&gt;Split into microservices when there's actual evidence it's needed: independent scaling requirements, team coordination becoming a bottleneck, or specific technology needs that justify the operational complexity.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Each of those signals, plus one more, can be checked instead of argued:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Independent scaling&lt;/strong&gt;: one module's load differs so much from the rest that scaling the whole deployment to meet it mostly buys capacity nothing else uses&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Team coordination&lt;/strong&gt;: teams routinely wait on each other to release, or a shared test suite and deploy queue hold every change to the pace of the slowest one&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Technology needs&lt;/strong&gt;: a module needs a runtime or data store the rest of the system can't host, and the gain outweighs operating a second stack&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fault isolation&lt;/strong&gt;: one part's failures or slowdowns keep spreading to unrelated parts, and timeouts, bulkheads, or separate queues inside the deployment haven't contained them&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These signals arrive late, once the pain is present. The monolith also gives an earlier one. Track the share of merged changes that touch more than one module, and how often a change moves code or tables between modules. Watch them over several releases while feature work continues. The boundaries have probably settled when three things hold: the first number is falling, the second has dropped to near zero, and no duplicated logic or swelling module is absorbing the work. That says the seams are stable enough to plan an extraction around. It isn't a reason to extract, which still waits for one of the signals above.&lt;/p&gt;
&lt;p&gt;A signal tells you that something needs to be separated, and the module boundaries tell you where the seam already runs. Until a signal shows up, the monolith is still doing the job it was chosen for, which is teaching you where the boundaries belong.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/7589/monoliths-for-discovery-microservices-for-optimization</guid>
<pubDate>Thu, 20 Nov 2025 23:41:52 +0000</pubDate>
</item>
<item>
<title>Event-Driven Updates with ThingsDB</title>
<link>https://coderlegion.com/7517/event-driven-updates-with-thingsdb</link>
<description>&lt;p&gt;In the world of real-time applications, delivering instantaneous updates to users is foundational. From collaborative documents to live chat applications, the ability to instantly reflect changes across all connected clients can make or break the user experience. &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.thingsdb.io/v1&quot;&gt;ThingsDB&lt;/a&gt;, with its robust event-driven architecture, offers a powerful solution for achieving this.&lt;/p&gt;
&lt;p&gt;Traditionally, implementing event-driven updates in a real-time application, like a chat room, often followed a pattern that could lead to unnecessary complexity. Let's break down the classic approach and then explore how ThingsDB's new &lt;em&gt;&quot;emit to peers only&quot;&lt;/em&gt; feature simplifies this process.&lt;/p&gt;
&lt;h3 id=&quot;traditional-flow&quot;&gt;Traditional Flow&lt;/h3&gt;
&lt;p&gt;Imagine a simple chat application. When a user sends a message, the desired flow is that the message appears instantly on the users screen, and via an event for everyone in the chat room.&lt;/p&gt;
&lt;p&gt;Here's how that flow typically worked:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A chat client sends a new message to the ThingsDB server.&lt;/li&gt;
&lt;li&gt;ThingsDB stores the message and then emits an event to a designated &quot;room&quot; (or topic) that all connected clients are listening to.&lt;/li&gt;
&lt;li&gt;All clients subscribed to that &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.thingsdb.io/v1/data-types/room/&quot;&gt;room&lt;/a&gt; receive the event and display the new message in their respective chat interfaces.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This approach works, but it introduces a subtle challenge: the client that sent the message also receives the same event back.&lt;/p&gt;
&lt;p&gt;Here's a visual representation of the traditional flow:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y92i3mpofjf0tjjcdjlk.png&quot; alt=&quot;visual-representation-traditional-flow&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;self-inflicted-updates&quot;&gt;Self-Inflicted Updates&lt;/h3&gt;
&lt;p&gt;While seemingly harmless, receiving one's own message back as an event can create issues, especially if the client is designed to immediately display the message locally upon sending. This leads to a double-update scenario: the message appears instantly when sent, and then again when the event is received from the server.&lt;/p&gt;
&lt;p&gt;To avoid this redundancy, developers often resorted to complex workarounds like implementing intricate code to detect if an incoming event originated from the current client, perhaps by including a unique client ID with each emitted event.&lt;/p&gt;
&lt;h3 id=&quot;the-thingsdb-solution-emitting-to-peers-only&quot;&gt;The ThingsDB Solution: Emitting to Peers Only&lt;/h3&gt;
&lt;p&gt;ThingsDB addresses this challenge elegantly with its new &lt;em&gt;&quot;emit to peers only&quot;&lt;/em&gt; option. This powerful feature allows you to broadcast events to all listeners within a room except the one that initiated the event.&lt;/p&gt;
&lt;p&gt;Here's how the simplified flow works:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A chat client sends a new message to ThingsDB, explicitly indicating that the event should only be emitted to other peers and displays the message in the chat screen.&lt;/li&gt;
&lt;li&gt;ThingsDB stores the message and then emits the event to the designated room. Crucially, it filters out the originating client from the list of recipients.&lt;/li&gt;
&lt;li&gt;All other clients subscribed to the &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.thingsdb.io/v1/data-types/room/&quot;&gt;room&lt;/a&gt; receive the event and display the new message.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Sender Updates Locally: The sending client, having already displayed the message locally, does not receive a redundant event from the server.&lt;/p&gt;
&lt;p&gt;Let's visualize this improved flow:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b3ja72tghqwgljyc7mlh.png&quot; alt=&quot;visual-representation-emit-peers-only-flow&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;beyond-chat-rooms&quot;&gt;Beyond Chat Rooms&lt;/h3&gt;
&lt;p&gt;The &lt;em&gt;&quot;emit to peers only&quot;&lt;/em&gt; concept extends far beyond chat applications. Consider any scenario where a client initiates an action and immediately reflects that change locally, while also needing to inform other connected clients:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Collaborative Document Editing:&lt;/strong&gt; When a user types a character, it appears instantly on their screen. Other collaborators see the update via an event, but the typing user doesn't need to process their own keystroke as an incoming event.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Real-time Dashboards:&lt;/strong&gt; A user performs an action that updates a metric locally. Other users viewing the dashboard receive an event to update their views, but the initiating user's dashboard is already updated.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Gaming:&lt;/strong&gt; A player moves their character. The local client updates the character's position instantly. Other players receive an event to update the character's position on their screens.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;conclusion-3&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;ThingsDB's &lt;em&gt;&quot;emit to peers only&quot;&lt;/em&gt; feature is implemented by all &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.thingsdb.io/v1/connect/&quot;&gt;supported clients&lt;/a&gt; and is addressing a common pain point in real-time application development. For any application built on the promise of instant updates, utilizing this feature means spending less time debugging logic and more time writing clean, purposeful code.&lt;/p&gt;
</description>
<category>Other</category>
<guid isPermaLink="true">https://coderlegion.com/7517/event-driven-updates-with-thingsdb</guid>
<pubDate>Wed, 19 Nov 2025 08:55:19 +0000</pubDate>
</item>
<item>
<title>Decorate services in ASP.NET Core, step by step</title>
<link>https://coderlegion.com/7347/decorate-services-in-asp-net-core-step-by-step</link>
<description>&lt;p&gt;Decorator pattern, in short&lt;/p&gt;
&lt;p&gt;The decorator pattern is a structural design pattern that lets you add behavior to an object at runtime by wrapping it with another object that has the same interface. The wrapper forwards calls to the inner object, then adds extra work before or after the call. You keep the original contract, you can stack multiple decorators, and you avoid subclass explosion by preferring composition over inheritance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key points&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Intent, attach additional responsibilities to an object dynamically&lt;/li&gt;
&lt;li&gt;How, wrap an object that implements the same interface and forward&lt;br&gt;
   calls&lt;/li&gt;
&lt;li&gt;Good for cross cutting concerns like logging, retry, caching,&lt;br&gt;
metrics, authorization&lt;/li&gt;
&lt;li&gt;Benefits, core code stays simple, behaviors are pluggable and&lt;br&gt;
testable, decorators compose&lt;/li&gt;
&lt;li&gt;Trade offs, more indirection and wiring, the order of decorators&lt;br&gt;
matters&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Why decorate in ASP.NET Core&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Decoration lets you add cross cutting behavior around a service without touching its core code, for example retry, logging, metrics, caching. It keeps concerns separated and easy to test.&lt;/p&gt;
&lt;p&gt;Packages&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotnet add package Scrutor
dotnet add package Polly
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Core service&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;

public interface IInvoicesClient
{
    Task&amp;lt;PagedResultDtoOfInvoice&amp;gt; GetInvoicesAsync(
        string filter, string orderBy, string include,
        int? pageIndex, int? pageSize, bool? transactionCurrency, bool? skipRounding,
        CancellationToken ct = default);
}

public sealed class InvoicesClient : IInvoicesClient
{
    private readonly HttpClient _http;
    private readonly string _baseUrl;
    private readonly Func&amp;lt;string&amp;gt; _getToken;
    private static readonly JsonSerializerOptions _json = new() { PropertyNameCaseInsensitive = true };

    public InvoicesClient(HttpClient http, string baseUrl, Func&amp;lt;string&amp;gt; getToken)
    {
        _http = http;
        _baseUrl = baseUrl.TrimEnd('/');
        _getToken = getToken;
    }

    public async Task&amp;lt;PagedResultDtoOfInvoice&amp;gt; GetInvoicesAsync(
        string filter, string orderBy, string include,
        int? pageIndex, int? pageSize, bool? transactionCurrency, bool? skipRounding,
        CancellationToken ct = default)
    {
        var url = BuildUrl(_baseUrl, filter, orderBy, include, pageIndex, pageSize, transactionCurrency, skipRounding);

        using var req = new HttpRequestMessage(HttpMethod.Get, url)
        {
#if NET5_0_OR_GREATER
            VersionPolicy = HttpVersionPolicy.RequestVersionOrHigher
#endif
        };

        var token = _getToken?.Invoke();
        if (!string.IsNullOrEmpty(token))
            req.Headers.Authorization = new AuthenticationHeaderValue(&quot;Bearer&quot;, token);

        using var resp = await _http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false);
        resp.EnsureSuccessStatusCode();

        await using var stream = await resp.Content.ReadAsStreamAsync(ct).ConfigureAwait(false);
        return await JsonSerializer.DeserializeAsync&amp;lt;PagedResultDtoOfInvoice&amp;gt;(stream, _json, ct).ConfigureAwait(false)
               ?? new PagedResultDtoOfInvoice();
    }

    private static string BuildUrl(
        string baseUrl, string filter, string orderBy, string include,
        int? pageIndex, int? pageSize, bool? transactionCurrency, bool? skipRounding)
    {
        var q = System.Web.HttpUtility.ParseQueryString(string.Empty);
        if (!string.IsNullOrWhiteSpace(filter)) q[&quot;filter&quot;] = filter;
        if (!string.IsNullOrWhiteSpace(orderBy)) q[&quot;orderBy&quot;] = orderBy;
        if (!string.IsNullOrWhiteSpace(include)) q[&quot;include&quot;] = include;
        if (pageIndex.HasValue) q[&quot;pageIndex&quot;] = pageIndex.Value.ToString();
        if (pageSize.HasValue) q[&quot;pageSize&quot;] = pageSize.Value.ToString();
        if (transactionCurrency.HasValue) q[&quot;transactionCurrency&quot;] = transactionCurrency.Value ? &quot;true&quot; : &quot;false&quot;;
        if (skipRounding.HasValue) q[&quot;skipRounding&quot;] = skipRounding.Value ? &quot;true&quot; : &quot;false&quot;;
        return $&quot;{baseUrl}/invoices?{q}&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Decorators&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using Polly;
using System.Net.Sockets;

public sealed class RetryInvoicesClientDecorator : IInvoicesClient
{
    private readonly IInvoicesClient _inner;
    private readonly IAsyncPolicy _retry;

    public RetryInvoicesClientDecorator(IInvoicesClient inner)
    {
        _inner = inner;
        _retry = Policy
            .Handle&amp;lt;HttpRequestException&amp;gt;()
            .Or&amp;lt;IOException&amp;gt;()
            .Or&amp;lt;SocketException&amp;gt;()
            .Or&amp;lt;TaskCanceledException&amp;gt;(ex =&amp;gt; !ex.CancellationToken.IsCancellationRequested)
            .WaitAndRetryAsync(3,
                attempt =&amp;gt; TimeSpan.FromSeconds(Math.Pow(2, attempt)) +
                           TimeSpan.FromMilliseconds(Random.Shared.Next(0, 750)));
    }

    public Task&amp;lt;PagedResultDtoOfInvoice&amp;gt; GetInvoicesAsync(
        string filter, string orderBy, string include,
        int? pageIndex, int? pageSize, bool? transactionCurrency, bool? skipRounding,
        CancellationToken ct = default)
    =&amp;gt;
        _retry.ExecuteAsync(t =&amp;gt;
            _inner.GetInvoicesAsync(filter, orderBy, include, pageIndex, pageSize, transactionCurrency, skipRounding, t),
            ct);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Logging decorator&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public sealed class LoggingInvoicesClientDecorator : IInvoicesClient
{
    private readonly IInvoicesClient _inner;
    private readonly ILogger&amp;lt;LoggingInvoicesClientDecorator&amp;gt; _log;

    public LoggingInvoicesClientDecorator(IInvoicesClient inner, ILogger&amp;lt;LoggingInvoicesClientDecorator&amp;gt; log)
    { _inner = inner; _log = log; }

    public async Task&amp;lt;PagedResultDtoOfInvoice&amp;gt; GetInvoicesAsync(
        string filter, string orderBy, string include,
        int? pageIndex, int? pageSize, bool? transactionCurrency, bool? skipRounding,
        CancellationToken ct = default)
    {
        var sw = System.Diagnostics.Stopwatch.StartNew();
        try
        {
            _log.LogInformation(&quot;Invoices request pageIndex={PageIndex} pageSize={PageSize}&quot;, pageIndex, pageSize);
            var res = await _inner.GetInvoicesAsync(filter, orderBy, include, pageIndex, pageSize, transactionCurrency, skipRounding, ct);
            _log.LogInformation(&quot;Invoices response count={Count} in {Elapsed} ms&quot;, res?.Data?.Count ?? 0, sw.ElapsedMilliseconds);
            return res;
        }
        catch (Exception ex)
        {
            _log.LogError(ex, &quot;Invoices failed in {Elapsed} ms&quot;, sw.ElapsedMilliseconds);
            throw;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Registration with Scrutor&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using Scrutor;
using System.Net;

builder.Services.AddHttpClient(&quot;Invoices&quot;, c =&amp;gt;
{
    c.Timeout = Timeout.InfiniteTimeSpan;
    c.DefaultRequestHeaders.Accept.ParseAdd(&quot;application/json&quot;);
})
.ConfigurePrimaryHttpMessageHandler(() =&amp;gt; new SocketsHttpHandler
{
    AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
    PooledConnectionLifetime = TimeSpan.FromMinutes(2),
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1),
    MaxConnectionsPerServer = 8
});

builder.Services.AddTransient&amp;lt;IInvoicesClient&amp;gt;(sp =&amp;gt;
{
    var http = sp.GetRequiredService&amp;lt;IHttpClientFactory&amp;gt;().CreateClient(&quot;Invoices&quot;);
    var cfg = sp.GetRequiredService&amp;lt;IConfiguration&amp;gt;();
    var baseUrl = cfg[&quot;Erp:BaseUrl&quot;] ?? &quot;&quot;;
    var tokenProvider = sp.GetRequiredService&amp;lt;ITokenProvider&amp;gt;();
    return new InvoicesClient(http, baseUrl, () =&amp;gt; tokenProvider.GetToken());
});

builder.Services.Decorate&amp;lt;IInvoicesClient, LoggingInvoicesClientDecorator&amp;gt;();
builder.Services.Decorate&amp;lt;IInvoicesClient, RetryInvoicesClientDecorator&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Native DI without Scrutor&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;builder.Services.AddHttpClient(&quot;Invoices&quot;, c =&amp;gt;
{
    c.Timeout = Timeout.InfiniteTimeSpan;
    c.DefaultRequestHeaders.Accept.ParseAdd(&quot;application/json&quot;);
});

builder.Services.AddTransient&amp;lt;IInvoicesClient&amp;gt;(sp =&amp;gt;
{
    var http = sp.GetRequiredService&amp;lt;IHttpClientFactory&amp;gt;().CreateClient(&quot;Invoices&quot;);
    var cfg = sp.GetRequiredService&amp;lt;IConfiguration&amp;gt;();
    var baseUrl = cfg[&quot;Erp:BaseUrl&quot;] ?? &quot;&quot;;
    var tokenProvider = sp.GetRequiredService&amp;lt;ITokenProvider&amp;gt;();

    IInvoicesClient svc = new InvoicesClient(http, baseUrl, () =&amp;gt; tokenProvider.GetToken());
    svc = new LoggingInvoicesClientDecorator(svc, sp.GetRequiredService&amp;lt;ILogger&amp;lt;LoggingInvoicesClientDecorator&amp;gt;&amp;gt;());
    svc = new RetryInvoicesClientDecorator(svc);
    return svc;
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DelegatingHandler for transport concerns&lt;/p&gt;
&lt;p&gt;Use handlers for wire level logic, keep decorators for service level logic.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public sealed class LoggingHandler : DelegatingHandler
{
    private readonly ILogger&amp;lt;LoggingHandler&amp;gt; _log;
    public LoggingHandler(ILogger&amp;lt;LoggingHandler&amp;gt; log) =&amp;gt; _log = log;

    protected override async Task&amp;lt;HttpResponseMessage&amp;gt; SendAsync(HttpRequestMessage request, CancellationToken ct)
    {
        var sw = System.Diagnostics.Stopwatch.StartNew();
        var response = await base.SendAsync(request, ct);
        _log.LogInformation(&quot;{Method} {Uri} -&amp;gt; {Status} in {Elapsed} ms&quot;,
            request.Method, request.RequestUri, (int)response.StatusCode, sw.ElapsedMilliseconds);
        return response;
    }
}

builder.Services.AddTransient&amp;lt;LoggingHandler&amp;gt;();
builder.Services.AddHttpClient(&quot;Invoices&quot;, c =&amp;gt; { /* defaults above */ })
    .AddHttpMessageHandler&amp;lt;LoggingHandler&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Best practices checklist&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;One retry layer only.&lt;/li&gt;
&lt;li&gt;Per call timeout with CancelAfter.&lt;/li&gt;
&lt;li&gt;ResponseHeadersRead for large payloads.&lt;/li&gt;
&lt;li&gt;Small page size for large ranges.&lt;/li&gt;
&lt;li&gt;Keep HttpClient immutable after registration.&lt;/li&gt;
&lt;li&gt;Put auth headers on the request message.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;References&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/App-vNext/Polly&quot;&gt;Polly, official docs and GitHub.&lt;/a&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/khellang/Scrutor&quot;&gt;Scrutor, GitHub and NuGet.&lt;/a&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory&quot;&gt;Microsoft docs, HttpClientFactory with resilience patterns.&lt;/a&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/AspNetCore.DecoratorSamples&quot;&gt;Source Code&lt;/a&gt;&lt;/p&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/7347/decorate-services-in-asp-net-core-step-by-step</guid>
<pubDate>Fri, 14 Nov 2025 11:21:56 +0000</pubDate>
</item>
<item>
<title>Reliable Messaging in .NET: Domain Events and the Outbox Pattern with EF Core Interceptors</title>
<link>https://coderlegion.com/6363/reliable-messaging-in-net-domain-events-and-the-outbox-pattern-with-ef-core-interceptors</link>
<description>&lt;h3 id=&quot;introduction-1&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;Distributed systems are powerful — but they come with one big challenge: &lt;strong&gt;reliability&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;What happens if your application updates a record in the database and then tries to publish a message to a message broker… but crashes in between?&lt;br&gt;
You’ve just lost an event.&lt;/p&gt;
&lt;p&gt;This problem is common in microservices, and it’s why the &lt;strong&gt;Outbox Pattern&lt;/strong&gt; exists — a simple, elegant approach that guarantees message delivery without introducing distributed transactions or external coordinators.&lt;/p&gt;
&lt;p&gt;In this article, we’ll implement a &lt;strong&gt;Domain Event + Outbox Pattern&lt;/strong&gt; in &lt;strong&gt;.NET 9&lt;/strong&gt;, using &lt;strong&gt;Entity Framework Core&lt;/strong&gt;, &lt;strong&gt;SQLite&lt;/strong&gt;, and an &lt;strong&gt;EF Core SaveChangesInterceptor&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-a-quick-look-at-entity-framework-core&quot;&gt;⚙️ A Quick Look at Entity Framework Core&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Entity Framework Core (EF Core)&lt;/strong&gt; is Microsoft’s modern &lt;strong&gt;Object-Relational Mapper (ORM)&lt;/strong&gt; for .NET.&lt;br&gt;
It allows developers to work with a database using &lt;strong&gt;C# classes&lt;/strong&gt; instead of raw SQL, automatically handling relationships, migrations, and change tracking.&lt;/p&gt;
&lt;p&gt;When you call &lt;code&gt;SaveChanges()&lt;/code&gt; or &lt;code&gt;SaveChangesAsync()&lt;/code&gt;, EF Core:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tracks your entity changes.&lt;/li&gt;
&lt;li&gt;Generates SQL commands.&lt;/li&gt;
&lt;li&gt;Executes them in a transaction.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This makes EF Core the perfect place to integrate the &lt;strong&gt;Outbox Pattern&lt;/strong&gt;, since we can hook into its save pipeline and ensure domain events are written in the same transaction as the data itself.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-are-interceptors-&quot;&gt;What Are Interceptors?&lt;/h3&gt;
&lt;p&gt;In EF Core, &lt;strong&gt;interceptors&lt;/strong&gt; are powerful hooks that allow you to intercept and extend the framework’s internal operations — such as saving, querying, or connecting to the database.&lt;/p&gt;
&lt;p&gt;They work like &lt;strong&gt;middleware for EF Core&lt;/strong&gt;:&lt;br&gt;
you can observe, modify, or add logic before and after EF performs certain actions.&lt;/p&gt;
&lt;p&gt;There are several types:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SaveChangesInterceptor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CommandInterceptor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ConnectionInterceptor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TransactionInterceptor&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this article, we’ll use &lt;strong&gt;SaveChangesInterceptor&lt;/strong&gt; to detect domain events before EF saves, serialize them, and insert them into our Outbox table.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem&quot;&gt;The Problem&lt;/h3&gt;
&lt;p&gt;In traditional architectures, your service might do something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;await _db.SaveChangesAsync();
await _bus.PublishAsync(new OrderPlacedEvent(orderId));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your app crashes after saving but before publishing, your event never reaches other services — resulting in inconsistent state across systems.&lt;/p&gt;
&lt;p&gt;We need a way to ensure &lt;strong&gt;atomicity&lt;/strong&gt;: either both the data and the event are persisted, or neither are.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-outbox-pattern-in-a-nutshell-&quot;&gt;The Outbox Pattern (In a Nutshell)&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;Outbox Pattern&lt;/strong&gt; solves this by introducing a dedicated &lt;strong&gt;Outbox table&lt;/strong&gt; inside your database.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When your aggregate raises a domain event, it’s &lt;strong&gt;captured and saved&lt;/strong&gt; into the Outbox table &lt;strong&gt;within the same transaction&lt;/strong&gt; as your entity.&lt;/li&gt;
&lt;li&gt;A background worker (or a separate service) &lt;strong&gt;polls&lt;/strong&gt; the Outbox table, &lt;strong&gt;publishes events&lt;/strong&gt;, and marks them as processed.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This way, even if your app crashes, the events are safely stored and can be retried later.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-architecture&quot;&gt;The Architecture&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;┌──────────────────────────────┐
│         Application          │
│ ──────────────────────────── │
│  Order raises DomainEvent    │
│  → EF Interceptor writes     │
│    Outbox row                │
│  → Transaction commits       │
└──────────────┬───────────────┘
               │
               ▼
        Outbox Dispatcher
       ├─ Polls database
       ├─ Publishes messages
       └─ Marks them processed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This pattern elegantly blends &lt;strong&gt;Domain-Driven Design&lt;/strong&gt; concepts with &lt;strong&gt;event-driven reliability&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;domain-events&quot;&gt;Domain Events&lt;/h3&gt;
&lt;p&gt;Domain events capture something meaningful that happened in your domain model.&lt;br&gt;
For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public sealed record OrderPlacedDomainEvent(Guid OrderId, decimal Total)
    : DomainEvent(DateTime.UtcNow);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And your aggregate raises them naturally:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public sealed class Order : IHasDomainEvents
{
    private readonly List&amp;lt;DomainEvent&amp;gt; _domainEvents = new();
    public Guid Id { get; private set; } = Guid.NewGuid();
    public decimal Total { get; private set; }

    public IReadOnlyCollection&amp;lt;DomainEvent&amp;gt; DomainEvents =&amp;gt; _domainEvents;

    public Order(decimal total)
    {
        Total = total;
        AddEvent(new OrderPlacedDomainEvent(Id, Total));
    }

    private void AddEvent(DomainEvent @event) =&amp;gt; _domainEvents.Add(@event);
    public void ClearDomainEvents() =&amp;gt; _domainEvents.Clear();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-capturing-events-with-an-ef-core-interceptor&quot;&gt;⚙️ Capturing Events with an EF Core Interceptor&lt;/h3&gt;
&lt;p&gt;Instead of overriding &lt;code&gt;SaveChangesAsync()&lt;/code&gt; directly inside &lt;code&gt;AppDbContext&lt;/code&gt;, we can use a &lt;strong&gt;SaveChangesInterceptor&lt;/strong&gt; — a clean, composable way to plug custom logic into EF’s lifecycle.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;
public sealed class OutboxSaveChangesInterceptor(IEventSerializer serializer) : SaveChangesInterceptor
{
    /// &amp;lt;summary&amp;gt;
    /// 
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;param name=&quot;eventData&quot;&amp;gt;&amp;lt;/param&amp;gt;
    /// &amp;lt;param name=&quot;result&quot;&amp;gt;&amp;lt;/param&amp;gt;
    /// &amp;lt;param name=&quot;cancellationToken&quot;&amp;gt;&amp;lt;/param&amp;gt;
    /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
    public override async ValueTask&amp;lt;InterceptionResult&amp;lt;int&amp;gt;&amp;gt; SavingChangesAsync(
        DbContextEventData eventData,
        InterceptionResult&amp;lt;int&amp;gt; result,
        CancellationToken cancellationToken = default)
    {
        var context = eventData.Context;
        if (context is null) 
            return await base.SavingChangesAsync(eventData, result, cancellationToken);

        var aggregates = context.ChangeTracker
                    .Entries()
                    .Where(e =&amp;gt; e.Entity is IHasDomainEvents hasEvents &amp;amp;&amp;amp; hasEvents.DomainEvents.Count &amp;gt; 0)
                    .Select(e =&amp;gt; (IHasDomainEvents)e.Entity)
                    .ToList();

        if (aggregates.Count == 0)
            return await base.SavingChangesAsync(eventData, result, cancellationToken);

        var outbox = context.Set&amp;lt;OutboxMessage&amp;gt;();

        foreach (var aggregate in aggregates)
        {
            foreach (var @event in aggregate.DomainEvents)
            {
                outbox.Add(new OutboxMessage
                {
                    OccurredOnUtc = @event.OccurredOnUtc,
                    Type = @event.GetType().AssemblyQualifiedName!,
                    Payload = serializer.Serialize(@event)
                });
            }
        }

        return await base.SavingChangesAsync(eventData, result, cancellationToken);
    }

}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now every time you call &lt;code&gt;SaveChangesAsync()&lt;/code&gt;, domain events are written to your &lt;strong&gt;OutboxMessages&lt;/strong&gt; table automatically — in the same transaction.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-outbox-dispatcher&quot;&gt;The Outbox Dispatcher&lt;/h3&gt;
&lt;p&gt;Once the transaction is committed, a background worker can safely process these messages.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;
public sealed class OutboxDispatcher(IServiceProvider sp, 
    ILogger&amp;lt;OutboxDispatcher&amp;gt; logger) : BackgroundService
{
    private readonly IServiceProvider _sp = sp;
    private readonly ILogger&amp;lt;OutboxDispatcher&amp;gt; _logger = logger;
    private readonly TimeSpan _pollInterval = TimeSpan.FromSeconds(2);
    private const int BatchSize = 50;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(&quot; Outbox Dispatcher started...&quot;);

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                using var scope = _sp.CreateScope();
                var db = scope.ServiceProvider.GetRequiredService&amp;lt;AppDbContext&amp;gt;();

                var jsonSerializer = scope.ServiceProvider.GetRequiredService&amp;lt;IEventSerializer&amp;gt;();

                var pending = await db.OutboxMessages
                    .Where(x =&amp;gt; x.ProcessedOnUtc == null)
                    .OrderBy(x =&amp;gt; x.Id)
                    .Take(BatchSize)
                    .ToListAsync(stoppingToken);

                foreach (var msg in pending)
                {
                    try
                    {
                        var evt = jsonSerializer.Deserialize(msg.Payload, msg.Type);
                        await SimulatedBusPublish(evt, stoppingToken);
                        msg.ProcessedOnUtc = DateTime.UtcNow;
                        msg.Error = null;
                    }
                    catch (Exception ex)
                    {
                        msg.Error = ex.Message;
                        _logger.LogError(ex, &quot;❌ Error dispatching OutboxMessage {Id}&quot;, msg.Id);
                    }
                }

                if (pending.Count &amp;gt; 0)
                    await db.SaveChangesAsync(stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, &quot;Outbox dispatcher loop error&quot;);
            }

            await Task.Delay(_pollInterval, stoppingToken);
        }
    }

    private Task SimulatedBusPublish(DomainEvent evt, CancellationToken ct)
        =&amp;gt; evt switch
        {
            OrderPlacedDomainEvent e =&amp;gt; HandleOrderPlaced(e),
            _ =&amp;gt; Task.CompletedTask
        };

    private Task HandleOrderPlaced(OrderPlacedDomainEvent e)
    {
        Console.WriteLine($&quot;[BUS] ✅ OrderPlaced published =&amp;gt; OrderId={e.OrderId}, Total={e.Total}&quot;);

        return Task.CompletedTask;
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-use-an-interceptor-&quot;&gt;Why Use an Interceptor?&lt;/h3&gt;
&lt;p&gt;Using an &lt;strong&gt;interceptor&lt;/strong&gt; instead of overriding &lt;code&gt;SaveChangesAsync()&lt;/code&gt; has several advantages:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Approach                      &lt;/th&gt;&lt;th&gt;Pros                          &lt;/th&gt;&lt;th&gt;Cons&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Override SaveChangesAsync&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Simple to start               &lt;/td&gt;&lt;td&gt;Harder to reuse across contexts&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Use Interceptor&lt;/strong&gt;           &lt;/td&gt;&lt;td&gt;Decoupled, reusable, testable &lt;/td&gt;&lt;td&gt;Slightly more setup&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Interceptors follow the &lt;strong&gt;Open/Closed Principle&lt;/strong&gt; — your DbContext remains focused on persistence, while cross-cutting logic (like event logging, auditing, or outbox handling) is easily attachable.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;benefits-of-the-outbox-pattern&quot;&gt;Benefits of the Outbox Pattern&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Transactional Safety&lt;/strong&gt; — data + events saved atomically&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Guaranteed Delivery&lt;/strong&gt; — retries handled by dispatcher&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;DDD Alignment&lt;/strong&gt; — aggregates raise domain events naturally&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Resilience&lt;/strong&gt; — works even if message broker is offline&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Extensibility&lt;/strong&gt; — can integrate with RabbitMQ, Kafka, or Azure Service Bus later&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;references-5&quot;&gt;References&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/logging-events-diagnostics/interceptors&quot;&gt;Microsoft Docs – EF Core Interceptors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://martinfowler.com/articles/patterns-of-distributed-systems/transactional-outbox.html&quot;&gt;Martin Fowler – Transactional Outbox Pattern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://udidahan.com/2008/05/14/when-to-use-sagas/&quot;&gt;Udi Dahan – Reliable Messaging without Distributed Transactions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/core/extensions/workers&quot;&gt;Microsoft Docs – BackgroundService in .NET&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.jimmybogard.com/domain-events-outbox-pattern/&quot;&gt;Jimmy Bogard – Domain Events and the Outbox Pattern in .NET&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/OutboxPattern&quot;&gt;Source Code &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-closing-thoughts&quot;&gt;✨ Closing Thoughts&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;Domain Events + Outbox Pattern&lt;/strong&gt; is one of the most practical ways to achieve &lt;strong&gt;reliable, eventually consistent event-driven systems&lt;/strong&gt; in .NET.&lt;/p&gt;
&lt;p&gt;By combining &lt;strong&gt;Entity Framework Core&lt;/strong&gt;, &lt;strong&gt;SaveChangesInterceptor&lt;/strong&gt;, and a simple &lt;strong&gt;background dispatcher&lt;/strong&gt;, you can ensure every event is persisted and delivered — even in the face of failure.&lt;/p&gt;
&lt;p&gt;Small pattern. Big reliability. &lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;author&quot;&gt;Author&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Spyros Ponaris&lt;/strong&gt;&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/spyros-ponaris-913a6937/&quot;&gt;LinkedIn&lt;/a&gt;&lt;br&gt;
 Senior Software Engineer &amp;amp; .NET Enthusiast&lt;/p&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/6363/reliable-messaging-in-net-domain-events-and-the-outbox-pattern-with-ef-core-interceptors</guid>
<pubDate>Mon, 13 Oct 2025 11:27:53 +0000</pubDate>
</item>
<item>
<title>MassTransit in ASP.NET Core: A Practical Guide to Event-Driven .NET</title>
<link>https://coderlegion.com/5749/masstransit-in-asp-net-core-a-practical-guide-to-event-driven-net</link>
<description>&lt;p&gt;MassTransit in ASP.NET Core: A Practical Guide to Event-Driven .NET&lt;br&gt;
Intro&lt;/p&gt;
&lt;p&gt;Event-driven architectures help teams decouple services, scale independently, and handle failures gracefully. MassTransit is a mature, open-source library that makes message-based workflows in .NET straightforward. This guide shows a minimal but production-ready setup in ASP.NET Core with RabbitMQ.&lt;/p&gt;
&lt;p&gt;1) Install packages&lt;/p&gt;
&lt;p&gt;From your Web API project:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotnet add package MassTransit
dotnet add package MassTransit.RabbitMQ
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(For Azure Service Bus use MassTransit.Azure.ServiceBus.Core instead.)&lt;/p&gt;
&lt;p&gt;2) Define a message contract&lt;/p&gt;
&lt;p&gt;Contracts should be versionable and live in a shared project.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public interface SubmitOrder
{
    Guid OrderId { get; }
    string CustomerId { get; }
    DateTime Timestamp { get; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3) Create a consumer&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using MassTransit;

public sealed class SubmitOrderConsumer : IConsumer&amp;lt;SubmitOrder&amp;gt;
{
    public async Task Consume(ConsumeContext&amp;lt;SubmitOrder&amp;gt; context)
    {
        var msg = context.Message;
        // Your domain logic here (idempotent!)
        Console.WriteLine($&quot;Received SubmitOrder {msg.OrderId} for {msg.CustomerId}&quot;);
        await Task.CompletedTask;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4) Configure MassTransit in Program.cs (.NET 9 minimal hosting)&lt;/p&gt;
&lt;p&gt;RabbitMQ example with retries and health checks.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using MassTransit;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMassTransit(cfg =&amp;gt;
{
    cfg.SetKebabCaseEndpointNameFormatter();

    cfg.AddConsumer&amp;lt;SubmitOrderConsumer&amp;gt;(c =&amp;gt;
    {
        // optional: configure consumer-level retry, etc.
    });

    cfg.UsingRabbitMq((context, bus) =&amp;gt;
    {
        bus.Host(&quot;rabbitmq&quot;, h =&amp;gt;
        {
            h.Username(&quot;guest&quot;);
            h.Password(&quot;guest&quot;);
        });

        bus.ReceiveEndpoint(&quot;submit-order-queue&quot;, e =&amp;gt;
        {
            e.ConfigureConsumeTopology = false; 
            e.ConfigureConsumer&amp;lt;SubmitOrderConsumer&amp;gt;(context);

            e.UseMessageRetry(r =&amp;gt; r.Interval(3, TimeSpan.FromSeconds(5)));
            e.PrefetchCount = 16;
            e.ConcurrentMessageLimit = 8;
        });
    });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;builder.Services.AddHealthChecks();&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var app = builder.Build();
app.MapHealthChecks(&quot;/health&quot;);
app.MapGet(&quot;/&quot;, () =&amp;gt; &quot;OK&quot;);
app.Run();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Docker tip&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you run RabbitMQ locally via Docker:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -d --hostname rabbit \
  -p 5672:5672 -p 15672:15672 \
  --name rabbitmq rabbitmq:3-management
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;UI is at &lt;a rel=&quot;nofollow&quot; href=&quot;http://localhost:15672&quot;&gt;http://localhost:15672&lt;/a&gt; (guest/guest).&lt;/p&gt;
&lt;p&gt;5) Publish a message (from a Controller or Service)&lt;/p&gt;
&lt;p&gt;Inject IPublishEndpoint for pub/sub or ISendEndpointProvider for point-to-point.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using MassTransit;

public sealed class OrderAppService
{
    private readonly IPublishEndpoint _publish;
    public OrderAppService(IPublishEndpoint publish) =&amp;gt; _publish = publish;

    public Task SubmitAsync(Guid orderId, string customerId)
        =&amp;gt; _publish.Publish&amp;lt;SubmitOrder&amp;gt;(new
        {
            OrderId = orderId,
            CustomerId = customerId,
            Timestamp = DateTime.UtcNow
        });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or via an endpoint (send to a specific queue):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public sealed class OrderSender
{
    private readonly ISendEndpointProvider _send;
    public OrderSender(ISendEndpointProvider send) =&amp;gt; _send = send;

    public async Task SendAsync(Guid orderId, string customerId)
    {
        var endpoint = await _send.GetSendEndpoint(new Uri(&quot;queue:submit-order-queue&quot;));
        await endpoint.Send&amp;lt;SubmitOrder&amp;gt;(new { OrderId = orderId, CustomerId = customerId, Timestamp = DateTime.UtcNow });
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;6) Error handling, retries, and observability&lt;/p&gt;
&lt;p&gt;Retries: use UseMessageRetry on endpoints or the bus. Prefer bounded retries with intervals or exponential backoff.&lt;/p&gt;
&lt;p&gt;Poison messages: failed messages after retries land in _error queues automatically.&lt;/p&gt;
&lt;p&gt;Health checks: expose /health and rely on container orchestration to restart unhealthy pods.&lt;/p&gt;
&lt;p&gt;Idempotency: make consumers safe to reprocess (e.g., check a processed table or use dedup keys).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;7) (Optional) Outbox &amp;amp; transactions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you publish events within a DB transaction, consider an outbox pattern (MassTransit integrates with EFCore Outbox) to avoid dual-write issues and ensure at-least-once delivery without duplicates.&lt;/p&gt;
&lt;p&gt;8) Azure Service Bus variant (quick sketch)&lt;/p&gt;
&lt;p&gt;Swap the transport:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;builder.Services.AddMassTransit(cfg =&amp;gt;
{
    cfg.AddConsumer&amp;lt;SubmitOrderConsumer&amp;gt;();

    cfg.UsingAzureServiceBus((context, bus) =&amp;gt;
    {
        bus.Host(builder.Configuration[&quot;ASB_CONNECTION&quot;]!);

        bus.SubscriptionEndpoint&amp;lt;SubmitOrder&amp;gt;(&quot;submit-order-sub&quot;, e =&amp;gt;
        {
            e.ConfigureConsumer&amp;lt;SubmitOrderConsumer&amp;gt;(context);
            e.MaxConcurrentCalls = 8;
        });
    });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Conclusion&lt;/p&gt;
&lt;p&gt;MassTransit keeps the happy path simple while giving you the tools for serious systems: retries, sagas, scheduling, observability, and transport flexibility. Start minimal, add policies as you learn your failure modes, and keep consumers idempotent.&lt;/p&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/5749/masstransit-in-asp-net-core-a-practical-guide-to-event-driven-net</guid>
<pubDate>Mon, 29 Sep 2025 09:25:53 +0000</pubDate>
</item>
<item>
<title>Building a Pub/Sub System in .NET: MassTransit, Reactive Extensions, and BlockingCollection</title>
<link>https://coderlegion.com/5021/building-pub-sub-system-in-net-masstransit-reactive-extensions-and-blockingcollection</link>
<description>&lt;p&gt;Modern applications often need to broadcast events across multiple services.&lt;br&gt;
Think of an order being placed in an e-commerce system. The order service publishes an event, and multiple subscribers react independently:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;the billing service charges the customer,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the email service sends a receipt,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the analytics service tracks the order.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is the essence of the Publish/Subscribe (Pub/Sub) pattern.&lt;/p&gt;
&lt;p&gt;In this post we’ll explore what it is, why it matters, and three implementations in .NET:&lt;/p&gt;
&lt;p&gt;MassTransit with RabbitMQ (for distributed systems)&lt;/p&gt;
&lt;p&gt;Reactive Extensions (Rx) (for in-memory event streams)&lt;/p&gt;
&lt;p&gt;BlockingCollection (for producer/consumer pipelines)&lt;/p&gt;
&lt;p&gt; What is the Pub/Sub Pattern&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Pub/Sub pattern decouples senders from receivers.&lt;/li&gt;
&lt;li&gt;The Publisher creates a message (e.g., OrderSubmitted) and sends it&lt;br&gt;
to a broker or channel.&lt;/li&gt;
&lt;li&gt;The Broker delivers that message to all interested Subscribers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each Subscriber receives a copy of the message and processes it independently.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Benefits&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Decoupling — publishers do not know subscribers&lt;/li&gt;
&lt;li&gt;Scalability — multiple subscribers can process in parallel&lt;/li&gt;
&lt;li&gt;Flexibility — add or remove subscribers without changing the&lt;br&gt;
publisher&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Why it matters&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This pattern has been around for decades in middleware like IBM MQ, JMS, RabbitMQ, and Kafka.&lt;br&gt;
It is fundamental in:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Event-driven microservices&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Real-time systems (chat apps, trading platforms)&lt;/li&gt;
&lt;li&gt;Notification and alerting systems&lt;/li&gt;
&lt;li&gt;Data pipelines and analytics&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;IoT device communication&lt;/p&gt;
&lt;p&gt;At its core, Pub/Sub enables loose coupling and asynchronous communication.&lt;/p&gt;
&lt;p&gt;⚙️ Hosted Services in ASP.NET Core&lt;/p&gt;
&lt;p&gt;A hosted service is a background task that runs with your app host.&lt;br&gt;
In .NET you typically inherit from BackgroundService and implement ExecuteAsync.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class SampleWorker : BackgroundService
{
    private readonly ILogger&amp;lt;SampleWorker&amp;gt; _log;
    public SampleWorker(ILogger&amp;lt;SampleWorker&amp;gt; log) =&amp;gt; _log = log;

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        _log.LogInformation(&quot;Worker started&quot;);
        while (!ct.IsCancellationRequested)
        {
            // do work here
            await Task.Delay(TimeSpan.FromSeconds(5), ct);
        }
        _log.LogInformation(&quot;Worker stopping&quot;);
    }
}

// Register in Program.cs
builder.Services.AddHostedService&amp;lt;SampleWorker&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the &lt;a rel=&quot;nofollow&quot; href=&quot;https://masstransit.io/&quot;&gt;MassTransit&lt;/a&gt; demo below, a PublisherService is implemented as a hosted service to periodically publish messages.&lt;/p&gt;
&lt;p&gt;1️⃣ Pub/Sub with MassTransit and RabbitMQ&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;appsettings.json
{
  &quot;RabbitMq&quot;: { &quot;Host&quot;: &quot;localhost&quot;, &quot;Username&quot;: &quot;guest&quot;, &quot;Password&quot;: &quot;guest&quot; },
  &quot;PublishIntervalSeconds&quot;: 5
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OrderSubmitted.cs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;namespace MassTransitDemo;

public record OrderSubmitted(Guid OrderId, string CustomerEmail, decimal Total);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OrderSubmittedConsumer.cs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using MassTransit;
using Microsoft.Extensions.Logging;

namespace MassTransitDemo;

public class OrderSubmittedConsumer(ILogger&amp;lt;OrderSubmittedConsumer&amp;gt; logger) : IConsumer&amp;lt;OrderSubmitted&amp;gt;
{
    public Task Consume(ConsumeContext&amp;lt;OrderSubmitted&amp;gt; context)
    {
        var m = context.Message;
        logger.LogInformation(&quot;Consumed OrderSubmitted, OrderId {OrderId}, Email {Email}, Total {Total}&quot;,
            m.OrderId, m.CustomerEmail, m.Total);
        return Task.CompletedTask;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PublisherService.cs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using MassTransit;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace MassTransitDemo;

public class PublisherOptions
{
    public int PublishIntervalSeconds { get; set; } = 5;
}

public class PublisherService(
    ILogger&amp;lt;PublisherService&amp;gt; logger,
    IPublishEndpoint publishEndpoint,
    IOptions&amp;lt;PublisherOptions&amp;gt; options) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var delay = TimeSpan.FromSeconds(options.Value.PublishIntervalSeconds);
        logger.LogInformation(&quot;Publisher started, interval {Seconds}s&quot;, options.Value.PublishIntervalSeconds);

        while (!stoppingToken.IsCancellationRequested)
        {
            var msg = new OrderSubmitted(Guid.NewGuid(), &quot;*Emails are not allowed*&quot;, Random.Shared.Next(50, 300));
            await publishEndpoint.Publish(msg, stoppingToken);
            logger.LogInformation(&quot;Published OrderSubmitted, OrderId {OrderId}&quot;, msg.OrderId);
            await Task.Delay(delay, stoppingToken);
        }

        logger.LogInformation(&quot;Publisher stopping&quot;);
    }
}

Program.cs

    using MassTransit;
    using MassTransitDemo;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Logging;
    
    var builder = Host.CreateApplicationBuilder(args);
    
    builder.Configuration.AddJsonFile(&quot;appsettings.json&quot;, optional: true, reloadOnChange: true);
    builder.Services.Configure&amp;lt;PublisherOptions&amp;gt;(builder.Configuration);
    
    builder.Services.AddMassTransit(mt =&amp;gt;
    {
        mt.AddConsumer&amp;lt;OrderSubmittedConsumer&amp;gt;();
    
        mt.UsingRabbitMq((ctx, cfg) =&amp;gt;
        {
            var section = builder.Configuration.GetSection(&quot;RabbitMq&quot;);
            cfg.Host(section[&quot;Host&quot;], h =&amp;gt;
            {
                h.Username(section[&quot;Username&quot;]);
                h.Password(section[&quot;Password&quot;]);
            });
    
            cfg.ReceiveEndpoint(&quot;masstransitdemo-order-submitted&quot;, e =&amp;gt;
            {
                e.ConfigureConsumer&amp;lt;OrderSubmittedConsumer&amp;gt;(ctx);
            });
    
            cfg.UseMessageRetry(r =&amp;gt; r.Interval(3, TimeSpan.FromSeconds(5)));
            cfg.UseInMemoryOutbox();
        });
    });
    
    builder.Services.AddHostedService&amp;lt;PublisherService&amp;gt;();
    
    builder.Logging.ClearProviders();
    builder.Logging.AddSimpleConsole(o =&amp;gt;
    {
        o.SingleLine = true;
        o.TimestampFormat = &quot;HH:mm:ss &quot;;
    });
    
    await builder.Build().RunAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run RabbitMQ&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -it --rm -p 5672:5672 -p 15672:15672 rabbitmq:3-management
# UI at http://localhost:15672  user guest  pass guest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MassTransit is a distributed application framework for message brokers like RabbitMQ and Azure Service Bus. It takes care of serialization, consumer lifecycle, retries, and endpoint setup.&lt;/p&gt;
&lt;p&gt;2️⃣ Pub/Sub with Reactive Extensions (Rx)&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/dotnet/reactive&quot;&gt;Reactive Extensions (Rx)&lt;/a&gt;&lt;br&gt;
 is a library for composing asynchronous and event based programs with observable sequences.&lt;/p&gt;
&lt;p&gt;Uses IObservable (event source) and IObserver (subscriber)&lt;/p&gt;
&lt;p&gt;Provides LINQ-style operators (Where, Select, Buffer) for event streams&lt;/p&gt;
&lt;p&gt;Perfect for in-memory event pipelines and UI scenarios&lt;/p&gt;
&lt;p&gt;⚠️ Limitation: Rx is in-memory only. Events vanish when the app stops.&lt;/p&gt;
&lt;p&gt;Program.cs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using System;
using System.Reactive.Linq;
using System.Reactive.Subjects;

public record OrderSubmitted(Guid OrderId, string CustomerEmail, decimal Total);

class Program
{
    static void Main()
    {
        var bus = new Subject&amp;lt;OrderSubmitted&amp;gt;();

        // Subscriber A
        var subA = bus.Subscribe(order =&amp;gt;
            Console.WriteLine($&quot;[Email] Send receipt to {order.CustomerEmail}&quot;));

        // Subscriber B with filter and transform
        var subB = bus
            .Where(o =&amp;gt; o.Total &amp;gt;= 100)
            .Select(o =&amp;gt; new { o.OrderId, Vat = o.Total * 0.24m })
            .Subscribe(x =&amp;gt;
                Console.WriteLine($&quot;[Analytics] Order {x.OrderId}, VAT {x.Vat:F2}&quot;));

        // Publish a few messages
        bus.OnNext(new OrderSubmitted(Guid.NewGuid(), &quot;*Emails are not allowed*&quot;, 79.90m));
        bus.OnNext(new OrderSubmitted(Guid.NewGuid(), &quot;*Emails are not allowed*&quot;, 149.00m));
        bus.OnNext(new OrderSubmitted(Guid.NewGuid(), &quot;*Emails are not allowed*&quot;, 220.00m));

        bus.OnCompleted();
        subA.Dispose();
        subB.Dispose();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3️⃣ Pub/Sub with BlockingCollection&lt;/p&gt;
&lt;p&gt;BlockingCollection is a thread safe producer and consumer collection in .NET.&lt;/p&gt;
&lt;p&gt;Supports blocking (consumers wait until data is available)&lt;/p&gt;
&lt;p&gt;Supports bounding (limit capacity to avoid flooding)&lt;/p&gt;
&lt;p&gt;Useful for background workers, pipelines, or batch processors&lt;/p&gt;
&lt;p&gt;Program.cs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using System.Collections.Concurrent;

public record OrderSubmitted(Guid OrderId, string CustomerEmail, decimal Total);

class Program
{
    static async Task Main()
    {
        using var queue = new BlockingCollection&amp;lt;OrderSubmitted&amp;gt;(boundedCapacity: 100);

        // Subscriber A
        var emailTask = Task.Run(() =&amp;gt;
        {
            foreach (var order in queue.GetConsumingEnumerable())
                Console.WriteLine($&quot;[Email] Send receipt to {order.CustomerEmail}&quot;);
        });

        // Subscriber B
        var analyticsTask = Task.Run(() =&amp;gt;
        {
            foreach (var order in queue.GetConsumingEnumerable())
                Console.WriteLine($&quot;[Analytics] Track order {order.OrderId}, total {order.Total}&quot;);
        });

        // Publisher
        foreach (var price in new[] { 49.50m, 129.00m, 250.00m, 15.00m })
        {
            queue.Add(new OrderSubmitted(Guid.NewGuid(), $&quot;user{price}@example.com&quot;, price));
        }

        queue.CompleteAdding();
        await Task.WhenAll(emailTask, analyticsTask);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;⚠️ Limitation: Only works inside a single process. No distribution.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=250057942209870061&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt; References&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://masstransit.io/&quot;&gt;MassTransit Documentation&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MassTransit/MassTransit&quot;&gt;MassTransit GitHub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.rabbitmq.com/tutorials/tutorial-one-dotnet.html&quot;&gt;RabbitMQ Tutorials&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/core/extensions/workers&quot;&gt;Microsoft Docs: Worker Services&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/azure/architecture/patterns/publisher-subscriber&quot;&gt;Microsoft Docs: Publisher-Subscriber Pattern&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/dotnet/reactive&quot;&gt;System.Reactive GitHub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/standard/collections/thread-safe/blockingcollection-overview&quot;&gt;BlockingCollection Overview&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;✅ With these three approaches, you now have the full spectrum of Pub/Sub in .NET:&lt;/p&gt;
&lt;p&gt;Lightweight in-memory streams with Rx and BlockingCollection&lt;/p&gt;
&lt;p&gt;Robust distributed messaging with MassTransit and RabbitMQ&lt;/p&gt;
&lt;p&gt;The Pub/Sub pattern has lasted decades because it makes systems more modular, more scalable, and more resilient to change.&lt;/p&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/5021/building-pub-sub-system-in-net-masstransit-reactive-extensions-and-blockingcollection</guid>
<pubDate>Tue, 26 Aug 2025 13:57:29 +0000</pubDate>
</item>
<item>
<title>Saga Pattern — an Introduction</title>
<link>https://coderlegion.com/4763/saga-pattern-an-introduction</link>
<description>&lt;p&gt;In the world of microservices and distributed systems, managing data consistency across multiple services presents unique challenges. Traditional database transactions with their ACID guarantees work beautifully within a single database, but they fall short when your business logic spans multiple services, each with its own database. Enter the Saga pattern—a powerful approach to handling distributed transactions that has become essential in modern microservices architectures.&lt;/p&gt;
&lt;p&gt;You can read the blog or &lt;strong&gt;read the&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf&quot;&gt;research paper&lt;/a&gt;. I recommend the latter.&lt;/p&gt;
&lt;p&gt;Sample projects:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rajkundalia/online-store-saga-choreography&quot;&gt;Online Store Saga Choreography Example&lt;/a&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rajkundalia/hotel-booking-saga-orchestration&quot;&gt;Hotel Booking Saga Orchestration Example&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;what-is-the-saga-pattern-&quot;&gt;What is the Saga Pattern?&lt;/h3&gt;
&lt;p&gt;The Saga pattern, first introduced by Hector Garcia-Molina and Kenneth Salem in their 1987 research paper, provides a way to manage long-running transactions that span multiple services in a distributed system. Rather than treating the entire business process as a single atomic transaction, a saga breaks it down into a series of smaller, independent transactions that can be coordinated across services.&lt;/p&gt;
&lt;p&gt;Think of booking a vacation package that involves reserving a flight, hotel, and rental car. In a monolithic system, you might wrap all these operations in a single database transaction. With microservices, each service (Flight Service, Hotel Service, Car Rental Service) manages its own data independently. The Saga pattern allows you to coordinate these separate operations while maintaining the ability to handle failures gracefully.&lt;/p&gt;
&lt;p&gt;At its core, a saga consists of a sequence of transactions T₁, T₂, ..., Tₙ, where each transaction has a corresponding compensating transaction C₁, C₂, ..., Cₙ. If any transaction fails, the saga executes the compensating transactions in reverse order to undo the work already completed, ensuring the system remains in a consistent state.&lt;/p&gt;
&lt;h3 id=&quot;problems-it-solves-and-consistency-trade-offs&quot;&gt;Problems it Solves and Consistency Trade-offs&lt;/h3&gt;
&lt;p&gt;The Saga pattern addresses several critical challenges in distributed systems:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Distributed Data Management&lt;/strong&gt;: In microservices architectures, services are designed to be autonomous, each owning its data. Traditional two-phase commit protocols can create tight coupling and availability issues across services. Sagas enable coordination without sacrificing service autonomy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Long-Running Processes&lt;/strong&gt;: Business processes often involve multiple steps that may take minutes, hours, or even days to complete. Holding database locks for such extended periods is impractical and can severely impact system performance. Sagas allow these processes to progress incrementally without blocking resources.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Failure Handling&lt;/strong&gt;: In distributed systems, failures are inevitable. Network partitions, service outages, and timeouts are part of the reality. Sagas provide a structured approach to handle these failures through compensation, rather than simply rolling back and starting over.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Eventual Consistency Trade-off&lt;/strong&gt;: The Saga pattern embraces eventual consistency over immediate consistency. This means that at any given moment, the system might be in an intermediate state, but it will eventually reach a consistent state once the saga completes or compensates. This trade-off is acceptable—and often preferable—in many business scenarios where absolute consistency isn't critical, but availability and resilience are paramount.&lt;/p&gt;
&lt;p&gt;Unlike strict ACID transactions that provide immediate consistency but can be brittle in distributed environments, sagas offer a pragmatic approach that acknowledges the realities of distributed systems. The business logic determines whether eventual consistency is acceptable, and in most real-world scenarios involving multiple services, it is.&lt;/p&gt;
&lt;h3 id=&quot;types-of-saga-patterns&quot;&gt;Types of Saga Patterns&lt;/h3&gt;
&lt;p&gt;The Saga pattern can be implemented using two primary approaches, each with distinct characteristics and use cases.&lt;/p&gt;
&lt;h4 id=&quot;choreography-based-sagas&quot;&gt;Choreography-Based Sagas&lt;/h4&gt;
&lt;p&gt;In choreography-based sagas, services coordinate themselves through events without a central coordinator. Each service listens for events, performs its part of the transaction, and publishes events for other services to consume.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How it works&lt;/strong&gt;: When a user places an order, the Order Service creates the order and publishes an &quot;OrderCreated&quot; event. The Payment Service listens for this event, processes payment, and publishes a &quot;PaymentProcessed&quot; event. The Inventory Service then reserves items and publishes an &quot;ItemsReserved&quot; event, and so on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Decentralized control promotes service autonomy&lt;/li&gt;
&lt;li&gt;No single point of failure from a coordinator perspective&lt;/li&gt;
&lt;li&gt;Natural fit for event-driven architectures&lt;/li&gt;
&lt;li&gt;Services remain loosely coupled&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Complex to track and debug the overall flow&lt;/li&gt;
&lt;li&gt;Difficult to understand the complete business process from code&lt;/li&gt;
&lt;li&gt;Challenging to handle circular dependencies&lt;/li&gt;
&lt;li&gt;Error handling can become distributed and complex&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;When to use&lt;/strong&gt;: Choose choreography when you have a relatively simple saga with clear, linear flow and when you want to maximize service independence. It works well for scenarios where the business process is stable and unlikely to change frequently.&lt;/p&gt;
&lt;p&gt;For a practical example, check out this &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rajkundalia/online-store-saga-choreography&quot;&gt;choreography-based online store implementation&lt;/a&gt; that demonstrates how services coordinate through events to handle order processing.&lt;/p&gt;
&lt;h4 id=&quot;orchestration-based-sagas&quot;&gt;Orchestration-Based Sagas&lt;/h4&gt;
&lt;p&gt;In orchestration-based sagas, a central orchestrator (saga manager) controls the execution flow, explicitly calling services and managing the overall transaction state.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How it works&lt;/strong&gt;: A Saga Orchestrator receives a request to start a saga, then sequentially calls each service based on predefined logic. It maintains the saga's state and handles both success and failure scenarios by invoking appropriate compensating actions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Clear, centralized control flow that's easy to understand and debug&lt;/li&gt;
&lt;li&gt;Explicit state management makes monitoring and troubleshooting straightforward&lt;/li&gt;
&lt;li&gt;Easier to implement complex routing logic and conditional flows&lt;/li&gt;
&lt;li&gt;Better support for timeout handling and retry mechanisms&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Central orchestrator can become a bottleneck or single point of failure&lt;/li&gt;
&lt;li&gt;Orchestrator needs to know about all participating services&lt;/li&gt;
&lt;li&gt;Can lead to more coupled architecture&lt;/li&gt;
&lt;li&gt;Additional infrastructure component to maintain&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;When to use&lt;/strong&gt;: Opt for orchestration when you have complex business flows with conditional logic, when you need clear visibility into the saga state, or when you're dealing with frequently changing business requirements that benefit from centralized control.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rajkundalia/hotel-booking-saga-orchestration&quot;&gt;hotel booking saga orchestration project&lt;/a&gt; provides a comprehensive example of how to implement orchestration-based sagas with proper state management and compensation handling.&lt;/p&gt;
&lt;h3 id=&quot;comparisons-with-other-patterns&quot;&gt;Comparisons with Other Patterns&lt;/h3&gt;
&lt;p&gt;Understanding how the Saga pattern compares to other consistency approaches helps clarify when to use each.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Saga vs. Two-Phase Commit (2PC)&lt;/strong&gt;: Two-Phase Commit provides strong consistency through a prepare-commit protocol but comes with significant drawbacks in distributed systems. It's blocking (services must wait for coordinator decisions), has poor fault tolerance (coordinator failure blocks everything), and doesn't scale well across networks with high latency. Sagas, in contrast, are non-blocking, more fault-tolerant, and better suited for loosely coupled microservices, though they provide only eventual consistency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Saga vs. Event Sourcing&lt;/strong&gt;: While both patterns work well in event-driven systems, they serve different purposes. Event sourcing focuses on storing state changes as events and rebuilding state from these events. Sagas focus on coordinating multi-service transactions. They complement each other well—you can implement sagas in an event-sourced system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Saga vs. Distributed Transactions&lt;/strong&gt;: Traditional distributed transactions aim for immediate consistency across all resources but are complex to implement correctly and perform poorly at scale. Sagas acknowledge that immediate consistency isn't always necessary and provide a simpler, more resilient alternative for most business scenarios.&lt;/p&gt;
&lt;p&gt;The practical advantage of sagas lies in their alignment with microservices principles: they maintain service autonomy, provide better availability characteristics, and offer a more pragmatic approach to consistency in distributed systems.&lt;/p&gt;
&lt;h3 id=&quot;implementation-in-practice-with-spring-boot&quot;&gt;Implementation in Practice with Spring Boot&lt;/h3&gt;
&lt;p&gt;Spring Boot provides excellent support for implementing saga patterns through various approaches. The most common implementation leverages Spring's event handling capabilities and message queues.&lt;/p&gt;
&lt;p&gt;For &lt;strong&gt;choreography-based sagas&lt;/strong&gt;, developers typically use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spring Events for intra-service communication&lt;/li&gt;
&lt;li&gt;Message brokers (RabbitMQ, Apache Kafka) for inter-service events&lt;/li&gt;
&lt;li&gt;Spring Boot Actuator for monitoring saga progress&lt;/li&gt;
&lt;li&gt;Custom event handlers that implement both forward and compensating actions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For &lt;strong&gt;orchestration-based sagas&lt;/strong&gt;, the implementation often includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A dedicated Saga Orchestrator service built with Spring Boot&lt;/li&gt;
&lt;li&gt;Spring State Machine for managing saga states and transitions&lt;/li&gt;
&lt;li&gt;RestTemplate or WebClient for service-to-service communication&lt;/li&gt;
&lt;li&gt;Scheduled tasks for handling timeouts and retries&lt;/li&gt;
&lt;li&gt;Database persistence for saga state management&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A typical Spring Boot saga implementation involves creating:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Saga Events&lt;/strong&gt;: Domain events that represent saga steps&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event Handlers&lt;/strong&gt;: Methods annotated with &lt;code&gt;@EventListener&lt;/code&gt; that process saga events&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compensation Logic&lt;/strong&gt;: Corresponding handlers for rollback operations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State Management&lt;/strong&gt;: Tracking saga progress and current state&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error Handling&lt;/strong&gt;: Timeout management and retry mechanisms&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The referenced sample projects demonstrate these concepts in action, showing how to structure your code, handle failures, and implement proper monitoring for production-ready saga implementations.&lt;/p&gt;
&lt;h3 id=&quot;common-challenges-and-testing-considerations&quot;&gt;Common Challenges and Testing Considerations&lt;/h3&gt;
&lt;p&gt;Implementing sagas in production environments presents several challenges that require careful consideration and planning.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Timeout Management&lt;/strong&gt;: Services in a saga may become temporarily unavailable or respond slowly. Implementing appropriate timeout strategies is crucial—too short, and you'll have false failures; too long, and failed sagas will tie up resources. Design your timeouts based on realistic service response times and implement exponential backoff for retries.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Compensating Transaction Complexity&lt;/strong&gt;: Not all operations can be easily compensated. Sending an email notification, for example, can't be &quot;unsent.&quot; Design your sagas to minimize non-compensatable actions, or implement semantic compensation (like sending an apology email). Sometimes, the compensation is more complex than the original transaction.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Idempotency&lt;/strong&gt;: Saga steps may be retried due to network issues or timeouts, so all operations must be idempotent. This means that executing the same operation multiple times should have the same effect as executing it once. Implement proper idempotency keys and state checking to handle duplicate requests gracefully.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Partial Failure Scenarios&lt;/strong&gt;: The most challenging aspect of sagas is handling scenarios where some steps succeed while others fail, potentially leaving the system in an intermediate state. Design your business processes to be resilient to these intermediate states, and ensure your UI and downstream systems can handle eventual consistency appropriately.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Testing Saga Flows&lt;/strong&gt;: Testing distributed sagas requires sophisticated approaches:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Unit Testing&lt;/strong&gt;: Test individual saga steps and their compensations in isolation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integration Testing&lt;/strong&gt;: Use tools like TestContainers to test saga flows with real message brokers and databases&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chaos Testing&lt;/strong&gt;: Deliberately introduce failures at different points to verify compensation logic&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;End-to-End Testing&lt;/strong&gt;: Test complete saga flows in staging environments that mirror production&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Monitoring and Observability&lt;/strong&gt;: Implement comprehensive logging and monitoring for saga execution. Track saga instances, their current state, execution times, and failure rates. Tools like distributed tracing can help you follow saga execution across multiple services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Design Recommendations&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keep saga steps as small and focused as possible&lt;/li&gt;
&lt;li&gt;Design for failure from the beginning—assume every step can fail&lt;/li&gt;
&lt;li&gt;Implement proper dead letter queues for handling poison messages&lt;/li&gt;
&lt;li&gt;Use correlation IDs to track saga instances across services&lt;/li&gt;
&lt;li&gt;Consider implementing saga timeouts at the business process level&lt;/li&gt;
&lt;li&gt;Plan for manual intervention in complex failure scenarios&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key to successful saga implementation is thorough testing and gradual rollout. Start with simple, linear sagas before moving to complex orchestration scenarios, and always have monitoring and alerting in place to catch issues early.&lt;/p&gt;
&lt;h3 id=&quot;conclusion-4&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;The Saga pattern represents a pragmatic approach to managing distributed transactions in microservices architectures. By embracing eventual consistency and providing structured failure handling through compensation, sagas enable developers to build resilient, scalable systems that can handle the complexities of distributed environments.&lt;/p&gt;
&lt;p&gt;Whether you choose choreography for simple, event-driven flows or orchestration for complex business processes, the key is to understand your specific requirements and constraints. The pattern's flexibility allows for various implementation approaches, from simple Spring Boot applications to sophisticated orchestration engines.&lt;/p&gt;
&lt;p&gt;As distributed systems continue to evolve, the Saga pattern remains a fundamental tool for managing complexity while maintaining the benefits of microservices architecture. Success with sagas comes from careful design, thorough testing, and a clear understanding of the consistency trade-offs that make modern distributed systems both scalable and resilient.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;references-6&quot;&gt;References&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Garcia-Molina, H., &amp;amp; Salem, K. (1987). &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf&quot;&gt;Sagas&lt;/a&gt;. ACM SIGMOD Record.&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/cloud-native-daily/microservices-patterns-part-04-saga-pattern-a7f85d8d4aa3&quot;&gt;Microservices Patterns: Saga Pattern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rajkundalia/online-store-saga-choreography&quot;&gt;Online Store Saga Choreography Example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rajkundalia/hotel-booking-saga-orchestration&quot;&gt;Hotel Booking Saga Orchestration Example&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Backend Dev</category>
<guid isPermaLink="true">https://coderlegion.com/4763/saga-pattern-an-introduction</guid>
<pubDate>Sun, 17 Aug 2025 07:58:36 +0000</pubDate>
</item>
<item>
<title>Supercharging EF Core Specifications with EF.CompileQuery</title>
<link>https://coderlegion.com/4443/supercharging-ef-core-specifications-with-ef-compilequery</link>
<description>&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When you’re using EF Core in production, query compilation can quietly become a performance bottleneck — especially if the same query is executed thousands of times.&lt;/p&gt;
&lt;p&gt;Normally, EF Core parses your LINQ, generates SQL, and prepares the execution plan every time.&lt;br&gt;
With EF.CompileQuery, EF Core does that work once, caching the query plan for all future executions.&lt;/p&gt;
&lt;p&gt;The catch?&lt;/p&gt;
&lt;p&gt;If you’re using the Specification pattern for clean, reusable queries, your repository method probably applies a spec dynamically to DbSet — which makes EF.CompileQuery less obvious to use.&lt;/p&gt;
&lt;p&gt;In this article, we’ll solve that and show you how to bake compiled queries into your specification-driven repository.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Quick Recap: Specification Pattern&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The Specification pattern encapsulates query logic into reusable, composable units.&lt;/p&gt;
&lt;p&gt;Example spec for active customers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class ActiveCustomersSpec : Specification&amp;lt;Customer&amp;gt;
{
    public ActiveCustomersSpec()
    {
        Query.Where(c =&amp;gt; c.IsActive)
             .OrderBy(c =&amp;gt; c.Name)
             .Include(c =&amp;gt; c.Orders);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your repository then applies the spec:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public async Task&amp;lt;IReadOnlyList&amp;lt;T&amp;gt;&amp;gt; ListAsync(ISpecification&amp;lt;T&amp;gt; spec)
{
    var evaluator = new SpecificationEvaluator();
    var query = evaluator.GetQuery(_db.Set&amp;lt;T&amp;gt;().AsQueryable(), spec);
    return await query.ToListAsync();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;The Problem&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;EF.CompileQuery needs a strongly-typed lambda starting from your DbContext:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private static readonly Func&amp;lt;AppDbContext, bool, IEnumerable&amp;lt;Customer&amp;gt;&amp;gt; _activeCustomers =
    EF.CompileQuery((AppDbContext db, bool isActive) =&amp;gt;
        db.Customers.Where(c =&amp;gt; c.IsActive == isActive));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But with the Specification pattern, you usually pass in a spec, not a lambda on DbContext.&lt;br&gt;
You don’t want every caller to remember how to invoke the compiled query.&lt;br&gt;
We need a way to integrate compiled queries into the repository itself.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The Solution: Encapsulate Compiled Queries in the Repository&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;4.1 Create the compiled query&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static class CompiledQueries
{
    public static readonly Func&amp;lt;AppDbContext, bool, IEnumerable&amp;lt;Customer&amp;gt;&amp;gt; ActiveCustomers =
        EF.CompileQuery((AppDbContext db, bool isActive) =&amp;gt;
            db.Customers
              .Where(c =&amp;gt; c.IsActive == isActive)
              .OrderBy(c =&amp;gt; c.Name)
              .Include(c =&amp;gt; c.Orders));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4.2 Update the repository&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class EfRepository&amp;lt;T&amp;gt; where T : class
{
    private readonly AppDbContext _db;
    public EfRepository(AppDbContext db) =&amp;gt; _db = db;

    public async Task&amp;lt;IReadOnlyList&amp;lt;T&amp;gt;&amp;gt; ListAsync(ISpecification&amp;lt;T&amp;gt; spec)
    {
        // Detect known spec → use compiled query
        if (typeof(T) == typeof(Customer) &amp;amp;&amp;amp; spec is ActiveCustomersSpec)
        {
            var result = CompiledQueries.ActiveCustomers(_db, true).ToList();
            return (IReadOnlyList&amp;lt;T&amp;gt;)result;
        }

        // Fallback: normal spec evaluation
        var evaluator = new SpecificationEvaluator();
        var query = evaluator.GetQuery(_db.Set&amp;lt;T&amp;gt;().AsQueryable(), spec);
        return await query.ToListAsync();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4.3 Usage stays clean&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var repo = new EfRepository&amp;lt;Customer&amp;gt;(db);
var customers = await repo.ListAsync(new ActiveCustomersSpec());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The caller doesn’t know (or care) if the query was compiled&lt;br&gt;
but hot-path specs now run ~3x faster.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=16149713430744441257&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ready-to-Use Libraries&lt;br&gt;
Instead of writing your own Specification pattern, you can use:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ardalis/Specification&quot;&gt;Ardalis.Specification&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Well-maintained, feature-rich.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Supports includes, projections, ordering, pagination.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/miholler/NSpecifications&quot;&gt;NSpecifications&lt;/a&gt; &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;DDD-focused, composable specifications.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Great for domain-driven designs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  Both integrate nicely with EF Core and can work with this&lt;br&gt;
   compiled-query approach.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Best Practices&lt;br&gt;
Use compiled queries for hot paths — they have a cost to set up but shine when reused often.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Avoid over-compiling — not every query benefits from precompilation.&lt;/p&gt;
&lt;p&gt;Keep specs small and focused — huge includes and joins can still dominate runtime.&lt;/p&gt;
&lt;p&gt;Combine with async wisely — EF.CompileQuery is sync; use EF.CompileAsyncQuery for async enumeration.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You don’t have to choose between clean architecture (Specification pattern) and speed.&lt;br&gt;
By encapsulating EF.CompileQuery inside your repository for frequently-used specs,&lt;br&gt;
you can keep your calling code clean while still getting huge performance wins.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/EfCompileSpecDemo&quot;&gt;Source Code&lt;/a&gt; : &lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/EfCompileSpecDemo&quot;&gt;https://github.com/stevsharp/EfCompileSpecDemo&lt;/a&gt;&lt;/p&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/4443/supercharging-ef-core-specifications-with-ef-compilequery</guid>
<pubDate>Tue, 05 Aug 2025 07:56:23 +0000</pubDate>
</item>
<item>
<title>Understanding MediatR Assembly Registration in .NET</title>
<link>https://coderlegion.com/3655/understanding-mediatr-assembly-registration-in-net</link>
<description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;br&gt;
When building clean, modular applications in .NET using the MediatR library, developers often encounter this configuration line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;builder.Services.AddMediatR(typeof(Application.Assembly.AssemblyReference).Assembly);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For newcomers to MediatR, this syntax can appear cryptic. However, it serves a critical role in enabling the framework to function correctly. This article provides a comprehensive explanation of assembly registration in MediatR, covering its purpose, implementation, and best practices.&lt;br&gt;
&lt;strong&gt;What Is MediatR?&lt;/strong&gt;&lt;br&gt;
MediatR is a lightweight, in-process messaging library for .NET that facilitates decoupling between request senders (such as controllers) and request handlers (such as services or command processors). It implements several important patterns:&lt;/p&gt;
&lt;p&gt; **- Command Query Responsibility Segregation (CQRS)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mediator pattern&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pipeline behaviors for cross-cutting concerns&lt;strong&gt;&lt;br&gt;
The library works by defining requests (commands or queries) and their corresponding handlers, then routing requests through a mediator to the appropriate handler.&lt;br&gt;
&lt;/strong&gt;Understanding Assembly Registration&lt;br&gt;
The Core Concept**&lt;br&gt;
MediatR requires explicit assembly registration to discover and register your handlers in the dependency injection container. The framework cannot automatically scan every assembly in your application—you must specify which assemblies contain your MediatR handlers.&lt;/p&gt;
&lt;p&gt; builder.Services.AddMediatR(typeof(SomeTypeInTargetAssembly).Assembly);&lt;br&gt;
&lt;strong&gt;Assembly Location:&lt;/strong&gt; typeof(SomeTypeInTargetAssembly).Assembly provides MediatR with access to the target assembly&lt;br&gt;
&lt;strong&gt;Handler Discovery:&lt;/strong&gt; MediatR uses reflection to scan the assembly for types implementing MediatR interfaces (IRequestHandler&amp;lt;,&amp;gt;, INotificationHandler&amp;lt;&amp;gt;, etc.)&lt;br&gt;
&lt;strong&gt;Service Registration&lt;/strong&gt;: Discovered handlers are automatically registered with the dependency injection container&lt;br&gt;
&lt;strong&gt;Why Assembly Registration Is Required&lt;/strong&gt;&lt;br&gt;
MediatR operates through dependency injection and reflection, but it cannot magically locate your handlers across all loaded assemblies. Without proper assembly registration, you will encounter this runtime exception:&lt;/p&gt;
&lt;p&gt; System.InvalidOperationException: No service for type 'IRequestHandler&amp;lt;YourCommand, YourResponse&amp;gt;' has been registered.&lt;br&gt;
This error occurs because the dependency injection container cannot resolve the required handler service.&lt;br&gt;
&lt;strong&gt;Implementation Best Practices&lt;br&gt;
Using a Marker Class&lt;/strong&gt;&lt;br&gt;
The recommended approach is to create a dedicated marker class within your handler assembly:&lt;br&gt;
&lt;strong&gt;&lt;em&gt;Application/Assembly/AssemblyReference.cs&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt; namespace Application.Assembly&lt;br&gt;
 {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; public class AssemblyReference 
 {
     // This class serves as an assembly marker for MediatR registration
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; }&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Program.cs (or Startup.cs)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;builder.Services.AddMediatR(typeof(Application.Assembly.AssemblyReference).Assembly);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach offers several advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Maintainability: Creates a stable reference point that won't change&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clarity: Makes the registration intent explicit&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Decoupling: Avoids tight coupling to specific handler classes&lt;br&gt;
Here's a typical Clean Architecture project structure:&lt;/p&gt;
&lt;p&gt; Solution/&lt;br&gt;
 ├── Api/                           # Presentation layer&lt;br&gt;
 │   └── Program.cs                 # Application entry point&lt;br&gt;
 ├── Application/                   # Application layer&lt;br&gt;
 │   ├── Features/&lt;br&gt;
 │   │   ├── Authentication/&lt;br&gt;
 │   │   │   ├── LoginCommand.cs&lt;br&gt;
 │   │   │   └── LoginCommandHandler.cs&lt;br&gt;
 │   │   └── Users/&lt;br&gt;
 │   │       ├── GetUserQuery.cs&lt;br&gt;
 │   │       └── GetUserQueryHandler.cs&lt;br&gt;
 │   └── Assembly/&lt;br&gt;
 │       └── AssemblyReference.cs   # Marker class&lt;br&gt;
 ├── Domain/                        # Domain layer&lt;br&gt;
 └── Infrastructure/                # Infrastructure layer&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;An Example&lt;/strong&gt;&lt;br&gt;
Defining a Command and Handler&lt;br&gt;
&lt;strong&gt;LoginCommand.cs&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    using MediatR;

namespace Application.Features.Authentication;

public class LoginCommand : IRequest&amp;lt;LoginResponse&amp;gt;
{
    public string Username { get; set; } = string.Empty;
    public string Password { get; set; } = string.Empty;
}

public class LoginResponse
{
    public bool IsSuccess { get; set; }
    public string Token { get; set; } = string.Empty;
    public string Message { get; set; } = string.Empty;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Using the Handler in a Controller&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ApiController]
[Route(&quot;api/[controller]&quot;)]
public class AuthController : ControllerBase
{
    private readonly IMediator _mediator;

    public AuthController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost(&quot;login&quot;)]
    public async Task&amp;lt;ActionResult&amp;lt;LoginResponse&amp;gt;&amp;gt; Login([FromBody] LoginCommand command)
    {
        var response = await _mediator.Send(command);
        return Ok(response);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
Proper assembly registration is fundamental to MediatR's operation in .NET applications. By understanding how AddMediatR(typeof(...).Assembly) works and following established best practices, you can build maintainable, decoupled applications that leverage MediatR's powerful messaging capabilities.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key Takeaways&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Always register the assembly containing your MediatR handlers&lt;br&gt;
Use marker classes for clean, maintainable assembly references&lt;br&gt;
Ensure proper project references between your API and Application layers&lt;br&gt;
Consider pipeline behaviors for implementing cross-cutting concerns&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next Steps&lt;/strong&gt;&lt;br&gt;
To further enhance your MediatR implementation:&lt;/p&gt;
&lt;p&gt;Implement validation using FluentValidation with MediatR pipeline behaviors&lt;br&gt;
Add logging and monitoring through custom pipeline behaviors&lt;br&gt;
Explore notifications using INotification and INotificationHandler&amp;lt;&amp;gt;&lt;br&gt;
Consider performance optimization with caching behaviors&lt;br&gt;
Implement error handling strategies using pipeline behaviors&lt;/p&gt;
</description>
<category>ASP.NET</category>
<guid isPermaLink="true">https://coderlegion.com/3655/understanding-mediatr-assembly-registration-in-net</guid>
<pubDate>Fri, 04 Jul 2025 09:33:30 +0000</pubDate>
</item>
<item>
<title>Is the Repository Pattern Dead?</title>
<link>https://coderlegion.com/3204/is-the-repository-pattern-dead</link>
<description>&lt;p&gt;Is the Repository Pattern Dead?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The repository pattern has been a cornerstone of enterprise .NET architecture for years. It promised clean separation between business logic and data access, testability, and maintainability. But with modern ORMs like Entity Framework Core, and new design trends like CQRS, many developers are asking:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is the repository pattern still relevant, or is it finally dead?&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What Is the Repository Pattern?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The Repository pattern is a well-known design pattern that aims to separate the logic that retrieves data from the business logic that acts on the data.&lt;/p&gt;
&lt;p&gt;&quot;A Repository mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.&quot;— Martin Fowler, &lt;a rel=&quot;nofollow&quot; href=&quot;https://martinfowler.com/books/eaa.html&quot;&gt;Patterns of Enterprise Application Architecture&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In practice, this means using a class that acts like an in-memory collection of domain objects — hiding whether the data comes from SQL, a web service, or somewhere else:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public interface ICustomerRepository
{
    Task&amp;lt;Customer&amp;gt; GetByIdAsync(Guid id);
    Task AddAsync(Customer customer);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Originally, this was valuable when ORMs weren’t mature and data access logic was verbose and database-specific.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Why People Say It’s Dead&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;✅ EF Core Is Already a Repository&lt;/p&gt;
&lt;p&gt;DbContext and DbSet provide the core functionality repositories offer: querying, adding, removing, and tracking entities.&lt;/p&gt;
&lt;p&gt;❌ Boilerplate Hell&lt;/p&gt;
&lt;p&gt;Most custom repositories end up duplicating what EF already provides:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public Task&amp;lt;Customer&amp;gt; GetByIdAsync(Guid id) =&amp;gt; _context.Customers.FindAsync(id);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;...which is not value-added abstraction.&lt;/p&gt;
&lt;p&gt; Over-Abstraction Hurts&lt;/p&gt;
&lt;p&gt;You lose direct access to EF Core's powerful features like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;.Include() for eager loading&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;.AsNoTracking() for read-only queries&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Raw SQL with .FromSqlRaw()&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, patterns like Ardalis.Specification allow you to retain advanced EF Core features such as eager loading via .Include(), while still keeping your repository clean and decoupled. Specifications can express query logic clearly, including filters, includes, and sorting.&lt;/p&gt;
&lt;p&gt; &lt;strong&gt;“Switching databases” is not a strong reason anymore&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A common argument for using repositories is to make it easier to swap out databases (e.g., from SQL Server to PostgreSQL). But with Entity Framework Core, your application already depends on an abstraction — EF itself. Changing providers typically involves minimal changes to the DbContext configuration and nothing in your business logic. Adding another repository layer just to enable “swap-ability” is usually over-engineering.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When the Repository Pattern Still Makes Sense&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Despite the criticism, there are valid scenarios where repositories shine:&lt;/p&gt;
&lt;p&gt;Domain-Driven Design (&lt;strong&gt;DDD&lt;/strong&gt;) with rich aggregates and business rules&lt;/p&gt;
&lt;p&gt;Abstracting external data sources (&lt;strong&gt;SQL + API + NoSQL&lt;/strong&gt;)&lt;/p&gt;
&lt;p&gt;Clear boundaries in microservices&lt;/p&gt;
&lt;p&gt; NuGet/Repo Suggestions:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ardalis/Specification&quot;&gt;Ardalis.Specification&lt;/a&gt;: A clean implementation of the Specification Pattern that works great with EF Core.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ardalis/CleanArchitecture&quot;&gt;Ardalis.CleanArchitecture&lt;/a&gt;: A full solution template with proper use of repositories and separation of concerns.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/reactiveui/refit&quot;&gt;Refit&lt;/a&gt;: For when your &quot;repository&quot; is actually calling an external API.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://entityframework-extensions.net/bulk-extensions&quot;&gt;EFCore.BulkExtensions&lt;/a&gt;: If you do stay close to DbContext, this helps with batch operations and performance.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Modern Alternatives&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Rather than force-fitting repositories, modern .NET developers are adopting:&lt;/p&gt;
&lt;p&gt;CQRS + MediatR: Commands and queries are separated cleanly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MediatR&lt;/strong&gt;: Lightweight in-process messaging.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Query Object Pattern:&lt;/strong&gt; Encapsulates complex queries into reusable classes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Minimal APIs&lt;/strong&gt; + DbContext injection (in .NET 6+): Simple, fast, and testable.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Pragmatic Verdict&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So, is the repository pattern dead? Not quite.&lt;/p&gt;
&lt;p&gt;It's just no longer mandatory.&lt;/p&gt;
&lt;p&gt;Use it when it adds value — such as enforcing boundaries, abstracting external sources, or aligning with your team's architecture. But don’t blindly wrap EF Core unless there’s a real reason.&lt;/p&gt;
&lt;p&gt;Conclusion&lt;/p&gt;
&lt;p&gt;The repository pattern isn’t dead — it’s evolved. In modern .NET, we have better tools, smarter defaults, and community-tested patterns like Ardalis.Specification that give you structure only when you need it.&lt;/p&gt;
&lt;p&gt;Choose the right tool for your context. That’s real architecture.&lt;/p&gt;
&lt;p&gt;References&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://martinfowler.com/eaaCatalog/repository.html&quot;&gt;Martin Fowler – Repository Pattern&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ardalis/Specification&quot;&gt;Steve Smith – Ardalis.Specification GitHub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ardalis/Specification&quot;&gt;Steve Smith – Clean Architecture Template&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/jbogard/MediatR&quot;&gt;Jeremy Bogard – MediatR GitHub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://entityframework-extensions.net/bulk-extensions&quot;&gt;EFCore Bulk Extensions – GitHub Repository&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/reactiveui/refit&quot;&gt;Refit – GitHub Repository&lt;/a&gt;&lt;/p&gt;
</description>
<category>Backend Dev</category>
<guid isPermaLink="true">https://coderlegion.com/3204/is-the-repository-pattern-dead</guid>
<pubDate>Wed, 04 Jun 2025 07:25:26 +0000</pubDate>
</item>
<item>
<title>Clean React code pattern with the useImperativeHandle hook</title>
<link>https://coderlegion.com/3105/clean-react-code-pattern-with-the-useimperativehandle-hook</link>
<description>&lt;p&gt;In the quest for a cleaner and more concise codebase that adheres to the DRY principle, one React hook that stands out is &lt;code&gt;useImperativeHandle&lt;/code&gt; hook.&lt;/p&gt;
&lt;p&gt;React’s declarative nature encourages developers to build components that rely on props and state to manage behavior and rendering. However, in some cases imperative programming can simplify complex state management, especially when a child component interacts with multiple parents.&lt;/p&gt;
&lt;h4 id=&quot;the-useimperativehandle-hook-pattern&quot;&gt;The &lt;code&gt;useImperativeHandle()&lt;/code&gt; hook Pattern&lt;/h4&gt;
&lt;p&gt;In React, as the app size grows, it also starts to grow in complexity and it becomes expedient for devs to utilize advanced patterns to reach their goals, as simpler patterns can no longer adequately provide the clean solutions we require. &lt;/p&gt;
&lt;p&gt;Managing state in child components typically involves lifting state up to parent components. However, when a child is shared among multiple parents, duplicating state can lead to repetitive code and potential inconsistencies. To address this, you can centralize state management within the child itself, using the useImperativeHandle hook to expose imperative methods for modifying its state. This approach not only prevents redundancy but also promotes a more maintainable architecture.&lt;/p&gt;
&lt;h5 id=&quot;the-pattern-in-action&quot;&gt;The Pattern in Action&lt;/h5&gt;
&lt;p&gt;Below is an example that demonstrates how to leverage &lt;code&gt;useImperativeHandle&lt;/code&gt; for better state management. In this pattern, a child component exposes methods like focus and clear through its &lt;code&gt;ref&lt;/code&gt;, allowing parent components to control its behavior without relying solely on props or state hooks.&lt;/p&gt;
&lt;h6 id=&quot;example&quot;&gt;Example&lt;/h6&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import React, { useRef, forwardRef, useImperativeHandle } from 'react';

const ChildInput = forwardRef((props, ref) =&amp;gt; {
  const inputRef = useRef();

  useImperativeHandle(ref, () =&amp;gt; ({
    focus: () =&amp;gt; inputRef.current?.focus(),
    clear: () =&amp;gt; {
      inputRef.current.value = '';
    },
  }));

  return &amp;lt;input ref={inputRef} {...props} /&amp;gt;;
});

export default function ParentComponent() {
  const inputRef = useRef();

  return (
    &amp;lt;div className=&quot;p-4 space-y-4&quot;&amp;gt;
      &amp;lt;ChildInput ref={inputRef} placeholder=&quot;Type something...&quot; /&amp;gt;
      &amp;lt;div className=&quot;space-x-2&quot;&amp;gt;
        &amp;lt;button
          onClick={() =&amp;gt; inputRef.current.focus()}
          className=&quot;px-3 py-1 bg-blue-500 text-white rounded&quot;
        &amp;gt;
          Focus Input
        &amp;lt;/button&amp;gt;
        &amp;lt;button
          onClick={() =&amp;gt; inputRef.current.clear()}
          className=&quot;px-3 py-1 bg-red-500 text-white rounded&quot;
        &amp;gt;
          Clear Input
        &amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;React 19 further refines this pattern by eliminating the need for &lt;code&gt;forwardRef&lt;/code&gt;. The example below illustrates the same behavior with a cleaner syntax.&lt;/p&gt;
&lt;h6 id=&quot;react-19-example&quot;&gt;React 19 Example&lt;/h6&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import React, { useImperativeHandle, useRef } from 'react';

function ChildInput(props, ref) {
  const inputRef = useRef();

  // Expose imperative methods to the parent via the ref.
  useImperativeHandle(ref, () =&amp;gt; ({
    focus: () =&amp;gt; inputRef.current?.focus(),
    clear: () =&amp;gt; {
      if (inputRef.current) {
        inputRef.current.value = '';
      }
    },
  }));

  return &amp;lt;input ref={inputRef} {...props} /&amp;gt;;
}

function ParentComponent() {
  const inputRef = useRef();

  return (
    &amp;lt;div className=&quot;p-4 space-y-4&quot;&amp;gt;
      &amp;lt;ChildInput ref={inputRef} placeholder=&quot;Type something...&quot; /&amp;gt;
      &amp;lt;div className=&quot;space-x-2&quot;&amp;gt;
        &amp;lt;button
          onClick={() =&amp;gt; inputRef.current.focus()}
          className=&quot;px-3 py-1 bg-blue-500 text-white rounded&quot;
        &amp;gt;
          Focus Input
        &amp;lt;/button&amp;gt;
        &amp;lt;button
          onClick={() =&amp;gt; inputRef.current.clear()}
          className=&quot;px-3 py-1 bg-red-500 text-white rounded&quot;
        &amp;gt;
          Clear Input
        &amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default ParentComponent;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;conclusion-5&quot;&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;This approach demonstrates how &lt;code&gt;useImperativeHandle&lt;/code&gt; can simplify state management in complex React applications. While the example above provides a practical solution, I would encourage you to learn more about &lt;code&gt;useImperativeHandle&lt;/code&gt;, &lt;code&gt;Declarative&lt;/code&gt; and &lt;code&gt;Imperative&lt;/code&gt; programming, &lt;/p&gt;
&lt;p&gt;I have provided starter prompts you can feed into an LLM to get detailed explanations for each of the concepts, and to fully appreciate the trade-offs and best practices behind this each of them.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Explain declarative and imperative programming?
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Explain declarative and imperative programming in the context of React
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Explain the useImperativeHandle hook, and give examples of when to utilize this hook versus using just props and state?
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Web Dev</category>
<guid isPermaLink="true">https://coderlegion.com/3105/clean-react-code-pattern-with-the-useimperativehandle-hook</guid>
<pubDate>Tue, 20 May 2025 07:26:26 +0000</pubDate>
</item>
<item>
<title>The Proxy Pattern: A Software Developer’s Essential Tool</title>
<link>https://coderlegion.com/2811/the-proxy-pattern-a-software-developers-essential-tool</link>
<description>&lt;p&gt; &lt;strong&gt;The Proxy Pattern&lt;/strong&gt; is a structural design pattern that provides a surrogate or placeholder for another object to control access to it. It's like having a representative that acts on behalf of another object.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Proxy Pattern allows you to:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;• Provide a substitute or placeholder for another object&lt;/p&gt;
&lt;p&gt;• Control access to the original object&lt;/p&gt;
&lt;p&gt;• Perform operations before or after requests reach the original object&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;When to Use the Proxy Pattern&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Lazy initialization is needed: When creating objects is expensive and you want to delay creation until absolutely necessary&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Access control is required: When you need to restrict access to certain operations or resources based on permissions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Resource management is important: When working with limited resources that need controlled allocation&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Added functionality is needed before/after access: When you want to perform operations before or after accessing the real object (like logging or caching)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The key indicator is when you need to control access to an object without changing its interface.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Structure of the Proxy Pattern&lt;br&gt;
The pattern consists of these key components:&lt;br&gt;
• Subject: An interface that defines the common operations for both the RealSubject and Proxy&lt;/p&gt;
&lt;p&gt;• RealSubject: The actual object that the proxy represents&lt;/p&gt;
&lt;p&gt;• Proxy: Maintains a reference to the RealSubject and controls access to it&lt;/p&gt;
&lt;p&gt;• Client: Interacts with the Subject interface&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b1ixjklactdw3ezz3fwi.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Types of Proxies&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Virtual Proxy&lt;/strong&gt;&lt;br&gt;
• Creates expensive objects on demand (lazy initialization)&lt;br&gt;
• Example: Loading large images in a document only when they need to be displayed&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Protection Proxy&lt;/strong&gt;&lt;br&gt;
• Controls access permissions to objects&lt;br&gt;
• Example: Access control systems that verify user permissions before allowing resource access&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Remote Proxy&lt;/strong&gt;&lt;br&gt;
• Represents objects in different address spaces&lt;br&gt;
• Example: A local object representing a remote service&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Smart Reference&lt;/strong&gt;&lt;br&gt;
• Performs additional actions when objects are accessed&lt;br&gt;
• Example: Reference counting, locking mechanisms&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Example: Virtual Proxy for Image Loading in C#&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Problem: Loading high-resolution images is expensive and may not be immediately necessary.&lt;/p&gt;
&lt;p&gt;Solution: Use a Virtual Proxy to load the actual image only when it needs to be displayed.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Subject Interface
public interface IImage
{
    void Display();
}

// RealSubject
public class HighResolutionImage : IImage
{
    private string _filename;
    
    public HighResolutionImage(string filename)
    {
        _filename = filename;
        LoadImageFromDisk();
    }
    
    private void LoadImageFromDisk()
    {
        Console.WriteLine($&quot;Loading high-resolution image: {_filename}&quot;);
        // Expensive operation: loading image data
        // In a real implementation, this might use System.Drawing or ImageSharp
        // to load and process the actual image file
    }
    
    public void Display()
    {
        Console.WriteLine($&quot;Displaying high-resolution image: {_filename}&quot;);
    }
}

// Proxy
public class ImageProxy : IImage
{
    private string _filename;
    private HighResolutionImage _realImage;
    
    public ImageProxy(string filename)
    {
        _filename = filename;
    }
    
    public void Display()
    {
        // Create the real image only when Display is called
        if (_realImage == null)
        {
            _realImage = new HighResolutionImage(_filename);
        }
        _realImage.Display();
    }
}

// Client
public class Program
{
    public static void Main(string[] args)
    {
        // Using proxies for multiple images
        IImage image1 = new ImageProxy(&quot;photo1.jpg&quot;);
        IImage image2 = new ImageProxy(&quot;photo2.jpg&quot;);
        
        // The high-resolution image is loaded only when displayed
        Console.WriteLine(&quot;Application started...&quot;);
        Console.WriteLine(&quot;Displaying first image:&quot;);
        image1.Display();  // Only now is the first image loaded
        
        Console.WriteLine(&quot;Second image is not displayed, so it's never loaded&quot;);
        
        Console.ReadKey(); // Wait for user input before closing
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Benefits of the Proxy Pattern&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Improved performance: Defers costly object creation until necessary&lt;/li&gt;
&lt;li&gt;Enhanced security: Controls access to sensitive objects&lt;/li&gt;
&lt;li&gt;Reduced complexity: Hides implementation details from clients&lt;/li&gt;
&lt;li&gt;Increased flexibility: Enables features like caching, logging, and access control without changing the original object&lt;/li&gt;
&lt;li&gt;Location transparency: Provides unified access to distributed objects&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Limitations of the Proxy Pattern&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Increased response time: Adds a level of indirection&lt;/li&gt;
&lt;li&gt;Implementation complexity: Some proxy types can be complex to implement&lt;/li&gt;
&lt;li&gt;Potential for overuse: Not every object needs a proxy&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Real-World Applications&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Web browsers: Caching proxy for web pages&lt;/li&gt;
&lt;li&gt;ORM frameworks: Proxies for lazy loading of database entities&lt;/li&gt;
&lt;li&gt;Credit cards: Act as proxies for bank accounts&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;br&gt;
The Proxy Pattern provides a surrogate for another object to control access to it. It's particularly useful for lazy loading, access control, and adding behavior when accessing objects. The pattern maintains the same interface as the original object while providing additional functionality.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Reference : Book : ”Design Patterns: Elements of Reusable Object-Oriented Software” by Gamma, Helm, Johnson, and Vlissides. also call Gun of Fure&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Setup Guide</category>
<guid isPermaLink="true">https://coderlegion.com/2811/the-proxy-pattern-a-software-developers-essential-tool</guid>
<pubDate>Fri, 25 Apr 2025 15:31:01 +0000</pubDate>
</item>
<item>
<title>Utility class of POM: Screenplay Pattern</title>
<link>https://coderlegion.com/1854/utility-class-of-pom-screenplay-pattern</link>
<description>&lt;h2 id=&quot;understanding-the-screenplay-pattern-in-test-automation&quot;&gt;Understanding the Screenplay Pattern in Test Automation&lt;/h2&gt;
&lt;p&gt;Test automation often starts simple a few UI actions, maybe a Page Object or two. But as the system grows, maintaining and scaling those tests becomes a real challenge.&lt;/p&gt;
&lt;p&gt;Enter the &lt;strong&gt;Screenplay Pattern&lt;/strong&gt;: a powerful, actor-based model for writing clean, reusable, and maintainable tests.&lt;/p&gt;
&lt;h3 id=&quot;what-is-the-screenplay-pattern-&quot;&gt;What is the Screenplay Pattern?&lt;/h3&gt;
&lt;p&gt;The Screenplay Pattern is a test automation design pattern that models test scripts like a movie script involving &lt;strong&gt;Actors&lt;/strong&gt;, &lt;strong&gt;Tasks&lt;/strong&gt;, &lt;strong&gt;Abilities&lt;/strong&gt;, &lt;strong&gt;Interactions&lt;/strong&gt;, and &lt;strong&gt;Questions&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It encourages us to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Think in terms of &lt;em&gt;what&lt;/em&gt; the actor does, not &lt;em&gt;how&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Build &lt;strong&gt;modular&lt;/strong&gt;, &lt;strong&gt;readable&lt;/strong&gt;, and &lt;strong&gt;reusable&lt;/strong&gt; test logic&lt;/li&gt;
&lt;li&gt;Focus on &lt;strong&gt;behavior&lt;/strong&gt; rather than implementation&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;key-building-blocks&quot;&gt;Key Building Blocks&lt;/h3&gt;
&lt;h4 id=&quot;actor&quot;&gt;Actor&lt;/h4&gt;
&lt;p&gt;Represents the user or persona interacting with the system.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;Actor yusuf = Actor.named(&quot;Yusuf&quot;).whoCan(BrowseTheWeb.with(driver));
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;task&quot;&gt;Task&lt;/h4&gt;
&lt;p&gt;A meaningful action or series of steps the actor performs.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Login implements Task {
    public static Login withCredentials(String username, String password) {
        return instrumented(Login.class, username, password);
    }

    public &amp;lt;T extends Actor&amp;gt; void performAs(T actor) {
        actor.attemptsTo(
            Enter.theValue(username).into(USERNAME_FIELD),
            Enter.theValue(password).into(PASSWORD_FIELD),
            Click.on(LOGIN_BUTTON)
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;yusuf.attemptsTo(Login.withCredentials(&quot;admin&quot;, &quot;1234&quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;question&quot;&gt;Question&lt;/h4&gt;
&lt;p&gt;Used to extract information from the system, like validations or assertions.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class WelcomeMessage implements Question&amp;lt;String&amp;gt; {
    public String answeredBy(Actor actor) {
        return Text.of(WELCOME_MESSAGE_LOCATOR).viewedBy(actor).asString();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;String message = yusuf.asksFor(new WelcomeMessage());
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;ability&quot;&gt;Ability&lt;/h4&gt;
&lt;p&gt;Defines what the actor can do. For example, browse the web or call APIs.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;BrowseTheWeb.with(driver); // Enables web interactions
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;target-screenplay-s-way-of-handling-locators&quot;&gt;Target: Screenplay’s Way of Handling Locators&lt;/h4&gt;
&lt;p&gt;If you stuck with POM and cant quit completely(like me) you can still integrate it to your POM too. Instead of using Page Object Model and WebElements, the Screenplay Pattern uses &lt;strong&gt;Target&lt;/strong&gt; objects to define locators.&lt;/p&gt;
&lt;p&gt;In traditional POM:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@AndroidFindBy(accessibility = &quot;Login Screen&quot;)
public WebElement loginScreen;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In Screenplay:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public final class LandingPage {

    private LandingPage() {} // Prevent instantiation

    public static final Target LOGIN_SCREEN = Target.the(&quot;Login screen&quot;)
        .locatedByAccessibilityId(&quot;Login Screen&quot;);

    public static final Target ECHO_BOX_SCREEN = Target.the(&quot;Echo Box screen&quot;)
        .locatedByAccessibilityId(&quot;Echo Box&quot;);

    public static final Target LIST_DEMO_SCREEN = Target.the(&quot;List Demo screen&quot;)
        .locatedByAccessibilityId(&quot;List Demo&quot;);

    public static final Target LIST_DATE_SCREEN = Target.the(&quot;Date Picker screen&quot;)
        .locatedByAccessibilityId(&quot;Picker Demo&quot;);

    public static final Target WEBVIEW_SCREEN = Target.the(&quot;WebView screen&quot;)
        .locatedByAccessibilityId(&quot;Webview Demo&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And in your tasks:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;actor.attemptsTo(
    Click.on(LandingPage.LOGIN_SCREEN)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No need for PageFactory or WebElement instantiation&lt;/li&gt;
&lt;li&gt;Targets are &lt;code&gt;static&lt;/code&gt;, so they can be reused anywhere&lt;/li&gt;
&lt;li&gt;Fully integrates with Screenplay’s task-based structure&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you're using Appium with Serenity, you can also use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;Target LOGIN_SCREEN = Target.the(&quot;Login screen&quot;)
    .located(MobileBy.AccessibilityId(&quot;Login Screen&quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;benefits-of-screenplay-pattern&quot;&gt;Benefits of Screenplay Pattern&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Improved &lt;strong&gt;readability&lt;/strong&gt; (tests look like real scenarios)&lt;/li&gt;
&lt;li&gt;High &lt;strong&gt;reusability&lt;/strong&gt; (Tasks &amp;amp; Questions are shared across tests)&lt;/li&gt;
&lt;li&gt;Better &lt;strong&gt;scalability&lt;/strong&gt; for growing test suites&lt;/li&gt;
&lt;li&gt;Strong adherence to &lt;strong&gt;SOLID principles&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you notice classes not using extends keyword for inheritance.(Congrats you are detail-oriented person) Because Screenplay prefers &lt;strong&gt;Composition&lt;/strong&gt; over &lt;strong&gt;Inheritance&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;inheritance-in-page-object-model-vs-implementation-in-screenplay-pattern&quot;&gt;Inheritance in Page Object Model vs. Implementation in Screenplay Pattern&lt;/h3&gt;
&lt;p&gt;One of the key architectural differences between the Page Object Model (POM) and the Screenplay Pattern lies in their use of object-oriented principles—specifically, inheritance versus implementation.&lt;/p&gt;
&lt;h4 id=&quot;page-object-model-inheritance-based-structure&quot;&gt;Page Object Model: Inheritance-Based Structure&lt;/h4&gt;
&lt;p&gt;In POM, it is common to use &lt;strong&gt;inheritance&lt;/strong&gt; via the &lt;code&gt;extends&lt;/code&gt; keyword. Typically, a base class (e.g., &lt;code&gt;BasePage&lt;/code&gt;) is created to house shared functionalities such as clicking elements, sending input, or waiting for elements. All page classes then extend this base class to inherit these methods. This approach promotes code reuse but can lead to tightly coupled and less flexible designs.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class BasePage {
    public void click(WebElement element) {
        element.click();
    }
}

public class LoginPage extends BasePage {
    WebElement loginButton;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;screenplay-pattern-interface-implementation&quot;&gt;Screenplay Pattern: Interface Implementation&lt;/h4&gt;
&lt;p&gt;Screenplay, on the other hand, follows a more &lt;strong&gt;composition-driven&lt;/strong&gt; design by using &lt;strong&gt;interfaces&lt;/strong&gt; like &lt;code&gt;Task&lt;/code&gt;, &lt;code&gt;Question&lt;/code&gt;, or &lt;code&gt;Interaction&lt;/code&gt;, which are implemented rather than extended. This provides greater flexibility and promotes the principle of &quot;composition over inheritance.&quot;&lt;/p&gt;
&lt;p&gt;Each &lt;code&gt;Actor&lt;/code&gt; performs actions by executing these Tasks or Questions, making the codebase more modular and maintainable.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Login implements Task {
    private final String username;
    private final String password;

    public static Login withCredentials(String username, String password) {
        return Tasks.instrumented(Login.class, username, password);
    }

    @Override
    public &amp;lt;T extends Actor&amp;gt; void performAs(T actor) {
        actor.attemptsTo(
            Enter.theValue(username).into(LoginPage.USERNAME_FIELD),
            Enter.theValue(password).into(LoginPage.PASSWORD_FIELD),
            Click.on(LoginPage.LOGIN_BUTTON)
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While POM is easier to grasp and implement for smaller test suites, the Screenplay Pattern offers a more scalable and maintainable architecture for complex testing needs.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;comparison-pom-vs-screenplay&quot;&gt;Comparison: POM vs Screenplay&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Feature &lt;/th&gt;&lt;th&gt;Page Object Model &lt;/th&gt;&lt;th&gt;Screenplay Pattern&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Structure &lt;/td&gt;&lt;td&gt;Page-based &lt;/td&gt;&lt;td&gt;Actor-based&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Code Reuse &lt;/td&gt;&lt;td&gt;Inheritance &lt;/td&gt;&lt;td&gt;Composition&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Reusability &lt;/td&gt;&lt;td&gt;Moderate &lt;/td&gt;&lt;td&gt;High&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Readability &lt;/td&gt;&lt;td&gt;Good &lt;/td&gt;&lt;td&gt;Excellent&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Maintainability&lt;/td&gt;&lt;td&gt;Challenging in large projects &lt;/td&gt;&lt;td&gt;Easier due to modularity&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Test Style &lt;/td&gt;&lt;td&gt;Imperative &lt;/td&gt;&lt;td&gt;Declarative&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Design Focus &lt;/td&gt;&lt;td&gt;Pages &amp;amp; Elements &lt;/td&gt;&lt;td&gt;Tasks &amp;amp; Behaviors&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts-1&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;The Screenplay Pattern takes a bit of setup at the beginning, because its a approach rather than ready-to-use methods.(Serenity Library comes with that methods!) but the benefits in clarity, reusability, and scalability are huge especially in large automation projects. If you're building or maintaining a serious test framework, it's worth giving Screenplay a try!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Thanks for reading.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Test Automation</category>
<guid isPermaLink="true">https://coderlegion.com/1854/utility-class-of-pom-screenplay-pattern</guid>
<pubDate>Tue, 15 Apr 2025 08:51:06 +0000</pubDate>
</item>
<item>
<title>Facade Pattern provides a simplified interface to complex subsystems, hiding implementation details behind a clean API.</title>
<link>https://coderlegion.com/1829/facade-pattern-provides-simplified-interface-complex-subsystems-implementation-details</link>
<description>&lt;h2 id=&quot;the-facade-pattern-simplifying-complex-systems&quot;&gt;&lt;strong&gt;The Facade Pattern: Simplifying Complex Systems&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=15622063334808448262&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;In modern software development, we often work with complex systems that consist of numerous interrelated components. These systems, while powerful, can be challenging to understand and use effectively. The Facade Pattern offers an elegant solution to this problem by providing a simplified interface to a complex subsystem.&lt;/p&gt;
&lt;h3 id=&quot;the-problem-complexity-in-action&quot;&gt;&lt;strong&gt;The Problem: Complexity in Action&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Consider a home theater system with multiple components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Popcorn popper&lt;/li&gt;
&lt;li&gt;Theater lights&lt;/li&gt;
&lt;li&gt;Amplifier&lt;/li&gt;
&lt;li&gt;Projection screen&lt;/li&gt;
&lt;li&gt;Projector&lt;/li&gt;
&lt;li&gt;CD player&lt;/li&gt;
&lt;li&gt;Tuner&lt;/li&gt;
&lt;li&gt;DVD player&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To watch a movie, a user would need to perform 13 separate operations:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Turn on the popcorn popper&lt;/li&gt;
&lt;li&gt;Start the popper popping&lt;/li&gt;
&lt;li&gt;Dim the lights&lt;/li&gt;
&lt;li&gt;Put the screen down&lt;/li&gt;
&lt;li&gt;Turn the projector on&lt;/li&gt;
&lt;li&gt;Put the projector on wide-screen mode&lt;/li&gt;
&lt;li&gt;Set the projector input to DVD&lt;/li&gt;
&lt;li&gt;Turn the sound amplifier on&lt;/li&gt;
&lt;li&gt;Set the amplifier to DVD input&lt;/li&gt;
&lt;li&gt;Set the amplifier volume to medium&lt;/li&gt;
&lt;li&gt;Turn the DVD player on&lt;/li&gt;
&lt;li&gt;Set the amplifier to surround sound&lt;/li&gt;
&lt;li&gt;Start the DVD player playing&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This process is cumbersome, error-prone, and requires detailed knowledge of each component and their interactions. Clearly, we need a highly abstract approach that simplifies our interaction with the system without worrying about the details.&lt;/p&gt;
&lt;h3 id=&quot;the-facade-pattern-definition-and-purpose&quot;&gt;&lt;strong&gt;The Facade Pattern: Definition and Purpose&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;The Facade Pattern provides a unified interface to a set of interfaces in a subsystem. It defines a higher-level interface that makes the subsystem easier to use by hiding its complexity from clients.&lt;/p&gt;
&lt;p&gt;The pattern emerged from the need to simplify interactions with complex systems. As software systems grow, they often develop intricate subsystems with numerous components and relationships. This complexity creates several challenges:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Steep learning curves for new developers&lt;/li&gt;
&lt;li&gt;Increased coupling between clients and implementation details&lt;/li&gt;
&lt;li&gt;Difficulty in maintaining code that interacts with many components&lt;/li&gt;
&lt;li&gt;Error-prone integration processes&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;how-it-works&quot;&gt;&lt;strong&gt;How It Works&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;The Facade Pattern works by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Creating a new class (the facade) that presents a simplified interface&lt;/li&gt;
&lt;li&gt;Internally, the facade manages the complex interactions with subsystem components&lt;/li&gt;
&lt;li&gt;Clients interact only with the facade rather than directly with the subsystem&lt;/li&gt;
&lt;li&gt;The facade translates simple, high-level requests into the appropriate sequence of subsystem operations&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This approach shields clients from the complexity of the subsystem while still allowing them to access its functionality.&lt;/p&gt;
&lt;h3 id=&quot;basic-components&quot;&gt;&lt;strong&gt;Basic Components&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=16399895263400749233&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The Facade Pattern consists of:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The Facade: A class that provides a simplified interface to the subsystem&lt;/li&gt;
&lt;li&gt;The Subsystem: A set of complex classes that implement functionality&lt;/li&gt;
&lt;li&gt;The Client: Code that interacts with the subsystem through the façade&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Important Note&lt;/strong&gt;: The pattern does not alter the subsystem classes or prevent clients from accessing them directly if needed. It simply offers an easier way to use the subsystem for common tasks.&lt;/p&gt;
&lt;h3 id=&quot;solution-to-the-home-theater-problem&quot;&gt;&lt;strong&gt;Solution to the Home Theater Problem&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=12811357404681033592&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;To solve our home theater complexity problem, we can create a HomeTheaterFacade class that wraps all the subsystem components and provides simplified methods:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class HomeTheaterFacade {
    
    // Component references
    Amplifier amp;
    Tuner tuner;
    DvdPlayer dvd;
    CdPlayer cd;
    Projector projector;
    TheaterLights lights;
    Screen screen;
    PopcornPopper popper;
    
    // Constructor with component initialization
    public void watchMovie(String movie) {
        
        System.out.println(&quot;Get ready to watch a movie...&quot;);
        popper.on();
        popper.pop();
        lights.dim(10);
        screen.down();
        projector.on();
        projector.wideScreenMode();
        amp.on();
        amp.setDvd(dvd);
        amp.setSurroundSound();
        amp.setVolume(5);
        dvd.on();
        dvd.play(movie);
    }
    
    public void endMovie() {
        // Similar sequence to shut everything down
    }
    
    // Other methods for different use cases
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, instead of 13 separate operations, the client simply calls:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;homeTheater.watchMovie(&quot;Raiders of the Lost Ark&quot;);
// When finished
homeTheater.endMovie();
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-principle-of-least-knowledge&quot;&gt;&lt;strong&gt;The Principle of Least Knowledge&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;The Facade Pattern supports the Principle of Least Knowledge (or Law of Demeter), which states: &quot;Talk only to your immediate friends.&quot; This principle guides us to reduce the interactions between objects to just a few closely related classes.&lt;/p&gt;
&lt;p&gt;For any object, you should only invoke methods that belong to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The object itself&lt;/li&gt;
&lt;li&gt;Objects passed as parameters to a method&lt;/li&gt;
&lt;li&gt;Any object the method creates&lt;/li&gt;
&lt;li&gt;Any direct component objects&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The Facade Pattern supports this principle by:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Providing clients with a single point of contact (the facade)&lt;/li&gt;
&lt;li&gt;Handling all the complex interactions with the subsystem internally&lt;/li&gt;
&lt;li&gt;Reducing the number of objects the client needs to know about&lt;/li&gt;
&lt;li&gt;Creating clear boundaries between the client and the subsystem&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This reduces dependencies and makes the system more maintainable.&lt;/p&gt;
&lt;h3 id=&quot;benefits-of-the-facade-pattern&quot;&gt;&lt;strong&gt;Benefits of the Facade Pattern&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Simplified Interface: Provides a simple interface to a complex subsystem, making it easier to use.&lt;/li&gt;
&lt;li&gt;Reduced Coupling: Decreases the dependency between client code and subsystem components.&lt;/li&gt;
&lt;li&gt;Subsystem Flexibility: Allows the subsystem to change without affecting client code.&lt;/li&gt;
&lt;li&gt;Improved Code Readability: Makes client code more readable and focused on high-level tasks.&lt;/li&gt;
&lt;li&gt;Better System Organization: Creates clear layers in your architecture.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;disadvantages-of-the-facade-pattern&quot;&gt;&lt;strong&gt;Disadvantages of the Facade Pattern&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Limited Functionality Access: May restrict access to some advanced features of the subsystem.&lt;/li&gt;
&lt;li&gt;Additional Indirection Layer: Adds an extra layer in the architecture.&lt;/li&gt;
&lt;li&gt;Potential Facade Bloat: The facade class can become too large if not designed carefully.&lt;/li&gt;
&lt;li&gt;Maintenance Overhead: Requires updating when the subsystem changes.&lt;/li&gt;
&lt;li&gt;Minor Performance Impact: May introduce slight overhead due to delegation.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;relation-with-other-patterns&quot;&gt;&lt;strong&gt;Relation with Other Patterns&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;The Facade Pattern differs from similar patterns in key ways:&lt;/p&gt;
&lt;h4 id=&quot;facade-vs-adapter&quot;&gt;&lt;strong&gt;Facade vs. Adapter&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Purpose: Adapter converts one interface to another; Facade simplifies complex interfaces&lt;/li&gt;
&lt;li&gt;Interface Change: Adapter changes interface format; Facade maintains format but simplifies&lt;/li&gt;
&lt;li&gt;Intent: Adapter makes incompatible interfaces compatible; Facade hides complexity&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;facade-vs-abstract-factory&quot;&gt;&lt;strong&gt;Facade vs. Abstract Factory&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Purpose: Abstract Factory creates families of related objects; Facade simplifies access to subsystem functionality&lt;/li&gt;
&lt;li&gt;Intent: Abstract Factory abstracts object creation; Facade abstracts subsystem use&lt;/li&gt;
&lt;li&gt;Client Knowledge: Abstract Factory client creates objects; Facade client uses existing objects&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;application-in-layered-architecture&quot;&gt;&lt;strong&gt;Application in Layered Architecture&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;media/image5.png&quot; alt=&quot;Layered Architecture with Facade&quot;&gt;&lt;/p&gt;
&lt;p&gt;The Facade Pattern is frequently used in layered architectures where it acts as a gateway between layers. For example, in a typical application with UI, Domain, and Data layers, facades can provide clean interfaces between these layers.&lt;/p&gt;
&lt;h3 id=&quot;modern-applications-cqrs-facades&quot;&gt;&lt;strong&gt;Modern Applications: CQRS Facades&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;In modern software architecture, the Facade Pattern finds application in Command Query Responsibility Segregation (CQRS) where separate facades are used for commands (write operations) and queries (read operations), providing cleaner code separation.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/@anthony_miller/cqrs-facades-cleaner-code-separation-3d4c25504dc8&quot;&gt;&lt;em&gt;CQRS Facades: Cleaner Code Separation | by Anthony Miller | Medium&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;conclusion-6&quot;&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The Facade Pattern offers a powerful way to manage complexity in software systems. By providing a simplified interface to complex subsystems, it makes code more maintainable, readable, and flexible. While it does introduce an additional layer of abstraction, the benefits in terms of usability and maintainability often outweigh this cost, particularly in systems with complex components that need to work together seamlessly.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;When designing your next complex system, consider whether a facade might help simplify your client code and create cleaner boundaries between system components.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Full-stack Dev</category>
<guid isPermaLink="true">https://coderlegion.com/1829/facade-pattern-provides-simplified-interface-complex-subsystems-implementation-details</guid>
<pubDate>Wed, 09 Apr 2025 19:04:44 +0000</pubDate>
</item>
<item>
<title>Mastering System Design: Your Definitive Guide to Success in Tech Interviews </title>
<link>https://coderlegion.com/1750/mastering-system-design-your-definitive-guide-to-success-in-tech-interviews</link>
<description>&lt;p&gt;Table of Contents&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Key Concepts of System Architecture&lt;/li&gt;
&lt;li&gt;Imagine This Scenario&lt;/li&gt;
&lt;li&gt;How This Relates to Design Patterns&lt;/li&gt;
&lt;li&gt;Problems Solved by Scalability&lt;/li&gt;
&lt;li&gt;Example in TypeScript, JavaScript&lt;/li&gt;
&lt;li&gt;Pros and Cons of Real-Time Systems&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;li&gt;Key Takeaways&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Introduction&lt;/p&gt;
&lt;p&gt;System design is a critical component in the toolkit of any software engineer, especially when preparing for interviews at top tech companies like &quot;MAANG&quot; (Meta, Amazon, Apple, Netflix, Google) or &quot;MAMAA&quot; (Meta, Amazon, Microsoft, Apple, Alphabet). A deep understanding of system architecture, design patterns, and the ability to build scalable systems is indispensable. This guide provides insights and practical knowledge to ace system design interviews and excel in the tech industry.&lt;/p&gt;
&lt;p&gt;Key Concepts of System Architecture&lt;/p&gt;
&lt;p&gt;System architecture refers to the structured design of a system’s components and their interactions. It involves defining software and hardware components, implementing system requirements, and ensuring the system's overall integrity and flexibility to adapt to future demands. Understanding system architecture is crucial for designing scalable, maintainable, and efficient software solutions.&lt;/p&gt;
&lt;p&gt;Imagine This Scenario&lt;/p&gt;
&lt;p&gt;To grasp system architecture better, let’s consider a real-world analogy:&lt;/p&gt;
&lt;p&gt;Imagine a bustling cafe that starts with a single barista serving coffee. As its popularity grows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Vertical Scaling: The cafe upgrades its equipment (e.g., faster coffee machines) to serve more customers.&lt;/li&gt;
&lt;li&gt;Horizontal Scaling: The cafe opens new branches in different locations to handle demand.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Similarly, in system design, ensuring your architecture can scale both horizontally and vertically is essential to maintaining performance as demand increases.&lt;/p&gt;
&lt;p&gt;How This Relates to Design Patterns&lt;/p&gt;
&lt;p&gt;System architecture and design patterns are closely linked:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;System Architecture: Provides the high-level overview of the entire system.&lt;/li&gt;
&lt;li&gt;Design Patterns: Offer tested templates for solving specific problems within that architecture.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Design patterns not only enhance scalability and maintainability but also enforce coding standards, improve readability, and reduce technical debt—all crucial for solving complex real-world problems.&lt;/p&gt;
&lt;p&gt;Problems Solved by Scalability&lt;/p&gt;
&lt;p&gt;Scalability addresses the need to handle increased loads while maintaining performance and cost-efficiency. Key benefits include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Managing large volumes of data efficiently.&lt;/li&gt;
&lt;li&gt;Maintaining system performance during peak loads.&lt;/li&gt;
&lt;li&gt;Reducing latency.&lt;/li&gt;
&lt;li&gt;Supporting elasticity (scaling up during high demand and scaling down to optimize costs during low demand).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Load balancers play a pivotal role in scalable architectures by distributing traffic evenly across servers, ensuring seamless performance.&lt;/p&gt;
&lt;p&gt;Example in TypeScript, JavaScript&lt;/p&gt;
&lt;p&gt;Here’s an example demonstrating the Singleton Design Pattern with a practical use case: managing a database connection.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Singleton Pattern - Managing a Database Connection in TypeScript
class Database {
  private static instance: Database;

  private constructor() {
    console.log(&quot;Database connection established.&quot;);
  }

  public static getInstance(): Database {
    if (!Database.instance) {
      Database.instance = new Database();
    }
    return Database.instance;
  }

  public query(sql: string) {
    console.log(`Executing query: ${sql}`);
  }
}

// Usage
const db1 = Database.getInstance();
const db2 = Database.getInstance();

db1.query(&quot;SELECT * FROM users&quot;);
console.assert(db1 === db2, &quot;Both are the same instance&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use Singleton patterns cautiously as they introduce global state and can make unit testing more challenging.&lt;/li&gt;
&lt;li&gt;In modern applications, dependency injection is often preferred to manage shared resources like database connections.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pros and Cons of Real-Time Systems&lt;/p&gt;
&lt;p&gt;Pros&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Provide immediate processing and response.&lt;/li&gt;
&lt;li&gt;Essential for applications requiring prompt data handling (e.g., stock trading platforms, IoT systems).&lt;/li&gt;
&lt;li&gt;Cloud services like AWS Kinesis or Azure Event Hubs make real-time processing more accessible.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Cons&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Developing real-time systems is complex and demands precise specifications.&lt;/li&gt;
&lt;li&gt;Maintenance costs can be higher due to specialized requirements.&lt;/li&gt;
&lt;li&gt;Testing real-time systems requires simulating live environments, which can be resource-intensive.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Summary&lt;/p&gt;
&lt;p&gt;This article explored key aspects of system design, including system architecture, design patterns, scalability, and real-time systems. By understanding these concepts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Developers can design robust systems capable of handling modern demands.&lt;/li&gt;
&lt;li&gt;Interview candidates can prepare effectively for high-stakes system design discussions at top tech companies.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Key Takeaways&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Scalability:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Involves horizontal scaling (adding more servers) and vertical scaling (upgrading existing servers).&lt;/li&gt;
&lt;li&gt;Elasticity allows systems to scale up or down based on demand, optimizing costs in cloud environments.&lt;/li&gt;
&lt;li&gt;Load balancers are critical for distributing traffic efficiently.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Design Patterns:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Enhance maintainability, readability, and scalability while reducing technical debt.&lt;/li&gt;
&lt;li&gt;Singleton patterns should be used judiciously due to their impact on global state management.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Real-Time Systems:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Pros: Immediate response times are essential for critical applications.&lt;/li&gt;
&lt;li&gt;Cons: Complex development and maintenance requirements, though cloud-based solutions reduce infrastructure costs.&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>TypeScript</category>
<guid isPermaLink="true">https://coderlegion.com/1750/mastering-system-design-your-definitive-guide-to-success-in-tech-interviews</guid>
<pubDate>Mon, 31 Mar 2025 18:31:47 +0000</pubDate>
</item>
<item>
<title>Understanding the Observer Pattern in C# with IObservable and IObserver</title>
<link>https://coderlegion.com/1434/understanding-the-observer-pattern-in-c-with-iobservable-and-iobserver</link>
<description>&lt;p&gt;Introduction&lt;/p&gt;
&lt;p&gt;The Observer Pattern is a behavioral design pattern that allows an object (the subject) to notify multiple other objects (observers) about state changes. It is commonly used in event-driven programming, messaging systems, and real-time notifications.&lt;/p&gt;
&lt;p&gt;C# provides a built-in way to implement this pattern using the IObservable and IObserver interfaces. Unlike traditional event handlers, IObservable offers better flexibility, structured error handling, and built-in subscription management.&lt;/p&gt;
&lt;p&gt;Implementation of the Observer Pattern&lt;/p&gt;
&lt;p&gt;The following example is based on the ObserverPattern repository by &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/ObserverPattern&quot;&gt;me&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using System;
using System.Collections.Generic;

namespace ObserverPattern
{
    // Observable: Order Processor
    public class OrderProcessor : IObservable&amp;lt;string&amp;gt;
    {
        private List&amp;lt;IObserver&amp;lt;string&amp;gt;&amp;gt; observers = new();

        public IDisposable Subscribe(IObserver&amp;lt;string&amp;gt; observer)
        {
            if (!observers.Contains(observer))
                observers.Add(observer);
            return new Unsubscriber(observers, observer);
        }

        public void ProcessOrder(int orderId)
        {
            Notify($&quot;Order {orderId} is Processing&quot;);
            Notify($&quot;Order {orderId} is Shipped&quot;);
            Notify($&quot;Order {orderId} is Delivered&quot;);
        }

        private void Notify(string message)
        {
            foreach (var observer in observers)
                observer.OnNext(message);
        }

        private class Unsubscriber : IDisposable
        {
            private List&amp;lt;IObserver&amp;lt;string&amp;gt;&amp;gt; _observers;
            private IObserver&amp;lt;string&amp;gt; _observer;

            public Unsubscriber(List&amp;lt;IObserver&amp;lt;string&amp;gt;&amp;gt; observers, IObserver&amp;lt;string&amp;gt; observer)
            {
                _observers = observers;
                _observer = observer;
            }

            public void Dispose()
            {
                if (_observer != null &amp;amp;&amp;amp; _observers.Contains(_observer))
                    _observers.Remove(_observer);
            }
        }
    }

    // Observer: Console Logger
    public class ConsoleLogger : IObserver&amp;lt;string&amp;gt;
    {
        public void OnNext(string value) =&amp;gt; Console.WriteLine($&quot;[Console] {value}&quot;);
        public void OnError(Exception error) =&amp;gt; Console.WriteLine(&quot;[Console] Error occurred.&quot;);
        public void OnCompleted() =&amp;gt; Console.WriteLine(&quot;[Console] No more updates.&quot;);
    }

    // Observer: File Logger
    public class FileLogger : IObserver&amp;lt;string&amp;gt;
    {
        private readonly string filePath = &quot;log.txt&quot;;

        public void OnNext(string value)
        {
            using StreamWriter writer = new(filePath, append: true);
            writer.WriteLine(value);
        }

        public void OnError(Exception error)
        {
            using StreamWriter writer = new(filePath, append: true);
            writer.WriteLine(&quot;[File] Error occurred.&quot;);
        }

        public void OnCompleted()
        {
            using StreamWriter writer = new(filePath, append: true);
            writer.WriteLine(&quot;[File] No more updates.&quot;);
        }
    }

    // Main Program
    class Program
    {
        static void Main()
        {
            var orderProcessor = new OrderProcessor();
            var consoleLogger = new ConsoleLogger();
            var fileLogger = new FileLogger();

            using var consoleSubscription = orderProcessor.Subscribe(consoleLogger);
            using var fileSubscription = orderProcessor.Subscribe(fileLogger);

            orderProcessor.ProcessOrder(101);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.&lt;strong&gt;OrderProcessor (Observable)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-Implements IObservable&amp;lt;string&amp;gt;    
-Notifies observers of order status changes    
-Allows observers to subscribe and unsubscribe dynamically
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.&lt;strong&gt;Observers (ConsoleLogger &amp;amp; FileLogger)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;   -Implement IObserver&lt;br&gt;
   -ConsoleLogger logs messages to the console&lt;br&gt;
   -FileLogger writes updates to a log file&lt;/p&gt;
&lt;p&gt;3&lt;strong&gt;.Subscription Management&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;   -Uses IDisposable to allow safe unsubscription&lt;br&gt;
   -Observers receive updates dynamically&lt;/p&gt;
&lt;p&gt;Why Not Use Event Handlers (event and delegate)?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=11588451199691131197&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Benefits of Using IObservable and IObserver&lt;/p&gt;
&lt;p&gt;✅ Better Resource Management: Unsubscribing is handled via IDisposable, reducing memory leaks.&lt;/p&gt;
&lt;p&gt;✅ Built-in Error Handling: The OnError(Exception) method provides structured error handling.&lt;/p&gt;
&lt;p&gt;✅ Completion Notification: OnCompleted() informs observers when no more updates will be sent.&lt;/p&gt;
&lt;p&gt;✅ Supports Asynchronous &amp;amp; Reactive Programming: Works seamlessly with Reactive Extensions (Rx.NET).&lt;/p&gt;
&lt;p&gt;✅ Scales Well for Complex Scenarios: Ideal for event-driven architectures where multiple observers must react to state changes dynamically.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real-World Use Cases&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Event-driven programming (e.g., UI events, logging systems)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stock market applications (e.g., notifying traders about price&lt;/p&gt;
&lt;p&gt;changes)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Messaging and notification systems (e.g., email/SMS updates)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reactive programming (e.g., Rx.NET, streaming APIs)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;IoT (Internet of Things) (e.g., sensor data updates)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The Observer Pattern is an essential design pattern for event-driven applications. While event handlers (event and delegate) are useful for simple event propagation, IObservable and IObserver provide a more structured and flexible approach to managing subscriptions, error handling, and completion notifications.&lt;/p&gt;
&lt;p&gt;By leveraging this pattern, developers can build scalable, reactive, and event-driven applications that efficiently manage state changes across multiple subscribers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/ObserverPattern&quot;&gt;GitHub Repository:&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;        &amp;lt;a href=&quot;https://github.com/stevsharp/ObserverPattern&quot; target=&quot;_blank&quot;&amp;gt;ObserverPattern&amp;lt;/a&amp;gt;
        
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/api/system.iobservable-1?view=net-9.0&quot;&gt;Microsoft Docs:&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/dotnet/reactive&quot;&gt;Reactive Extensions (Rx.NET) Official:&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Book Reference : &lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612&quot;&gt;Design Patterns: Elements of Reusable Object-Oriented Software&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/1434/understanding-the-observer-pattern-in-c-with-iobservable-and-iobserver</guid>
<pubDate>Tue, 11 Mar 2025 10:41:06 +0000</pubDate>
</item>
</channel>
</rss>