<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Yazan's Software Thoughts]]></title><description><![CDATA[A blog about clean software architecture and design using javascript]]></description><link>https://yazanalaboudi.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 10 Oct 2026 02:32:32 GMT</lastBuildDate><atom:link href="https://yazanalaboudi.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[The Last Event-Sourced Repository You'll Ever Need]]></title><description><![CDATA[Introduction
In Domain-Driven Design (DDD), repositories play a crucial role—they represent a boundary between the domain model and the underlying infrastructure. But in event-sourced systems, developers often fall into one of two traps: either overs...]]></description><link>https://yazanalaboudi.dev/the-last-event-sourced-repository-youll-ever-need</link><guid isPermaLink="true">https://yazanalaboudi.dev/the-last-event-sourced-repository-youll-ever-need</guid><category><![CDATA[DDD]]></category><category><![CDATA[repository]]></category><category><![CDATA[repository-pattern]]></category><category><![CDATA[#CQRS]]></category><category><![CDATA[clean code]]></category><category><![CDATA[Architecture Design]]></category><dc:creator><![CDATA[Yazan Alaboudi]]></dc:creator><pubDate>Sat, 05 Apr 2025 20:03:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/bO4UR1VzQu8/upload/ce810fc816afd0e77f50a09d1b01f6d4.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<h2 id="heading-introduction">Introduction</h2>
<p>In Domain-Driven Design (DDD), repositories play a crucial role—they represent a boundary between the domain model and the underlying infrastructure. But in event-sourced systems, developers often fall into one of two traps: either oversimplifying the repository to the point of uselessness, or overengineering it to the point of confusion.</p>
<p>This article builds up a production-grade event-sourced repository step-by-step, evolving it as new needs and insights emerge. Each design choice is carefully justified. By the end, you'll have a flexible, cleanly layered, and testable repository structure built in <strong>TypeScript</strong> that embraces the nuances of event sourcing.</p>
<hr />
<h2 id="heading-why-repositories-matter-in-ddd">Why Repositories Matter in DDD</h2>
<p>A repository isn’t a fancy name for a database access class. It’s a <strong>semantic boundary</strong>. In DDD, a repository is a contract that provides access to aggregates, and its purpose is to abstract away how aggregates are persisted and reconstructed.</p>
<p>A general repository contract starts simple:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">interface</span> Repository&lt;A&gt; {
  getById(id: Identifier): <span class="hljs-built_in">Promise</span>&lt;A | <span class="hljs-literal">undefined</span>&gt;;
  save(aggregate: A): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt;;
}
</code></pre>
<p>Then, we define aggregate-specific repositories:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">interface</span> UserRepository <span class="hljs-keyword">extends</span> Repository&lt;User&gt; {}
</code></pre>
<p>This makes the domain model dependent on <strong>what</strong> a repository does—not <strong>how</strong> it does it. Whether you're using a relational database, a document store, or an event store is irrelevant at this level. That abstraction is intentional.</p>
<p>But when you <strong>do</strong> use event sourcing, things get more interesting.</p>
<hr />
<h2 id="heading-why-event-sourcing-requires-more-structure">Why Event Sourcing Requires More Structure</h2>
<p>In an event-sourced system, aggregates are rehydrated from event streams. The repository must:</p>
<ul>
<li><p>Reconstruct an aggregate from a sequence of past events</p>
</li>
<li><p>Store newly produced events</p>
</li>
<li><p>Track versions to ensure optimistic concurrency</p>
</li>
<li><p>Translate between domain events and serialized forms</p>
</li>
</ul>
<p>These are nontrivial concerns, and they repeat for every aggregate. That’s why it makes sense to create an abstract base class that handles this boilerplate while leaving domain-specific behavior to concrete implementations.</p>
<p>We call this base class <code>EventSourcedRepository</code>:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> EventSourcedRepository&lt;A <span class="hljs-keyword">extends</span> AggregateRoot&lt;E&gt;, E <span class="hljs-keyword">extends</span> Event&gt; {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> eventStore: EventStore</span>) {}

  <span class="hljs-keyword">protected</span> <span class="hljs-keyword">abstract</span> getStreamId(id: Identifier): <span class="hljs-built_in">string</span>;
  <span class="hljs-keyword">protected</span> <span class="hljs-keyword">abstract</span> rehydrate(events: E[]): A;
  <span class="hljs-keyword">protected</span> <span class="hljs-keyword">abstract</span> toESEvent(event: E): ESEvent;
  <span class="hljs-keyword">protected</span> <span class="hljs-keyword">abstract</span> fromESEvent(es: ESEvent): E;

  <span class="hljs-keyword">async</span> getById(id: Identifier): <span class="hljs-built_in">Promise</span>&lt;AggregateEnvelope&lt;A&gt; | <span class="hljs-literal">undefined</span>&gt; {
    <span class="hljs-keyword">const</span> streamId = <span class="hljs-built_in">this</span>.getStreamId(id);
    <span class="hljs-keyword">const</span> esEvents = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.eventStore.getEventsByStreamId(streamId);
    <span class="hljs-keyword">if</span> (esEvents.length === <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">undefined</span>;

    <span class="hljs-keyword">const</span> domainEvents = esEvents.map(<span class="hljs-built_in">this</span>.fromESEvent.bind(<span class="hljs-built_in">this</span>));
    <span class="hljs-keyword">const</span> aggregate = <span class="hljs-built_in">this</span>.rehydrate(domainEvents);

    <span class="hljs-keyword">return</span> { aggregate, version: esEvents.length };
  }

  <span class="hljs-keyword">async</span> save(envelope: AggregateEnvelope&lt;A&gt;): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
    <span class="hljs-keyword">const</span> { aggregate, version } = envelope;
    <span class="hljs-keyword">const</span> events = aggregate.events.map(<span class="hljs-built_in">this</span>.toESEvent.bind(<span class="hljs-built_in">this</span>));
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.eventStore.save(<span class="hljs-built_in">this</span>.getStreamId(aggregate.id), version, events);
    aggregate.clearEvents();
  }
}
</code></pre>
<p>Let’s unpack why each of these responsibilities is handled here.</p>
<hr />
<h2 id="heading-why-the-aggregateenvelope-matters">Why the AggregateEnvelope Matters</h2>
<p>At first glance, you might wonder why we don't just return the aggregate from <code>getById</code>. But remember: event-sourced systems use <strong>optimistic concurrency</strong>. That means the stream version at the time of loading is crucial when saving back later.</p>
<p>Instead of tracking that version inside the aggregate—which pollutes the domain with infrastructure—we wrap the aggregate in an envelope:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">interface</span> AggregateEnvelope&lt;A <span class="hljs-keyword">extends</span> AggregateRoot&gt; {
  aggregate: A;
  version: <span class="hljs-built_in">number</span>;
}
</code></pre>
<p>This design keeps the domain pure while still giving the repository the data it needs to safely persist changes.</p>
<h3 id="heading-in-the-application-layer">In the Application Layer</h3>
<pre><code class="lang-ts"><span class="hljs-keyword">class</span> RenameUserHandler {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> userRepo: UserRepository</span>) {}

  <span class="hljs-keyword">async</span> execute(id: Identifier, newName: <span class="hljs-built_in">string</span>) {
    <span class="hljs-keyword">const</span> envelope = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.userRepo.getById(id);
    <span class="hljs-keyword">if</span> (!envelope) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"User not found"</span>);

    envelope.aggregate.rename(newName);
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.userRepo.save(envelope);
  }
}
</code></pre>
<p>Note that the application code doesn’t care about event sourcing or stream versions—it just performs business logic.</p>
<hr />
<h2 id="heading-why-event-translation-must-be-explicit">Why Event Translation Must Be Explicit</h2>
<p>You might be tempted to serialize domain events directly into the store, but this introduces long-term risks:</p>
<ul>
<li><p>You can’t evolve the schema easily</p>
</li>
<li><p>Value objects may not serialize correctly</p>
</li>
<li><p>Refactoring becomes brittle</p>
</li>
</ul>
<p>Instead, we define translation methods:</p>
<h3 id="heading-flatten-events-for-storage">Flatten events for storage:</h3>
<pre><code class="lang-ts"><span class="hljs-keyword">protected</span> toESEvent(event: UserEvent): ESEvent {
  <span class="hljs-keyword">switch</span> (event.constructor) {
    <span class="hljs-keyword">case</span> UserCreated:
      <span class="hljs-keyword">return</span> {
        <span class="hljs-keyword">type</span>: <span class="hljs-string">"UserCreated"</span>,
        data: { id: event.id.value, name: event.name.value },
      };
    <span class="hljs-keyword">case</span> UserNameUpdated:
      <span class="hljs-keyword">return</span> {
        <span class="hljs-keyword">type</span>: <span class="hljs-string">"UserNameUpdated"</span>,
        data: { name: event.name.value },
      };
  }
}
</code></pre>
<h3 id="heading-reconstruct-events-for-rehydration">Reconstruct events for rehydration:</h3>
<pre><code class="lang-ts"><span class="hljs-keyword">protected</span> fromESEvent(es: ESEvent): UserEvent {
  <span class="hljs-keyword">switch</span> (es.type) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">"UserCreated"</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> UserCreated(<span class="hljs-keyword">new</span> Identifier(es.data.id), <span class="hljs-keyword">new</span> Name(es.data.name));
    <span class="hljs-keyword">case</span> <span class="hljs-string">"UserNameUpdated"</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> UserNameUpdated(<span class="hljs-keyword">new</span> Name(es.data.name));
    <span class="hljs-keyword">default</span>:
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Unknown event: <span class="hljs-subst">${es.<span class="hljs-keyword">type</span>}</span>`</span>);
  }
}
</code></pre>
<p>This makes versioning, refactoring, and interoperability <strong>explicit and safe</strong>.</p>
<hr />
<h2 id="heading-stream-id-namespacing">Stream ID Namespacing</h2>
<p>In real-world systems, the same conceptual entity—like a user—might be represented across multiple aggregate boundaries. For example, you might have one aggregate modeling user identity, another modeling account preferences, and another modeling user activity. All of these aggregates might share the same user ID value.</p>
<p>In a traditional database, this is typically not a problem—tables differentiate data implicitly. But in an event store, stream IDs must be globally unique. That’s where namespacing comes in.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">protected</span> getStreamId(id: Identifier): <span class="hljs-built_in">string</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-string">`user:<span class="hljs-subst">${id.value}</span>`</span>;
}
</code></pre>
<p>Here, we use <code>user:</code> as a namespace to differentiate the aggregate stream from others that may use the same ID. This prevents collisions, respects the autonomy of bounded contexts, and reinforces that stream ID construction is a responsibility of the repository—not the domain.</p>
<hr />
<h2 id="heading-capability-of-testing-at-every-layer-and-why-its-powerful">Capability of Testing at Every Layer (and Why It's Powerful)</h2>
<p>Thanks to the clean separation of concerns, testing is now straightforward:</p>
<h3 id="heading-unit-test-the-aggregate">Unit test the aggregate</h3>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> user = User.create(<span class="hljs-keyword">new</span> Identifier(<span class="hljs-string">"1"</span>), <span class="hljs-string">"Alice"</span>);
user.rename(<span class="hljs-string">"Bob"</span>);
expect(user.events[<span class="hljs-number">0</span>]).toBeInstanceOf(UserNameUpdated);
</code></pre>
<h3 id="heading-test-the-repository-with-a-mock-store">Test the repository with a mock store</h3>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> store = <span class="hljs-keyword">new</span> InMemoryEventStore();
<span class="hljs-keyword">const</span> repo = <span class="hljs-keyword">new</span> UserEventSourcedRepository(store);

<span class="hljs-keyword">const</span> user = User.create(<span class="hljs-keyword">new</span> Identifier(<span class="hljs-string">"1"</span>), <span class="hljs-string">"Alice"</span>);
<span class="hljs-keyword">await</span> repo.save({ aggregate: user, version: <span class="hljs-number">0</span> });

<span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> repo.getById(user.id);
expect(result?.aggregate.name).toBe(<span class="hljs-string">"Alice"</span>);
</code></pre>
<h3 id="heading-test-the-application-layer">Test the application layer</h3>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> store = <span class="hljs-keyword">new</span> InMemoryEventStore();
<span class="hljs-keyword">const</span> repo = <span class="hljs-keyword">new</span> UserEventSourcedRepository(store);
<span class="hljs-keyword">const</span> handler = <span class="hljs-keyword">new</span> RenameUserHandler(repo);

<span class="hljs-keyword">await</span> handler.execute(<span class="hljs-keyword">new</span> Identifier(<span class="hljs-string">"1"</span>), <span class="hljs-string">"Charlie"</span>);
<span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> repo.getById(<span class="hljs-keyword">new</span> Identifier(<span class="hljs-string">"1"</span>));
expect(result?.aggregate.name).toBe(<span class="hljs-string">"Charlie"</span>);
</code></pre>
<p>You can mock just the event store or the entire repository—it’s up to you. Because each piece is clearly separated, you can even swap the whole repository out for an in-memory implementation if that better suits your test needs. This allows you to bypass event serialization, concurrency logic, or store-specific behavior when testing higher-level workflows or application services. The composability of this design means you can tune your test setup to match your context.</p>
<hr />
<h2 id="heading-final-output-a-complete-user-repository">Final Output: A Complete User Repository</h2>
<pre><code class="lang-ts"><span class="hljs-keyword">class</span> UserEventSourcedRepository <span class="hljs-keyword">extends</span> EventSourcedRepository&lt;User, UserEvent&gt; {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">eventStore: EventStore</span>) {
    <span class="hljs-built_in">super</span>(eventStore);
  }

  <span class="hljs-keyword">protected</span> getStreamId(id: Identifier): <span class="hljs-built_in">string</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-string">`user:<span class="hljs-subst">${id.value}</span>`</span>;
  }

  <span class="hljs-keyword">protected</span> rehydrate(events: UserEvent[]): User {
    <span class="hljs-keyword">return</span> User.rehydrate(events);
  }

  <span class="hljs-keyword">protected</span> toESEvent(event: UserEvent): ESEvent {
    <span class="hljs-keyword">switch</span> (event.constructor) {
      <span class="hljs-keyword">case</span> UserCreated:
        <span class="hljs-keyword">return</span> {
          <span class="hljs-keyword">type</span>: <span class="hljs-string">"UserCreated"</span>,
          data: { id: event.id.value, name: event.name.value },
        };
      <span class="hljs-keyword">case</span> UserNameUpdated:
        <span class="hljs-keyword">return</span> {
          <span class="hljs-keyword">type</span>: <span class="hljs-string">"UserNameUpdated"</span>,
          data: { name: event.name.value },
        };
    }
  }

  <span class="hljs-keyword">protected</span> fromESEvent(es: ESEvent): UserEvent {
    <span class="hljs-keyword">switch</span> (es.type) {
      <span class="hljs-keyword">case</span> <span class="hljs-string">"UserCreated"</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> UserCreated(<span class="hljs-keyword">new</span> Identifier(es.data.id), <span class="hljs-keyword">new</span> Name(es.data.name));
      <span class="hljs-keyword">case</span> <span class="hljs-string">"UserNameUpdated"</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> UserNameUpdated(<span class="hljs-keyword">new</span> Name(es.data.name));
      <span class="hljs-keyword">default</span>:
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Unknown event type: <span class="hljs-subst">${es.<span class="hljs-keyword">type</span>}</span>`</span>);
    }
  }
}
</code></pre>
<hr />
<h2 id="heading-conclusion">Conclusion</h2>
<p>A great event-sourced repository isn’t just about reading and writing events. It’s about aligning your technical model with your business domain, keeping responsibilities clear, and making your system a pleasure to work with.</p>
<p>Throughout this article, we built a repository layer that:</p>
<ul>
<li><p>Starts from a clean, behavioral contract with the domain</p>
</li>
<li><p>Introduces a flexible and reusable abstract base class for event-sourced concerns</p>
</li>
<li><p>Keeps persistence details out of aggregates by using envelopes</p>
</li>
<li><p>Explicitly translates events between the domain and the event store</p>
</li>
<li><p>Namespaces stream IDs to reflect aggregate context boundaries</p>
</li>
<li><p>Makes every layer—from aggregate to application—easy to test</p>
</li>
</ul>
<p>The result is a well-structured repository pattern that scales with your system’s complexity, supports evolving business needs, and resists rot over time.</p>
<p>This is more than just an implementation—it’s a mental model for building robust systems.</p>
<p>This really is the last event-sourced repository you'll ever need.</p>
]]></content:encoded></item><item><title><![CDATA[Avoid the Variant Prop]]></title><description><![CDATA[Developing great contracts is one of the most crucial parts of software engineering. The components exported from your design system are no exception. In a React application, this means designing thoughtful and opinionated component APIs. Yet, there’...]]></description><link>https://yazanalaboudi.dev/avoid-the-variant-prop</link><guid isPermaLink="true">https://yazanalaboudi.dev/avoid-the-variant-prop</guid><category><![CDATA[Design]]></category><category><![CDATA[Design Systems]]></category><category><![CDATA[React]]></category><category><![CDATA[CSS]]></category><category><![CDATA[CSS in JS]]></category><category><![CDATA[component libraries]]></category><category><![CDATA[components]]></category><dc:creator><![CDATA[Yazan Alaboudi]]></dc:creator><pubDate>Tue, 01 Apr 2025 15:00:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/RTXwyy3ctXE/upload/v1640469647743/G0Ge8V1Mq.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Developing great contracts is one of the most crucial parts of software engineering. The components exported from your design system are no exception. In a React application, this means designing thoughtful and opinionated component APIs. Yet, there’s one prop that has quietly become the default—despite being a poor abstraction: the <code>variant</code> prop.</p>
<p>You’ll also see it as <code>type</code>, <code>size</code>, or <code>kind</code>. It's a simple enum-style prop that lets consumers select from a predefined set of styles or behaviors. But here's the catch: while it seems harmless, it often leads to deeply flawed component design. Worse, it pushes responsibility onto the application layer—right where consistency is hardest to enforce.</p>
<h3 id="heading-whats-the-problem-with-variant-props">What’s the Problem With <code>variant</code> Props?</h3>
<p>They seem like a great idea at first. After all, enums are simple! Consider this typical usage:</p>
<pre><code class="lang-typescript">&lt;Text size=<span class="hljs-string">"big"</span>&gt;Hello, world&lt;/Text&gt;
</code></pre>
<p>Looks fine, right? But this component doesn’t encapsulate the <em>design rule</em>—it defers it. It means the app must <em>always</em> remember that <code>size="big"</code> is what should be used for titles, across every context. The system isn’t enforcing a design decision; it’s offering a styling toolkit. And now, your app code is responsible for maintaining design consistency.</p>
<p>If your design system exports primitives like this, then the design system isn’t a system. It’s just a set of styled components. The actual <em>design</em> is happening in the app layer.</p>
<h3 id="heading-design-systems-should-make-decisions-not-defer-them">Design Systems Should Make Decisions, Not Defer Them</h3>
<p>Design systems exist to encode constraints. When we expose a <code>variant</code>, we ask the application to choose what the design system should’ve already decided.</p>
<p>Let’s say we need a <code>Thumbnail</code> component that displays user avatars at different sizes. Instead of this:</p>
<pre><code class="lang-typescript">&lt;Thumbnail size=<span class="hljs-string">"small"</span> url={url} /&gt;
</code></pre>
<p>You could offer purpose-driven components:</p>
<pre><code class="lang-typescript">&lt;CommentAvatar url={url} /&gt;
&lt;HeaderAvatar url={url} /&gt;
</code></pre>
<p>Now the design system owns the sizing logic. The application simply says <em>what</em> it wants to display, not <em>how</em> it should look. This creates a more declarative API, one that carries semantic meaning and removes styling decisions from the application layer.</p>
<h3 id="heading-god-components-and-leaky-abstractions">God Components and Leaky Abstractions</h3>
<p>The moment you introduce a <code>variant</code> prop, your component is at risk of becoming a God Component.</p>
<p>Let’s look at a common example:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> ButtonProps = {
  variant: <span class="hljs-string">'primary'</span> | <span class="hljs-string">'secondary'</span> | <span class="hljs-string">'danger'</span> | <span class="hljs-string">'link'</span>;
  size: <span class="hljs-string">'small'</span> | <span class="hljs-string">'medium'</span> | <span class="hljs-string">'large'</span>;
  isDisabled?: <span class="hljs-built_in">boolean</span>;
  onClick?: <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">void</span>;
};
</code></pre>
<p>Already, this component is branching internally:</p>
<ul>
<li><p>Only <code>danger</code> needs confirmation logic</p>
</li>
<li><p>Only <code>link</code> ignores <code>size</code></p>
</li>
<li><p>Only <code>primary</code> and <code>secondary</code> share color styles</p>
</li>
</ul>
<p>These props are not always meaningful together. This is a red flag: the component is <strong>non-cohesive</strong>. Props are only used <em>conditionally</em>, based on the variant. This violates the <strong>Single Responsibility Principle</strong>—a foundational idea in maintainable system design.</p>
<p>Like the God Class anti-pattern, this component tries to do everything. It becomes hard to reason about, harder to test, and almost impossible to extend without side effects.</p>
<h3 id="heading-a-better-approach-small-purposeful-components">A Better Approach: Small, Purposeful Components</h3>
<p>Instead of:</p>
<pre><code class="lang-typescript">&lt;Button variant=<span class="hljs-string">"primary"</span> size=<span class="hljs-string">"large"</span>&gt;Submit&lt;/Button&gt;
</code></pre>
<p>Split your design intent into meaningful components:</p>
<pre><code class="lang-typescript">&lt;PrimaryActionButton onClick={save}&gt;Submit&lt;/PrimaryActionButton&gt;
&lt;DestructiveButton onClick={deleteItem}&gt;Delete&lt;/DestructiveButton&gt;
</code></pre>
<p>This removes branching, improves readability, and strengthens alignment between design and code. The app doesn’t need to know which styles to apply—it just picks the component that represents the action.</p>
<h3 id="heading-bonus-its-easier-to-refactor">Bonus: It's Easier to Refactor</h3>
<p>Let’s say you want to change the padding on all <code>DestructiveButton</code>s. With purpose-built components, you can do that in one place. With a God Component, you'd have to branch inside <code>Button</code> and hope no other variant breaks.</p>
<p>This is <strong>Open-Closed Principle</strong> in action: your system is open for extension (add a new component), but closed for modification (no need to touch shared internals).</p>
<h3 id="heading-but-what-about-shared-visual-consistency">But What About Shared Visual Consistency?</h3>
<p>A common concern with this approach is: how do we maintain consistency across all these small components? For example, how do we ensure that all buttons still share the same border radius, font size, or padding?</p>
<p>The answer is: <strong>through shared design tokens or private base component primitives</strong>.</p>
<p>You don’t need to centralize behavior into a God Component to share styling. You can define a private <code>BaseButton</code> that encapsulates common styles:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// private to the component library</span>
<span class="hljs-keyword">const</span> BaseButton = <span class="hljs-function">(<span class="hljs-params">{ className, ...props }: React.ButtonHTMLAttributes&lt;HTMLButtonElement&gt;</span>) =&gt;</span> (
  &lt;button className={<span class="hljs-string">`rounded-md font-semibold <span class="hljs-subst">${className}</span>`</span>} {...props} /&gt;
);
</code></pre>
<p>Then you build on top of it:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> PrimaryActionButton = <span class="hljs-function">(<span class="hljs-params">props</span>) =&gt;</span> (
  &lt;BaseButton className=<span class="hljs-string">"bg-blue-600 text-white px-4 py-2"</span> {...props} /&gt;
);

<span class="hljs-keyword">const</span> DestructiveButton = <span class="hljs-function">(<span class="hljs-params">props</span>) =&gt;</span> (
  &lt;BaseButton className=<span class="hljs-string">"bg-red-600 text-white px-4 py-2"</span> {...props} /&gt;
);
</code></pre>
<p>This lets you preserve internal consistency without sacrificing cohesion or introducing bloated variant logic.</p>
<p>Better yet, you can define your border radius, colors, and spacing in <strong>design tokens</strong>—either via a theme, a CSS-in-JS solution, or even utility classes like Tailwind.</p>
<p>That way, the system ensures consistency, while each component remains focused and readable.</p>
<h3 id="heading-key-takeaways">Key Takeaways</h3>
<ul>
<li><p><strong>Avoid generic</strong> <code>variant</code><strong>,</strong> <code>type</code><strong>, or</strong> <code>size</code> <strong>props</strong> in design system components</p>
</li>
<li><p><strong>Expose semantic, intention-driven components</strong> that map directly to use cases</p>
</li>
<li><p><strong>Watch for conditional prop relevance</strong>—it's a sign your component is doing too much</p>
</li>
<li><p><strong>Lean on composition and specialization</strong> to enforce constraints in the system layer, not the app layer</p>
</li>
<li><p><strong>Use base components and design tokens</strong> to coordinate shared styling without centralizing logic</p>
</li>
<li><p><strong>Design APIs that describe <em>what</em> to show, not <em>how</em> it should look</strong></p>
</li>
</ul>
<p>Exporting flexible primitives might seem powerful—but design systems are most powerful when they encode decisions, not defer them. Let your components be opinionated. Let them enforce constraints. Let them be the system.</p>
]]></content:encoded></item><item><title><![CDATA[In Defence of Typescript Enums]]></title><description><![CDATA[In the past few years, there’s been a growing trend of criticizing TypeScript’s enum feature. Videos, blog posts, and tweets call for avoiding them entirely, and many developers are now defaulting to union types or const objects instead. And yes—Type...]]></description><link>https://yazanalaboudi.dev/in-defence-of-typescript-enums</link><guid isPermaLink="true">https://yazanalaboudi.dev/in-defence-of-typescript-enums</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[enum]]></category><category><![CDATA[antipattern]]></category><dc:creator><![CDATA[Yazan Alaboudi]]></dc:creator><pubDate>Mon, 31 Mar 2025 19:00:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Kzlrydy-sb4/upload/7551d187917655c6f450303b47a63864.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the past few years, there’s been a growing trend of criticizing TypeScript’s <code>enum</code> feature. Videos, blog posts, and tweets call for avoiding them entirely, and many developers are now defaulting to union types or <code>const</code> objects instead. And yes—TypeScript enums do have some quirky behaviors that make them less elegant than their counterparts in other languages. But here’s the thing:</p>
<p>Most of the complaints about enums aren’t about the feature itself—they’re about misuse.</p>
<p>I’d even go as far as to say that the majority of enum pitfalls in TypeScript are caused by a fundamental misunderstanding of what an enum is meant to be in the first place.</p>
<p>This article isn’t just a defense of enums. It’s a reminder of what enums <em>are for</em>—and why, when used correctly, they’re still a powerful and expressive tool in TypeScript.</p>
<hr />
<h2 id="heading-enums-are-meant-to-be-symbolic">Enums Are Meant To Be Symbolic</h2>
<p>Let’s get one thing straight: enums are not meant to represent literal values. They’re meant to represent symbolic ones.</p>
<p>This distinction might sound pedantic, but it’s at the core of the misunderstanding. As <a target="_blank" href="https://en.wikipedia.org/wiki/Enumerated_type">Wikipedia</a> puts it:</p>
<blockquote>
<p>"An enumerated type has values that are different from each other, and that can be compared and assigned, but are not specified by the programmer."</p>
</blockquote>
<p>That last part is key: <em>not specified by the programmer</em>. In other words, we shouldn’t care what the underlying value of an enum is. We only care about what it <em>represents</em>.</p>
<hr />
<h2 id="heading-what-does-symbolic-really-mean">What Does "Symbolic" Really Mean?</h2>
<p>Let’s make it concrete.</p>
<p>Imagine you're building a traffic light simulation. A traffic light can be red, yellow, or green. How should you represent those states in your code?</p>
<p>You could do:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> RED = <span class="hljs-string">"red"</span>;
<span class="hljs-keyword">const</span> YELLOW = <span class="hljs-string">"yellow"</span>;
<span class="hljs-keyword">const</span> GREEN = <span class="hljs-string">"green"</span>;
</code></pre>
<p>Or maybe you're feeling fancier and you use hex codes:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> RED = <span class="hljs-string">"#FF0000"</span>;
<span class="hljs-keyword">const</span> YELLOW = <span class="hljs-string">"#FFFF00"</span>;
<span class="hljs-keyword">const</span> GREEN = <span class="hljs-string">"#00FF00"</span>;
</code></pre>
<p>Or perhaps localized strings:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> RED = <span class="hljs-string">"rouge"</span>;
<span class="hljs-keyword">const</span> YELLOW = <span class="hljs-string">"jaune"</span>;
<span class="hljs-keyword">const</span> GREEN = <span class="hljs-string">"vert"</span>;
</code></pre>
<p>None of these are wrong—but none are ideal either. Because what you’re really trying to express is the <em>concept</em> of red, yellow, and green. Not a particular string or color code or translation. Just the idea.</p>
<p>That’s where symbolic values shine.</p>
<hr />
<h2 id="heading-javascript-has-a-built-in-symbolic-primitive">JavaScript Has a Built-in Symbolic Primitive</h2>
<p>ES6 introduced the <code>Symbol</code> primitive, and it’s a perfect metaphor for this kind of use:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> RED = <span class="hljs-built_in">Symbol</span>(<span class="hljs-string">"Red"</span>);
<span class="hljs-keyword">const</span> YELLOW = <span class="hljs-built_in">Symbol</span>(<span class="hljs-string">"Yellow"</span>);
<span class="hljs-keyword">const</span> GREEN = <span class="hljs-built_in">Symbol</span>(<span class="hljs-string">"Green"</span>);
</code></pre>
<p>Each call to <code>Symbol()</code> returns a unique value, regardless of the label. That means <code>Symbol("Red") !== Symbol("Red")</code>, unless you store it and reuse the same reference. The value itself doesn’t matter—just the identity.</p>
<p>This gets to the heart of how enums should behave: they should stand for something abstract, not something concrete.</p>
<hr />
<h2 id="heading-where-typescript-gets-a-little-funky">Where TypeScript Gets a Little Funky</h2>
<p>Here’s where TypeScript throws a curveball: unlike many other statically typed languages, TypeScript allows you to assign actual string or number <em>literal values</em> to enum members.</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">enum</span> TrafficLight {
  Red = <span class="hljs-string">"red"</span>,
  Yellow = <span class="hljs-string">"yellow"</span>,
  Green = <span class="hljs-string">"green"</span>
}
</code></pre>
<p>This feels convenient at first. You can now log enum values, serialize them into JSON, and display them in UIs directly. But this is a trap.</p>
<p>Why? Because you’re no longer using enums <em>symbolically</em>. You’ve turned them into a tightly-coupled representation layer. The enum value <em>is</em> the thing you're going to display, transmit, and compare externally. And that opens the door to a few major risks:</p>
<h3 id="heading-1-leaky-abstractions">1. Leaky Abstractions</h3>
<p>By using a string value like <code>"red"</code> directly in your enum, you’re tying your logic to that representation. Now every part of your app—your UI, backend, tests—might start to depend on that <code>"red"</code> string. If you later need to localize, change the display name, or serialize differently, you’re stuck.</p>
<p>Example:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">enum</span> TrafficLight {
  Red = <span class="hljs-string">"red"</span>,
  Yellow = <span class="hljs-string">"yellow"</span>,
  Green = <span class="hljs-string">"green"</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">renderLight</span>(<span class="hljs-params">color: TrafficLight</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-string">`&lt;div class="light-<span class="hljs-subst">${color}</span>"&gt;&lt;/div&gt;`</span>;
}
</code></pre>
<p>At first glance, this seems fine. But what happens when marketing wants the classes to change to <code>"stop"</code>, <code>"caution"</code>, and <code>"go"</code> instead of <code>"red"</code>, <code>"yellow"</code>, and <code>"green"</code>?</p>
<p>You're now forced to either:</p>
<ul>
<li><p>Rename your enum values (breaking everything),</p>
</li>
<li><p>Or inject a weird indirection layer to undo the coupling you introduced in the first place.</p>
</li>
</ul>
<p>You’ve tightly bound your <em>logic layer</em> and <em>representation layer</em>, which makes the system harder to evolve.</p>
<h3 id="heading-2-fragile-equality">2. Fragile Equality</h3>
<p>Using literal values opens up the door for subtle bugs due to implicit casting, loose comparisons, and typos.</p>
<p>For example:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">if</span> (color === <span class="hljs-string">"Red"</span>) {
  <span class="hljs-comment">// oops – this will never be true if your enum is "red" (lowercase)</span>
}
</code></pre>
<p>Or worse:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> userInput = req.body.color; <span class="hljs-comment">// from frontend form input</span>

<span class="hljs-keyword">if</span> (userInput === TrafficLight.Red) {
  <span class="hljs-comment">// works... until someone changes the frontend string casing or translation</span>
}
</code></pre>
<p>Now your enum value is effectively part of a contract—and not a very safe one. There's no type safety guarding the string being passed around. A typo or localization mismatch breaks your logic silently.</p>
<h3 id="heading-3-enum-values-become-public-api">3. Enum Values Become Public API</h3>
<p>Once you serialize your enums as strings or numbers and expose them (e.g. in JSON APIs), they become part of your public contract. Now you're stuck maintaining these exact values forever, even if they were never intended to be meaningful.</p>
<p>Imagine this:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Backend API</span>
{
  <span class="hljs-string">"status"</span>: <span class="hljs-string">"Processing"</span>
}
</code></pre>
<p>This might come from:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">enum</span> OrderStatus {
  Processing = <span class="hljs-string">"Processing"</span>,
  Shipped = <span class="hljs-string">"Shipped"</span>,
  Delivered = <span class="hljs-string">"Delivered"</span>,
}
</code></pre>
<p>Now <code>Processing</code> is in your API contract. What happens when you want to rename it to <code>InProgress</code>? You can't—at least not without a breaking change.</p>
<p>Instead, the enum should have stayed symbolic:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">enum</span> OrderStatus {
  Processing,
  Shipped,
  Delivered
}
</code></pre>
<p>And your serialization should happen in a dedicated mapping layer:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> statusToApi = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>&lt;OrderStatus, <span class="hljs-built_in">string</span>&gt;([
  [OrderStatus.Processing, <span class="hljs-string">"Processing"</span>],
  [OrderStatus.Shipped, <span class="hljs-string">"Shipped"</span>],
  [OrderStatus.Delivered, <span class="hljs-string">"Delivered"</span>],
]);

<span class="hljs-keyword">return</span> {
  status: statusToApi.get(currentStatus)
};
</code></pre>
<p>This lets you keep your internal logic clean and symbolic, while exposing only the necessary representation at the edge of your system.</p>
<hr />
<h2 id="heading-enums-should-be-for-internal-logic-only">Enums Should Be for Internal Logic Only</h2>
<p>When used correctly, enums are great for internal logic and branching:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">enum</span> TrafficLight {
  Red,
  Yellow,
  Green
}

<span class="hljs-keyword">const</span> currentLight: TrafficLight = TrafficLight.Red;

<span class="hljs-keyword">const</span> instructions = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>&lt;TrafficLight, <span class="hljs-built_in">string</span>&gt;([
  [TrafficLight.Red, <span class="hljs-string">"Stop"</span>],
  [TrafficLight.Yellow, <span class="hljs-string">"Slow down"</span>],
  [TrafficLight.Green, <span class="hljs-string">"Go"</span>],
]);

<span class="hljs-built_in">console</span>.log(instructions.get(currentLight));
</code></pre>
<p>Here, the enum values themselves are symbolic. They aren’t used directly in the UI, APIs, or persisted anywhere—they're just internal tags for logic flow. If you want to serialize the enum value or display it to a user, that should be an explicit translation step (like the <code>Map</code> above).</p>
<hr />
<h2 id="heading-the-problem-with-misunderstood-criticism">The Problem With Misunderstood Criticism</h2>
<p>I once saw someone critique TypeScript enums in a video, complaining that enums were “unusable” because they “don’t serialize cleanly.” But that’s like complaining that a hammer doesn’t write well. Of course it doesn’t—it wasn’t designed for that.</p>
<p>If you’re using enums as if they’re just constant strings or serialized values, then yes, they’ll bite you. But if you use them as symbolic representations of domain ideas, then they’re incredibly powerful—and semantically rich.</p>
<hr />
<h2 id="heading-should-you-use-typescript-enums">Should You Use TypeScript Enums?</h2>
<p>I’ll be honest: TypeScript enums <em>are</em> quirky. The <code>enum</code> keyword behaves differently than a lot of people expect, especially those coming from other languages. But abandoning them altogether is throwing the baby out with the bathwater.</p>
<p>The problem isn’t that TypeScript enums are bad. The problem is that we’ve forgotten what enums are for.</p>
<p>So let’s bring back proper enums—used symbolically, decoupled from implementation details, and grounded in good modeling.</p>
<p>Because sometimes, the best way to represent an idea… is with a symbol.</p>
]]></content:encoded></item><item><title><![CDATA[Sharing IDs Across Aggregates]]></title><description><![CDATA[In the world of Domain-Driven Design (DDD), certain principles often feel like ironclad rules—one of which is that every aggregate must have a unique identity. But what if I told you that sharing IDs across aggregates isn’t inherently wrong and, in f...]]></description><link>https://yazanalaboudi.dev/sharing-ids-across-aggregates</link><guid isPermaLink="true">https://yazanalaboudi.dev/sharing-ids-across-aggregates</guid><category><![CDATA[DDD]]></category><category><![CDATA[aggregate]]></category><category><![CDATA[identity-management]]></category><dc:creator><![CDATA[Yazan Alaboudi]]></dc:creator><pubDate>Mon, 31 Mar 2025 14:37:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743431809451/b2cbcf11-5586-483b-8690-95d8b2b5f2d3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the world of Domain-Driven Design (DDD), certain principles often feel like ironclad rules—one of which is that every aggregate must have a unique identity. But what if I told you that <strong>sharing IDs across aggregates isn’t inherently wrong</strong> and, in fact, can be a practical, scalable approach in many domains?</p>
<h2 id="heading-the-controversial-question">The Controversial Question</h2>
<p>When designing a system with multiple related aggregates—say, a <code>CustomerAggregate</code>, <code>OrderAggregate</code>, and <code>CustomerPreferencesAggregate</code>—the instinct is to assign each a unique ID. After all, isn’t each aggregate supposed to be an independent consistency boundary?</p>
<p>But what if these aggregates share the same ID, such as a customer ID? Would this violate the sacred principles of DDD, or could it be a pragmatic decision?</p>
<h2 id="heading-designing-aggregates-consistency-boundaries-not-relationships">Designing Aggregates: Consistency Boundaries, Not Relationships</h2>
<p>A common misconception when designing aggregates is to organize them around <strong>relationships, hierarchy, or real-world manifestations</strong> rather than <strong>consistency boundaries</strong>. Aggregates are about managing invariants—rules that must always hold true—not about representing relational data structures.</p>
<p>For example, consider a retail domain:</p>
<ul>
<li><p>An <code>OrderAggregate</code> enforces consistency around orders—items, payment status, and shipping details.</p>
</li>
<li><p>A <code>CustomerAggregate</code> manages identity and personal details, but not orders or preferences.</p>
</li>
<li><p>A <code>CustomerPreferencesAggregate</code> handles marketing subscriptions and notifications.</p>
</li>
</ul>
<p>Designing aggregates based on relationships often results in bloated objects and inconsistent states. Instead, aggregates should isolate rules and invariants, ensuring consistency within their boundary.</p>
<h2 id="heading-why-sharing-ids-can-be-ok">Why Sharing IDs Can Be OK</h2>
<p>Using the same ID across multiple aggregates is acceptable—<strong>if done correctly</strong>:</p>
<h3 id="heading-1-distinct-responsibilities">✅ 1. Distinct Responsibilities</h3>
<p>When aggregates encapsulate <strong>separate, independent behaviors</strong>, sharing an ID can make sense. For instance:</p>
<ul>
<li><p><code>CustomerAggregate</code> manages identity and personal information.</p>
</li>
<li><p><code>OrderAggregate</code> handles orders and purchases.</p>
</li>
<li><p><code>CustomerPreferencesAggregate</code> manages notification and marketing preferences.</p>
</li>
</ul>
<p>If each aggregate enforces its own rules, they can coexist peacefully, even with the same ID.</p>
<h3 id="heading-2-clear-consistency-boundaries">✅ 2. Clear Consistency Boundaries</h3>
<p>When consistency boundaries are <strong>truly independent</strong>, you can safely use shared IDs. For example, changes to <code>CustomerPreferencesAggregate</code> should never require an immediate update to <code>OrderAggregate</code>.</p>
<h3 id="heading-3-identity-vs-state-management">✅ 3. Identity vs. State Management</h3>
<p>An ID can represent a <strong>common identity</strong> without implying ownership. Each aggregate manages its own data and state, avoiding tight coupling.</p>
<h2 id="heading-expanded-example-a-food-delivery-domain">Expanded Example: A Food Delivery Domain</h2>
<p>Let’s consider a food delivery service with three aggregates:</p>
<ol>
<li><p><code>CustomerAggregate</code> tracks user identity and contact details.</p>
</li>
<li><p><code>OrderAggregate</code> handles order details, payment status, and delivery logistics.</p>
</li>
<li><p><code>LoyaltyPointsAggregate</code> manages customer rewards.</p>
</li>
</ol>
<pre><code class="lang-javascript"><span class="hljs-comment">// CustomerAggregate.js</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomerAggregate</span> </span>{
  <span class="hljs-keyword">constructor</span>(customerId, name, address) {
    <span class="hljs-built_in">this</span>.customerId = customerId;
    <span class="hljs-built_in">this</span>.name = name;
    <span class="hljs-built_in">this</span>.address = address;
  }

  updateAddress(newAddress) {
    <span class="hljs-built_in">this</span>.address = newAddress;
  }
}
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-comment">// OrderAggregate.js</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderAggregate</span> </span>{
  <span class="hljs-keyword">constructor</span>(customerId, orderId, items) {
    <span class="hljs-built_in">this</span>.customerId = customerId;
    <span class="hljs-built_in">this</span>.orderId = orderId;
    <span class="hljs-built_in">this</span>.items = items;
    <span class="hljs-built_in">this</span>.status = <span class="hljs-string">"Pending"</span>;
  }

  markAsDelivered() {
    <span class="hljs-built_in">this</span>.status = <span class="hljs-string">"Delivered"</span>;
  }
}
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-comment">// LoyaltyPointsAggregate.js</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LoyaltyPointsAggregate</span> </span>{
  <span class="hljs-keyword">constructor</span>(customerId) {
    <span class="hljs-built_in">this</span>.customerId = customerId;
    <span class="hljs-built_in">this</span>.points = <span class="hljs-number">0</span>;
  }

  addPoints(points) {
    <span class="hljs-built_in">this</span>.points += points;
  }
}
</code></pre>
<p>This approach aligns aggregates with consistency boundaries—each handles its own invariants. We can use the customer ID across all aggregates because their state and behavior remain independent.</p>
<h2 id="heading-when-its-a-problem">When It’s a Problem</h2>
<p>Sharing IDs becomes problematic if:</p>
<ul>
<li><p>Aggregates frequently rely on each other’s state.</p>
</li>
<li><p>Consistency across aggregates is required in a single transaction.</p>
</li>
<li><p>You’re modeling the same entity in different places.</p>
</li>
</ul>
<p>If these conditions exist, consider merging aggregates or using domain events to ensure consistency.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>In DDD, aggregates are about managing business rules, not reflecting real-world hierarchies. Sharing IDs isn’t heretical—it’s a pragmatic choice when aggregates have distinct responsibilities and consistency boundaries. Avoid dogmatic thinking; focus on designing aggregates around consistency, invariants, and behaviors. The goal is a <strong>model that fits the business problem</strong>, not one that rigidly follows patterns. Embrace trade-offs; DDD is about practical solutions, not absolute rules.</p>
<p>And remember—<strong>It's acceptable to share IDs across different aggregate types, but an individual aggregate instance must always have a unique identity within its own type.</strong> This ensures each instance maintains a unique identity and consistency boundary, reinforcing the core principles of DDD while remaining pragmatic.</p>
]]></content:encoded></item><item><title><![CDATA[Misusing the DRY Principle]]></title><description><![CDATA[I still remember the very first time when the DRY principle was preached to me. "Whenever you find yourself writing duplicate code, always remember DRY. Don't Repeat Yourself" they said. I can only imagine that the majority of us has received this in...]]></description><link>https://yazanalaboudi.dev/misusing-the-dry-principle</link><guid isPermaLink="true">https://yazanalaboudi.dev/misusing-the-dry-principle</guid><category><![CDATA[design principles]]></category><category><![CDATA[SOLID principles]]></category><dc:creator><![CDATA[Yazan Alaboudi]]></dc:creator><pubDate>Sat, 25 Dec 2021 21:50:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/5GKdpmo0wI8/upload/v1640468760609/8gBM1Sm4q.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I still remember the very first time when the DRY principle was preached to me. "Whenever you find yourself writing duplicate code, always remember DRY. <strong>D</strong>on't <strong>R</strong>epeat <strong>Y</strong>ourself" they said. I can only imagine that the majority of us has received this introduction to DRY and thinks the definition ends there. This definition can be true but it does not truly capture the spirit of the DRY principle. In this article, I want to talk about the idea of <strong>incidental duplication </strong> and its relationship with the DRY principle. This is an important advanced topic to discuss because it is a paradigm shift towards our view of duplications and abstractions. In addition, I want to change the notion that finding good abstractions is an art. I am convinced that it's a science and it can be mastered.</p>
<h2 id="heading-purpose-behind-dry">Purpose behind DRY</h2>
<p>Let's start the article by taking a second to talk about the motivation behind the DRY principle. When the author's of "The Programmatic Programmer" introduced the concept, the following was used to describe the DRY principle:</p>
<blockquote>
<p>Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.</p>
</blockquote>
<p>That's it! It's short, succinct and to the point. So how do people use this simple principle incorrectly? An innocent mistake we generally commit is thinking that the objective of this principle is to minimize the overall lines of code. <strong>This is false.</strong> If you noticed, this definition does not emphasize on code duplication at all. It just states that any rule or information about your business/app is deserving of an abstraction (i.e. function) and should have a single reference point in your application. These rules would be written once but potentially referenced multiple times. By abiding by this principle, you will also naturally notice a decrease in duplications in the code most times.</p>
<h2 id="heading-incidental-duplication">Incidental Duplication</h2>
<p>So when we mention the term "incidental duplication" we are referring to duplication that has occurred in different places by mere chance (or "incidentally" if you will). It is code that looks the same but represents different behaviours/fact/knowledge in the system. DRY is generally misapplied when incidentally duplicated code is abstracted away. This tends to deliver negative outcomes in our system which we colloquially refer to as spaghetti code.</p>
<h3 id="heading-example-the-what-not-to-do">Example: The What Not To Do</h3>
<p>Let's take a look at a practical example to illustrate how abstracting incidental duplication minimizes lines of code but also contributes to worse code quality. The following is hypothetical code that could possibly live in a restaurant's codebase. Let's imagine a restaurant serves pizzas and sandwiches.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> makePizza = <span class="hljs-function">() =&gt;</span> {
  grateCheese();
  sliceTomatoes();
  sliceOnions();
  sliceBellPeppers();
  sliceOlives();
  putIngredientsOnDough();
  bakeDough();
}

<span class="hljs-keyword">const</span> makeSandwich = <span class="hljs-function">() =&gt;</span> {
  grateCheese();
  sliceTomatoes();
  sliceOnions();
  sliceBellPeppers();
  squirtMayonnaise();
  putIngredientsBetweenBread();
  toastBread();
}
</code></pre>
<p>At first glance, you will notice some commonality between making a pizza and making a sandwich. They both have similar ingredients such as cheese, tomatoes, onions, and bell peppers. One might be tempted to abstract this commonality for the sole purpose of reducing the number of lines of code. </p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> prepareIngredients = <span class="hljs-function">() =&gt;</span> {
  grateCheese();
  sliceTomatoes();
  sliceOnions();
  sliceBellPeppers();
}

<span class="hljs-keyword">const</span> makePizza = <span class="hljs-function">() =&gt;</span> {
  prepareIngredients();
  sliceOlives();
  putIngredientsOnDough();
  bakeDough();
}

<span class="hljs-keyword">const</span> makeSandwich = <span class="hljs-function">() =&gt;</span> {
  prepareIngredients();
  squirtMayonnaise();
  putIngredientsBetweenBread();
}
</code></pre>
<p>The above refactoring has indeed reduced duplication in the code but there are a set of questions that we should ask ourselves. Is code now better? What does <code>prepareIngredients</code> represent? Is this DRY? Are there any new benefits gained from this? Have you seen code in your code base that has undergone this sort of "refactoring"?</p>
<p>Let's take a second to assess the <code>prepareIngredients</code> function and ask ourselves "what knowledge is being captured by this abstraction". It doesn't represent how to make a sandwich. It doesn't represent how to make a pizza. Instead, it represents the overlapping ingredients between how to make a pizza and a sandwich. If you think critically, you may come to realize that this information is most likely not significant to any person in the business and carries no real semantic value. Although truthful, its is considered to be arbitrary information. To put it in terms used by the DRY authors, it is not an authoritative representation of any significant piece of knowledge.</p>
<p>If you do not see the issue so far, consider what would happen if we introduce another item on the menu that also has overlaps with the sandwich and the pizza. Let's assume we need to introduce a burger to the menu and that a burger is simply tomatoes, cheese, a beef patty in a bun. If you are adamant about maintaining the <code>prepareIngredients</code> function, you would like experience the following updates.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> prepareIngredients = <span class="hljs-function">() =&gt;</span> {
  grateCheese();
  sliceTomatoes();
}

<span class="hljs-keyword">const</span> makeBurger = <span class="hljs-function">() =&gt;</span> {
  prepareIngredients();
  cookBeefPatty();
  putInbuns();
}

<span class="hljs-keyword">const</span> makePizza = <span class="hljs-function">() =&gt;</span> {
  prepareIngredients();
  sliceOnions();
  sliceBellPeppers(); 
  sliceOlives();
  putIngredientsOnDough();
  bakeDough();
}

<span class="hljs-keyword">const</span> makeSandwich = <span class="hljs-function">() =&gt;</span> {
  prepareIngredients();
  sliceOnions();
  sliceBellPeppers();
  squirtMayonnaise();
  putIngredientsBetweenBread();
}
</code></pre>
<p>The <code>prepareIngredients</code> function has been minimized to only capture the overlap between all three menu items. In addition <code>makeSandwich</code> and <code>makePizza</code> has been  updated to compensate for the changes done in <code>prepareIngredients</code> since it's a dependency to both these functions. To put it in other words, introducing a new item in the menu, which seemingly has nothing to do with the current items, will cause more updates in the code base due to this wrongful abstractions. Although this is a simple example, it implicitly is becoming a little harder to understand what ingredients are involved in a menu item.</p>
<h3 id="heading-the-fix">The Fix</h3>
<p>Although there is less code written in our example above, the quality of our code is clearly worse. Rather than do all this, you could have simply kept your code as follows:</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">const</span> makeBurger = <span class="hljs-function">() =&gt;</span> {
  grateCheese();
  sliceTomatoes();
  cookBeefPatty();
  putInbuns();
}

<span class="hljs-keyword">const</span> makePizza = <span class="hljs-function">() =&gt;</span> {
  grateCheese();
  sliceTomatoes();
  sliceOnions();
  sliceBellPeppers(); 
  sliceOlives();
  putIngredientsOnDough();
  bakeDough();
}

<span class="hljs-keyword">const</span> makeSandwich = <span class="hljs-function">() =&gt;</span> {
  grateCheese();
  sliceTomatoes();
  sliceOnions();
  sliceBellPeppers();
  squirtMayonnaise();
  putIngredientsBetweenBread();
}
</code></pre>
<p>Although longer, this code is linearly simple to understand, involves one less function, and has no interdependencies. This funnily just means that it was correct and better to have done nothing in the first place. There was not issue to begin with.</p>
<h2 id="heading-last-words">Last words</h2>
<p>The examples I have written above are meant to easily demonstrate that aiming for less lines of code does not necessarily contribute to an increase in code quality. Unfortunately, I have seen signs of this misunderstanding in numerous code bases that I have been involved in. I would also be ballsy enough to say that I think all instances of what we call "spaghetti code" includes a similar misunderstanding of the DRY principle to that included in this article. If I can leave you with 1 simple message, I would leave you with this: Before creating a function, always ask yourself whether or not you are capturing <strong>knowledge</strong> that the business and other developers are interested in. If not, remove the abstraction!</p>
]]></content:encoded></item><item><title><![CDATA[Minimizing Integration Tests Through Dependency Injection]]></title><description><![CDATA[Introduction
Based on how an application has been put together, it is entirely possible for a developer to find themself in an unfortunate position where creating isolated unit tests is not frequently possible. As a result, they would resort to more ...]]></description><link>https://yazanalaboudi.dev/minimizing-integration-tests-through-dependency-injection</link><guid isPermaLink="true">https://yazanalaboudi.dev/minimizing-integration-tests-through-dependency-injection</guid><category><![CDATA[unit testing]]></category><category><![CDATA[dependency injection]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[software architecture]]></category><dc:creator><![CDATA[Yazan Alaboudi]]></dc:creator><pubDate>Mon, 01 Mar 2021 19:30:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1614476797711/FndMJtgZZ.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Based on how an application has been put together, it is entirely possible for a developer to find themself in an unfortunate position where creating isolated unit tests is not frequently possible. As a result, they would resort to more integration tests and utilize heavy mocks and patches to assert correct functionality. In my experience, I have come to realize that this situation is a result of less than optimal code design. In this article, I would like to present how one can utilize dependency injection to shift from relying on integration tests to more isolated unit tests instead. We will go through an example, written in typescript, that starts off in the situation I have described that would then be refactored into a more modular piece of software that is easily testable.</p>
<h2 id="example">Example</h2>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { average } <span class="hljs-keyword">from</span> <span class="hljs-string">"./utils"</span>;
<span class="hljs-keyword">import</span> examHttpRepository <span class="hljs-keyword">from</span> <span class="hljs-string">"./student-repository"</span>;

<span class="hljs-keyword">const</span> calculateAverageGrade = <span class="hljs-function">(<span class="hljs-params">exams: Exam[]</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> grades = exams.map(<span class="hljs-function"><span class="hljs-params">exam</span> =&gt;</span> exam.grade);
  <span class="hljs-keyword">return</span> average(grades);
}

<span class="hljs-keyword">const</span> getStudentAverageGrade = <span class="hljs-keyword">async</span> (studentId: <span class="hljs-built_in">string</span>) =&gt; {
  <span class="hljs-comment">// This will get exams via an HTTP call. This makes our function impure</span>
  <span class="hljs-keyword">const</span> exams = <span class="hljs-keyword">await</span> examHttpRepository.getByStudentId(studentId);
  <span class="hljs-keyword">return</span> calculateAverageGrade(exams);
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> getStudentAverageGrade
</code></pre>
<p>Let's focus on the <code>getStudentAverageGrade</code> function in the code snippet above. The function is quite simple and has an intuitive signature. It takes in a student ID and returns the student's average grade. The implementation uses a repository to retrieve an array of exams which is then transformed into the student's average grade. Easy peasy. This type of code is rather typical in most JavaScript projects.</p>
<p>Although common, this pattern is not necessarily the best and may present challenges in maintaining a modular and testable codebase. In particular, the presence of the <code>examHttpRepository</code> inside <code>getStudentAverageGrade</code> introduces side-effects and a form of coupling within <code>getStudentAverageGrade</code>. Let's demonstrate these difficulties by writing out what this current function's test would look like.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> nock <span class="hljs-keyword">from</span> <span class="hljs-string">"nock"</span>;
<span class="hljs-keyword">import</span> getStudentAverageGrade <span class="hljs-keyword">from</span> <span class="hljs-string">"./get-student-average-grade"</span>;

describe(<span class="hljs-string">"getStudentAverageGrade"</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">"should get a student's average grade"</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> mockExams = [
      { name: <span class="hljs-string">'math'</span>, grade: <span class="hljs-number">90</span> },
      { name: <span class="hljs-string">'history'</span>, grade: <span class="hljs-number">80</span> }
    ];

    <span class="hljs-comment">// intercept HTTP request to to endpoint and return mock</span>
    nock(<span class="hljs-string">'https://api.mysite.com'</span>)
      .get(<span class="hljs-string">'/exams'</span>)
      .query({ studentid: <span class="hljs-string">'abc'</span> })
      .reply(<span class="hljs-number">200</span>, { exams: mockExams });

    <span class="hljs-keyword">const</span> averageGrade = <span class="hljs-keyword">await</span> getStudentAverageGrade(<span class="hljs-string">'abc'</span>);
    expect(averageGrade).toBe(<span class="hljs-number">85</span>);
  });
});
</code></pre>
<p>This test by definition is an integration test since <code>getStudentAverageGrade</code> has an <strong>integration dependency with the execution environment</strong>. We had to override Node's HTTP functionality to return our mock list of exams to accommodate <code>examHttpRepository.getByStudentId(:studentID)</code> in this test.</p>
<p>At this point, you might be fairly asking yourself "Meh, this works. What's the big deal with having this type of testing?". The biggest disadvantage is that the test is slightly implementation aware. It knows that it must provide a certain environment due to the underlying HTTP implementation detail. If we decided to change the protocol or the location of the exam resource, the test must be changed accordingly as well. In other words, our test would be coupled to the "how" rather than the "what" of the function.</p>
<h2 id="dependency-injection-to-the-rescue">Dependency Injection To The Rescue</h2>
<p>This is where dependency injection comes into play. Rather than explicitly coupling the <code>getStudentAverageGrade</code> function to the <code>examHttpRepository</code> directly, we can instead expect the repository to be "injected" instead. This is just a fancy way of saying that the repository will be passed in as a parameter making the <code>getStudentAverageGrade</code> function coupled to the interface of the <code>ExamRepository</code> rather than the concretion. Take a look at the following amendments.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { average } <span class="hljs-keyword">from</span> <span class="hljs-string">"./utils"</span>;
<span class="hljs-keyword">import</span> { IExamRepository } <span class="hljs-keyword">from</span> <span class="hljs-string">"./student-repository"</span>;

<span class="hljs-keyword">const</span> calculateAverageGrade = <span class="hljs-function">(<span class="hljs-params">exams: Exam[]</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> grades = exams.map(<span class="hljs-function"><span class="hljs-params">exam</span> =&gt;</span> exam.grade);
  <span class="hljs-keyword">return</span> average(grades);
}

<span class="hljs-keyword">const</span> getStudentAverageGrade = <span class="hljs-function">(<span class="hljs-params">examRepository: IExamRepository, studentId: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> exams = examRepository.getByStudentId(studentId);
  <span class="hljs-keyword">return</span> calculateAverageGrade(exams);
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> getStudentAverageGrade
</code></pre>
<p>Although this is a minor change to the previous implementation, it presents huge advantages to the modularity and testability of the <code>getStudentAverageGrade</code> function. </p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> getStudentAverageGrade <span class="hljs-keyword">from</span> <span class="hljs-string">"./get-student-average-grade"</span>;

describe(<span class="hljs-string">"getStudentAverageGrade"</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">"should get a student's average grade"</span>, <span class="hljs-keyword">async</span> () =&gt; { 
    <span class="hljs-keyword">const</span> mockExams = [
      { name: <span class="hljs-string">'math'</span>, grade: <span class="hljs-number">90</span> },
      { name: <span class="hljs-string">'history'</span>, grade: <span class="hljs-number">80</span> }
    ];

    <span class="hljs-keyword">const</span> mockExamRepo: IExamRepository = {
      getByStudentId: <span class="hljs-function">(<span class="hljs-params">id: <span class="hljs-built_in">string</span></span>) =&gt;</span> <span class="hljs-built_in">Promise</span>.resolve(mockExams)
    }

    <span class="hljs-keyword">const</span> averageGrade = <span class="hljs-keyword">await</span> getStudentAverageGrade(mockExamRepo, <span class="hljs-string">'abc'</span>);
    expect(averageGrade).toBe(<span class="hljs-number">85</span>);
  });
});
</code></pre>
<p>This test is no longer coupled to the <code>getStudentAverageGrade</code> nor is it implementation aware anymore. This enables it to be driven by the function's public API and be explicitly converted into a fully isolated unit test. As for the point on modularity, <code>getStudentAverageGrade</code> is now able to use alternative variants of the exam repository that abide by the <code>IExamRepository</code> interface and not care about the implementation details behind those variants. It shouldn't matter if the repository uses HTTP, web sockets, or the browser's local storage. The test would still pass.</p>
<p>You can take this example one step further. I personally would like to preserve the initial intuitive signature of <code>getStudentAverageGrade</code> such that it still takes in a student ID <strong>only</strong> and returns back the average grade. We can do so by creating a higher-order function, a factory, that would return our desired signature. </p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { average } <span class="hljs-keyword">from</span> <span class="hljs-string">"./utils"</span>;
<span class="hljs-keyword">import</span> { IExamRepository, examRepositoryHttp } <span class="hljs-keyword">from</span> <span class="hljs-string">"./student-repository"</span>;

<span class="hljs-keyword">interface</span> IGetStudentAverageGrade {
  (studentID: <span class="hljs-built_in">string</span>) =&gt; <span class="hljs-built_in">number</span>;
}

<span class="hljs-keyword">const</span> calculateAverageGrade = <span class="hljs-function">(<span class="hljs-params">exams: Exam[]</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> grades = exams.map(<span class="hljs-function"><span class="hljs-params">exam</span> =&gt;</span> exam.grade);
  <span class="hljs-keyword">return</span> average(grades);
}

<span class="hljs-comment">// This is the higher-order function</span>
<span class="hljs-keyword">const</span> makeGetStudentAverageGrade = <span class="hljs-function">(<span class="hljs-params">examRepository: IExamRepository</span>) =&gt;</span> (
  studentId: <span class="hljs-built_in">string</span>
) =&gt; {
  <span class="hljs-keyword">const</span> exams = examRepository.getByStudentId(studentId);
  <span class="hljs-keyword">return</span> calculateAverageGrade(exams);
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> makeGetStudentAverageGrade

<span class="hljs-comment">// Generally the injection would occur in a separate place. for example:</span>
<span class="hljs-comment">// const getStudentAverageGrade = makeGetStudentAverageGrade(examRepositoryHttp);</span>
</code></pre>
<p>With this setup, you are able to inject the variant of the <code>examRepository</code> that you need only once upon booting the application. This factory method would return a version of the <code>getStudentAverageGrade</code> function with the chosen repository variant preloaded in. We have preserved the simple contract that we started off with, <code>(studentId: string) =&gt; number</code>, increased code modularity, and simplified testing as you will see below.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> makeGetStudentAverageGrade <span class="hljs-keyword">from</span> <span class="hljs-string">"./get-student-average-grade"</span>;

describe(<span class="hljs-string">"getStudentAverageGrade"</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">"should get a student's average grade"</span>, <span class="hljs-keyword">async</span> () =&gt; { 
    <span class="hljs-keyword">const</span> mockExams = [
      { name: <span class="hljs-string">'math'</span>, grade: <span class="hljs-number">90</span> },
      { name: <span class="hljs-string">'history'</span>, grade: <span class="hljs-number">80</span> }
    ];

    <span class="hljs-keyword">const</span> mockExamRepo: IExamRepository = {
      getByStudentId: <span class="hljs-function">(<span class="hljs-params">id: <span class="hljs-built_in">string</span></span>) =&gt;</span> <span class="hljs-built_in">Promise</span>.resolve(mockExams)
    }

    <span class="hljs-keyword">const</span> getStudentAverageGrade = makeGetStudentAverageGrade(mockExamRepo);
    <span class="hljs-keyword">const</span> averageGrade = <span class="hljs-keyword">await</span> getStudentAverageGrade(<span class="hljs-string">'abc'</span>);
    expect(averageGrade).toBe(<span class="hljs-number">85</span>);
  });
});
</code></pre>
<p>Pretty neat!</p>
<h2 id="are-integration-tests-necessary">Are Integration Tests Necessary?</h2>
<p>In short, yes. Integration tests definitely provide value to the code base. But if we take a look at the example we went through, an integration test is not necessary to test the <code>getStudentAverageGrade</code> function. Thinking critically about this, it's only our HTTP variant of the exam repository, not the mock variant, that produces a side-effect. It's only this variant that should be tested with integration test techniques. This means that integration test coverages can be minimized to the smallest side-effect producing units within the application.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I hope this example was able to demonstrate that it is highly possible to reduce the number of integration tests by amending your application's architecture and design. By promoting dependency injection, you enable your codebase to be more modular which inherently increases the number of unit tests as well. There is nothing wrong with having integration tests in your application as they are still valuable. But a high number of them may be an indicator that an improvement could be made to the software.</p>
]]></content:encoded></item></channel></rss>