<?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" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ #Domain-Driven-Design - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ #Domain-Driven-Design - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 08 Oct 2026 03:33:38 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/domain-driven-design/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ Feature Modularization in Flutter: How to Combine Clean Architecture and Domain-Driven Design for Self-Contained, Scalable Features ]]>
                </title>
                <description>
                    <![CDATA[ As an engineer working on a small team, your current structure could fly. But what if your team, all contributing to the same codebase, reaches 20 or more people at scale? You'll need careful design t ]]>
                </description>
                <link>https://www.freecodecamp.org/news/feature-modularization-in-flutter-combine-clean-architecture-and-domain-driven-design/</link>
                <guid isPermaLink="false">6a9754cacade799374a0231b</guid>
                
                    <category>
                        <![CDATA[ engineering-management ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ DDD ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #Domain-Driven-Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Clean Architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ clean code ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oluwaseyi Fatunmole ]]>
                </dc:creator>
                <pubDate>Tue, 01 Sep 2026 22:42:18 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/9dac98f7-f9bf-4597-bbd9-95e220cce9ec.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>As an engineer working on a small team, your current structure could fly. But what if your team, all contributing to the same codebase, reaches 20 or more people at scale? You'll need careful design thinking, seamless contribution workflows, and processes for keeping the codebase concise and compact.</p>
<p>You'll also need to be quite intentional in selecting your folder structure, architecture, and the patterns you use in your projects.</p>
<p>Most Flutter apps start the same way. In the beginning, you have a lib folder, a few screens, maybe a models directory, and a services file that handles everything. It works, the app ships, and everyone is happy.</p>
<p>Then the app grows. New features come in. The team expands. What was once a manageable codebase becomes a maze. Changing one thing breaks another. Nobody is sure where business logic actually lives. The models folder has three hundred files. The services file is six thousand lines long. New engineers take weeks to understand where to add anything.</p>
<p>This isn't a discipline problem. It's a structure problem. The app was never organized in a way that could absorb growth without collapsing.</p>
<p>Feature Modularization is the organizational strategy that prevents this collapse. It's not a new framework, and it's not a replacement for Clean Architecture or Domain-Driven Design. It takes the principles of both and applies them feature by feature, so that every piece of your application is self-contained, independently testable, and scalable without interference.</p>
<p>In this article, we'll cover the current issues you might encounter using the layer first pattern, how Domain-Driven Design actually works, some business rules with value objects, what Clean Architecture means, and how they all come together in feature modularization.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-what-feature-modularization-actually-is">What Feature Modularization Actually Is</a></p>
</li>
<li><p><a href="#heading-the-problem-with-layer-first-organization">The Problem With Layer-First Organization</a></p>
</li>
<li><p><a href="#heading-the-building-blocks-clean-architecture-meets-ddd">The Building Blocks: Clean Architecture Meets DDD</a></p>
</li>
<li><p><a href="#heading-entities-value-objects-and-dtos-in-a-modular-feature">Entities, Value Objects, and DTOs in a Modular Feature</a></p>
</li>
<li><p><a href="#heading-business-rules-and-where-they-live">Business Rules and Where They Live</a></p>
</li>
<li><p><a href="#heading-exception-handling-and-safe-state-propagation">Exception Handling and Safe State Propagation</a></p>
</li>
<li><p><a href="#heading-the-folder-structure">The Folder Structure</a></p>
</li>
<li><p><a href="#heading-real-world-example-one-the-employee-feature">Real World Example One: The Employee Feature</a></p>
</li>
<li><p><a href="#heading-real-world-example-two-the-payment-feature">Real World Example Two: The Payment Feature</a></p>
</li>
<li><p><a href="#heading-cross-feature-communication">Cross-Feature Communication</a></p>
</li>
<li><p><a href="#heading-patterns-that-enhance-modularization">Patterns That Enhance Modularization</a></p>
</li>
<li><p><a href="#heading-scaling-to-large-teams">Scaling to Large Teams</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before reading this article, you should be comfortable with:</p>
<ul>
<li><p>Building Flutter applications in Dart</p>
</li>
<li><p>What object-oriented programming is: classes, inheritance, interfaces</p>
</li>
<li><p>What layers mean in software architecture: separating UI from business logic from data access</p>
</li>
<li><p>Async programming in Dart: Futures and async/await</p>
</li>
</ul>
<p>You don't need prior experience with Clean Architecture or Domain-Driven Design. This article introduces the relevant concepts from both as they come up.</p>
<h2 id="heading-what-feature-modularization-actually-is">What Feature Modularization Actually Is</h2>
<p>Feature Modularization is the practice of organizing your application around features, not around technical layers.</p>
<p>Every feature in your application contains everything it needs to function: its domain model, business rules, data access, and UI. The feature is a vertical slice through the entire application stack. It doesn't depend on other features to function. It doesn't expose its internals to other features. It stands alone.</p>
<p>This is fundamentally different from how most apps are initially organized, where all the domain code lives in one folder, all the data access lives in another folder, and all the UI lives in a third folder, regardless of which feature that code belongs to.</p>
<p>Feature Modularization isn't Clean Architecture. Clean Architecture defines what the layers are and the rules that govern how they interact. It tells you that the domain layer can't depend on the infrastructure layer, that business rules can't know about the UI, and that data flows inward through defined boundaries.</p>
<p>Feature Modularization isn't Domain-Driven Design either. DDD gives you the vocabulary for modeling complex business domains. It introduces entities, value objects, aggregates, domain services, and bounded contexts as tools for capturing business complexity in code.</p>
<p>Feature Modularization takes both of these and combines them. It uses Clean Architecture's layering rules and DDD's modeling vocabulary, and it applies them at the feature level. Each feature gets its own domain layer, its own application layer, its own infrastructure layer, and its own presentation layer. The rules from Clean Architecture govern how those layers interact within the feature. The concepts from DDD govern how the domain model is structured inside the domain layer.</p>
<p>The result is a codebase that can grow indefinitely without any single part of it becoming too large to understand or modify safely.</p>
<h2 id="heading-the-problem-with-layer-first-organization">The Problem With Layer-First Organization</h2>
<p>To understand why Feature Modularization matters, you need to see what the alternative looks like at scale.</p>
<p>In a layer-first organization, the folder structure groups code by technical role:</p>
<pre><code class="language-plaintext">lib/
  domain/
    employee.dart
    payment.dart
    product.dart
    notification.dart
    user.dart
    ... 47 more files

  application/
    get_employee_usecase.dart
    process_payment_usecase.dart
    get_products_usecase.dart
    send_notification_usecase.dart
    ... 83 more use cases

  infrastructure/
    employee_repository_impl.dart
    payment_repository_impl.dart
    product_datasource.dart
    notification_service_impl.dart
    ... 91 more implementations

  presentation/
    employee_page.dart
    payment_page.dart
    product_list_page.dart
    ... 200 more screens and widgets
</code></pre>
<p>This works fine at small scale. At medium scale it starts to show cracks. At large scale it becomes genuinely painful.</p>
<p>When a developer needs to modify the Employee feature, they touch files in four separate top-level folders. A change to the Employee entity in the domain folder requires navigating to the application folder for the use case, then the infrastructure folder for the repository implementation, and then the presentation folder for the UI. These folders aren't next to each other. They're separated by all the other features sharing those folders.</p>
<p>Understanding any single feature requires mentally assembling it from pieces scattered across the entire codebase. Onboarding a new engineer to the Employee feature means showing them four different locations before they can see the complete picture.</p>
<p>Testing the Employee feature in isolation is difficult because its pieces aren't isolated. They share folders and sometimes share dependencies with other features in ways that make it hard to draw a clean boundary.</p>
<p>Feature Modularization solves this by keeping everything that belongs to a feature together.</p>
<h2 id="heading-the-building-blocks-clean-architecture-meets-ddd">The Building Blocks: Clean Architecture Meets DDD</h2>
<p>Before looking at the folder structure and code, you need to understand what Clean Architecture and DDD each contribute to Feature Modularization and why both are necessary.</p>
<h3 id="heading-what-clean-architecture-contributes">What Clean Architecture Contributes</h3>
<p>Clean Architecture organizes code into layers with a strict dependency rule: inner layers never depend on outer layers. The domain is the innermost layer. The application layer wraps the domain. The infrastructure layer is outermost.</p>
<p>In a Flutter application, this means a few things:</p>
<p>The <strong>domain layer</strong> contains entities, value objects, and repository interfaces. It has zero dependencies on Flutter, on HTTP libraries, on local databases, or on any external package. It's pure Dart, and it's the business logic of the feature, completely isolated from how the app is built or deployed.</p>
<p>The <strong>application layer</strong> contains use cases. A use case orchestrates domain objects to accomplish a specific business task. It depends only on the domain layer. It knows about entities and repositories but doesn't know about HTTP or SQLite or Riverpod.</p>
<p>The <strong>infrastructure layer</strong> contains the concrete implementations of repository interfaces defined in the domain. It knows about HTTP clients, local databases, and external services. It depends on the domain layer interfaces but the domain layer never depends on it.</p>
<p>The <strong>presentation layer</strong> contains the UI: widgets, notifiers, and state. It depends on the application layer through use cases. It reacts to state and has no business logic.</p>
<p>This dependency direction ensures that business rules are never corrupted by infrastructure details. Changing your HTTP client from Dio to http doesn't require touching a single domain entity. Changing your state management from Riverpod to BLoC doesn't require touching a single use case.</p>
<h3 id="heading-what-domain-driven-design-contributes">What Domain-Driven Design Contributes</h3>
<p>DDD provides the vocabulary for modeling the domain layer properly.</p>
<p>An <strong>Entity</strong> is a domain object with identity. It has an ID that distinguishes it from other entities of the same type. An Employee entity is identified by its employee ID. Two employees with the same name are still different entities because they have different IDs. Entities can have state that changes over time, and they enforce rules about how that state can change.</p>
<p>A <strong>Value Object</strong> wraps a single piece of data and enforces its intrinsic validity. An EmployeeId is a Value Object, as is an email address or monetary amount. Value Objects are immutable. If the data is invalid, the Value Object throws an exception at construction time, before invalid data can enter the domain.</p>
<p>A <strong>Domain Service</strong> handles business logic that doesn't naturally belong to a single entity. If a rule spans multiple entities or requires coordination between entities in ways that don't fit on any single entity, that logic belongs in a Domain Service.</p>
<p>A <strong>Repository</strong> is an interface defined in the domain layer that describes how to persist and retrieve domain objects. The domain knows what operations it needs. The infrastructure layer provides the concrete implementation. The domain never knows which database or API is behind the repository.</p>
<h2 id="heading-entities-value-objects-and-dtos-in-a-modular-feature">Entities, Value Objects, and DTOs in a Modular Feature</h2>
<p>Understanding the distinction between these three types is fundamental to feature modularization. Getting this wrong leads to business rules leaking into the wrong layers.</p>
<h3 id="heading-value-objects">Value Objects</h3>
<p>A Value Object wraps a single piece of data and enforces that the data is always valid. It throws immediately when invalid data is provided. This means invalid data can never exist inside your domain.</p>
<pre><code class="language-dart">class EmployeeId {
  final String value;

  EmployeeId(this.value) {
    if (value.isEmpty) {
      throw DomainException('Employee ID cannot be empty');
    }
    if (value.length &lt; 4) {
      throw DomainException('Employee ID must be at least 4 characters');
    }
  }

  @override
  bool operator ==(Object other) =&gt;
      other is EmployeeId &amp;&amp; other.value == value;

  @override
  int get hashCode =&gt; value.hashCode;

  @override
  String toString() =&gt; value;
}
</code></pre>
<pre><code class="language-dart">class Money {
  final double amount;
  final String currency;

  Money({required this.amount, required this.currency}) {
    if (amount &lt; 0) {
      throw DomainException('Amount cannot be negative');
    }
    if (currency.isEmpty) {
      throw DomainException('Currency cannot be empty');
    }
  }

  Money add(Money other) {
    if (currency != other.currency) {
      throw DomainException('Cannot add different currencies');
    }
    return Money(amount: amount + other.amount, currency: currency);
  }

  @override
  String toString() =&gt; '$currency ${amount.toStringAsFixed(2)}';
}
</code></pre>
<p>Value Objects are immutable. You never modify a Value Object. Instead, you create a new one. <code>EmployeeId</code> and <code>Money</code> above both validate at construction. No invalid <code>EmployeeId</code> can ever exist anywhere in your domain. No negative <code>Money</code> can ever exist. The guard is built into the type itself.</p>
<h3 id="heading-entities">Entities</h3>
<p>An Entity is a domain object with an identity and rules about how its state can change.</p>
<pre><code class="language-dart">class Employee {
  final EmployeeId id;
  final String name;
  DateTime? clockInTime;
  DateTime? clockOutTime;

  Employee({
    required this.id,
    required this.name,
  });

  void clockIn(DateTime time) {
    if (clockInTime != null &amp;&amp; clockOutTime == null) {
      throw DomainException('Cannot clock in: already clocked in');
    }
    clockInTime = time;
    clockOutTime = null;
  }

  void clockOut(DateTime time) {
    if (clockInTime == null) {
      throw DomainException('Cannot clock out: not clocked in');
    }
    if (time.isBefore(clockInTime!)) {
      throw DomainException('Clock out time cannot be before clock in time');
    }
    clockOutTime = time;
  }

  bool get isClockedIn =&gt; clockInTime != null &amp;&amp; clockOutTime == null;

  Duration? get hoursWorked {
    if (clockInTime == null || clockOutTime == null) return null;
    return clockOutTime!.difference(clockInTime!);
  }
}
</code></pre>
<p>The Entity owns its state transitions. <code>clockIn</code> and <code>clockOut</code> aren't just setters. They enforce business rules. An employee can't clock in twice without clocking out, just like they can't clock out before they've clocked in. These rules live on the Entity where they belong, not in a use case, widget, or repository.</p>
<h3 id="heading-dtos">DTOs</h3>
<p>A Data Transfer Object carries raw data from an external source: an API response, database row, or JSON payload. DTOs have no business rules or behavior. They exist only to transport data across a boundary.</p>
<pre><code class="language-dart">class EmployeeDTO {
  final String id;
  final String name;
  final String? clockInTime;
  final String? clockOutTime;

  const EmployeeDTO({
    required this.id,
    required this.name,
    this.clockInTime,
    this.clockOutTime,
  });

  factory EmployeeDTO.fromJson(Map&lt;String, dynamic&gt; json) {
    return EmployeeDTO(
      id: json['id'] as String,
      name: json['name'] as String,
      clockInTime: json['clock_in_time'] as String?,
      clockOutTime: json['clock_out_time'] as String?,
    );
  }

  Employee toDomain() {
    final employee = Employee(
      id: EmployeeId(id),
      name: name,
    );

    if (clockInTime != null) {
      employee.clockIn(DateTime.parse(clockInTime!));
    }
    if (clockOutTime != null) {
      employee.clockOut(DateTime.parse(clockOutTime!));
    }

    return employee;
  }
}
</code></pre>
<p>The DTO lives in the infrastructure layer. It knows about JSON, while the domain entity knows nothing about JSON. The <code>toDomain()</code> method converts raw data into a properly constructed domain object, firing the business rules along the way.</p>
<h2 id="heading-business-rules-and-where-they-live">Business Rules and Where They Live</h2>
<p>Every business rule in a modular feature lives in the domain layer. This isn't a preference. It's the architectural contract that makes the entire approach work.</p>
<p><strong>Value Objects</strong> enforce atomic, intrinsic rules. An email address must contain an @ symbol. A monetary amount can't be negative. An employee ID can't be empty. These rules are about the data itself, and they belong on the Value Object.</p>
<p><strong>Entities</strong> enforce stateful rules. An employee can't clock in twice. A payment can't be processed if it has already been refunded. A task can't be completed if its dependencies aren't complete. These rules involve the state of the entity changing over time. They belong on the Entity.</p>
<p><strong>Domain Services</strong> enforce rules that span multiple entities or require coordination. An employee can't approve their own leave request. A payment requires verification from both the payer and a fraud detection check. These rules involve multiple entities and don't naturally belong on any single one. They belong in a Domain Service.</p>
<p>The rule is simple and absolute: if something is a business rule, it lives in the domain layer. The application layer calls the domain. The infrastructure layer implements the domain's interfaces. The presentation layer reacts to the results. None of them define or modify business rules.</p>
<p>This boundary is what makes the system trustworthy. Business rules can't be bypassed by going directly to the database. They can't be skipped by calling a repository method directly from a widget. They're always enforced because the only way to change domain state is through the domain objects themselves.</p>
<h2 id="heading-exception-handling-and-safe-state-propagation">Exception Handling and Safe State Propagation</h2>
<p>When a business rule is violated in the domain layer, the domain throws a <code>DomainException</code>. This exception travels up through the application layer to the presentation layer where it's converted into UI state.</p>
<p>The key insight is that the exception doesn't crash the app. It's caught at a defined boundary and converted into a safe state that the UI can render.</p>
<p>Here's the complete flow:</p>
<pre><code class="language-dart">// domain layer — throws when rules are violated
class Employee {
  void clockIn(DateTime time) {
    if (clockInTime != null &amp;&amp; clockOutTime == null) {
      throw DomainException('Cannot clock in: already clocked in');
    }
    clockInTime = time;
  }
}
</code></pre>
<pre><code class="language-dart">// application layer — catches domain exceptions and returns Result
class ClockInUseCase {
  final EmployeeRepository _repository;

  ClockInUseCase(this._repository);

  Future&lt;Result&lt;Employee, AppException&gt;&gt; execute(
    String employeeId,
    DateTime time,
  ) async {
    try {
      final employee = await _repository.findById(EmployeeId(employeeId));

      if (employee == null) {
        return Result.failure(AppException.notFound('Employee not found'));
      }

      employee.clockIn(time);
      await _repository.save(employee);

      return Result.success(employee);
    } on DomainException catch (e) {
      return Result.failure(AppException.businessRule(e.message));
    } catch (e) {
      return Result.failure(AppException.unknown(e.toString()));
    }
  }
}
</code></pre>
<pre><code class="language-dart">// presentation layer — converts result into UI state
@riverpod
class EmployeeNotifier extends _$EmployeeNotifier {
  @override
  AsyncValue&lt;Employee?&gt; build() =&gt; const AsyncData(null);

  Future&lt;void&gt; clockIn(String employeeId) async {
    state = const AsyncLoading();

    final result = await ref
        .read(clockInUseCaseProvider)
        .execute(employeeId, DateTime.now());

    result.fold(
      onSuccess: (employee) =&gt; state = AsyncData(employee),
      onFailure: (error) =&gt; state = AsyncError(error, StackTrace.current),
    );
  }
}
</code></pre>
<pre><code class="language-dart">// widget — renders state, owns nothing
class EmployeeClockInWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(employeeNotifierProvider);

    return state.when(
      data: (employee) =&gt; employee != null
          ? ClockInSuccess(employee: employee)
          : const ClockInForm(),
      loading: () =&gt; const LoadingIndicator(),
      error: (error, _) =&gt; ErrorMessage(message: error.toString()),
    );
  }
}
</code></pre>
<p>The flow is linear and predictable:</p>
<ol>
<li><p>User input triggers a use case.</p>
</li>
<li><p>The use case calls the domain.</p>
</li>
<li><p>The domain enforces rules.</p>
</li>
<li><p>If a rule is violated, a DomainException is thrown.</p>
</li>
<li><p>The use case catches it and returns a failure Result.</p>
</li>
<li><p>The notifier converts the Result into AsyncError state.</p>
</li>
<li><p>The widget renders the error.</p>
</li>
</ol>
<p>At no point does the exception escape unhandled. And at no point does the widget touch business logic.</p>
<h2 id="heading-the-folder-structure">The Folder Structure</h2>
<p>With these concepts in place, the folder structure follows naturally:</p>
<pre><code class="language-plaintext">lib/
  shared/
    core/
      errors/
        domain_exception.dart
        app_exception.dart
      result/
        result.dart
      di/
        injection.dart
      utils/
        date_utils.dart

  features/
    employee/
      domain/
        entities/
          employee.dart
        value_objects/
          employee_id.dart
        repositories/
          employee_repository.dart
        services/
          attendance_domain_service.dart
        exceptions/
          employee_exceptions.dart

      application/
        use_cases/
          clock_in_usecase.dart
          clock_out_usecase.dart
          get_employee_usecase.dart

      infrastructure/
        datasources/
          employee_remote_datasource.dart
          employee_local_datasource.dart
        repositories/
          employee_repository_impl.dart
        dtos/
          employee_dto.dart

      presentation/
        notifier/
          employee_notifier.dart
          employee_notifier.g.dart
        state/
          employee_state.dart
        pages/
          employee_dashboard_page.dart
          clock_in_page.dart
        widgets/
          employee_card.dart
          clock_in_button.dart

    payment/
      domain/
        entities/
          payment.dart
        value_objects/
          money.dart
          payment_reference.dart
        repositories/
          payment_repository.dart
        services/
          payment_verification_service.dart

      application/
        use_cases/
          initiate_payment_usecase.dart
          verify_payment_usecase.dart
          refund_payment_usecase.dart

      infrastructure/
        datasources/
          payment_remote_datasource.dart
        repositories/
          payment_repository_impl.dart
        dtos/
          payment_dto.dart

      presentation/
        notifier/
          payment_notifier.dart
          payment_notifier.g.dart
        pages/
          payment_page.dart
          payment_confirmation_page.dart
        widgets/
          payment_summary_card.dart

  main.dart
</code></pre>
<p>The <code>shared/core</code> folder holds only what's genuinely shared across all features: base error types, the Result type, dependency injection configuration, and utilities that have no business logic. Everything else belongs inside a feature.</p>
<p>Each feature is a complete vertical slice. Every layer a feature needs lives inside that feature's folder. A developer working on the Employee feature never needs to navigate outside the <code>features/employee</code> directory to understand or modify the feature.</p>
<h2 id="heading-real-world-example-one-the-employee-feature">Real World Example One: The Employee Feature</h2>
<p>The Employee feature manages the complete lifecycle of employee attendance in a workforce management application. Clock-in, clock-out, shift management, and attendance records.</p>
<h3 id="heading-the-domain">The Domain</h3>
<pre><code class="language-dart">// value objects
class EmployeeId {
  final String value;

  EmployeeId(this.value) {
    if (value.isEmpty) throw DomainException('Employee ID cannot be empty');
  }
}

class ShiftDuration {
  final Duration duration;

  ShiftDuration(this.duration) {
    if (duration.isNegative) {
      throw DomainException('Shift duration cannot be negative');
    }
    if (duration.inHours &gt; 16) {
      throw DomainException('Shift duration cannot exceed 16 hours');
    }
  }
}
</code></pre>
<pre><code class="language-dart">// entity
class Employee {
  final EmployeeId id;
  final String name;
  final String teamId;
  DateTime? clockInTime;
  DateTime? clockOutTime;

  Employee({
    required this.id,
    required this.name,
    required this.teamId,
  });

  void clockIn(DateTime time) {
    if (isClockedIn) {
      throw DomainException('Employee is already clocked in');
    }
    clockInTime = time;
    clockOutTime = null;
  }

  void clockOut(DateTime time) {
    if (!isClockedIn) {
      throw DomainException('Employee is not clocked in');
    }
    if (time.isBefore(clockInTime!)) {
      throw DomainException('Clock out time cannot be before clock in time');
    }

    final shift = ShiftDuration(time.difference(clockInTime!));
    clockOutTime = time;
  }

  bool get isClockedIn =&gt; clockInTime != null &amp;&amp; clockOutTime == null;

  Duration? get currentShiftDuration {
    if (!isClockedIn) return null;
    return DateTime.now().difference(clockInTime!);
  }
}
</code></pre>
<pre><code class="language-dart">// repository interface — in the domain layer
abstract class EmployeeRepository {
  Future&lt;Employee?&gt; findById(EmployeeId id);
  Future&lt;List&lt;Employee&gt;&gt; findByTeam(String teamId);
  Future&lt;void&gt; save(Employee employee);
}
</code></pre>
<h3 id="heading-the-application-layer">The Application Layer</h3>
<pre><code class="language-dart">class ClockInUseCase {
  final EmployeeRepository _repository;

  ClockInUseCase(this._repository);

  Future&lt;Result&lt;Employee, AppException&gt;&gt; execute(String employeeId) async {
    try {
      final id = EmployeeId(employeeId);
      final employee = await _repository.findById(id);

      if (employee == null) {
        return Result.failure(
          AppException.notFound('Employee $employeeId not found'),
        );
      }

      employee.clockIn(DateTime.now());
      await _repository.save(employee);

      return Result.success(employee);
    } on DomainException catch (e) {
      return Result.failure(AppException.businessRule(e.message));
    } catch (e) {
      return Result.failure(AppException.unknown(e.toString()));
    }
  }
}

class ClockOutUseCase {
  final EmployeeRepository _repository;

  ClockOutUseCase(this._repository);

  Future&lt;Result&lt;Employee, AppException&gt;&gt; execute(String employeeId) async {
    try {
      final id = EmployeeId(employeeId);
      final employee = await _repository.findById(id);

      if (employee == null) {
        return Result.failure(
          AppException.notFound('Employee $employeeId not found'),
        );
      }

      employee.clockOut(DateTime.now());
      await _repository.save(employee);

      return Result.success(employee);
    } on DomainException catch (e) {
      return Result.failure(AppException.businessRule(e.message));
    } catch (e) {
      return Result.failure(AppException.unknown(e.toString()));
    }
  }
}
</code></pre>
<h3 id="heading-the-infrastructure-layer">The Infrastructure Layer</h3>
<pre><code class="language-dart">class EmployeeDTO {
  final String id;
  final String name;
  final String teamId;
  final String? clockInTime;
  final String? clockOutTime;

  const EmployeeDTO({
    required this.id,
    required this.name,
    required this.teamId,
    this.clockInTime,
    this.clockOutTime,
  });

  factory EmployeeDTO.fromJson(Map&lt;String, dynamic&gt; json) {
    return EmployeeDTO(
      id: json['id'] as String,
      name: json['name'] as String,
      teamId: json['team_id'] as String,
      clockInTime: json['clock_in_time'] as String?,
      clockOutTime: json['clock_out_time'] as String?,
    );
  }

  Employee toDomain() {
    final employee = Employee(
      id: EmployeeId(id),
      name: name,
      teamId: teamId,
    );

    if (clockInTime != null) {
      employee.clockIn(DateTime.parse(clockInTime!));
    }
    if (clockOutTime != null) {
      employee.clockOut(DateTime.parse(clockOutTime!));
    }

    return employee;
  }

  Map&lt;String, dynamic&gt; toJson() {
    return {
      'id': id,
      'name': name,
      'team_id': teamId,
      'clock_in_time': clockInTime,
      'clock_out_time': clockOutTime,
    };
  }
}

class EmployeeRepositoryImpl implements EmployeeRepository {
  final EmployeeRemoteDataSource _remote;

  EmployeeRepositoryImpl(this._remote);

  @override
  Future&lt;Employee?&gt; findById(EmployeeId id) async {
    final dto = await _remote.fetchEmployee(id.value);
    return dto?.toDomain();
  }

  @override
  Future&lt;List&lt;Employee&gt;&gt; findByTeam(String teamId) async {
    final dtos = await _remote.fetchTeamEmployees(teamId);
    return dtos.map((dto) =&gt; dto.toDomain()).toList();
  }

  @override
  Future&lt;void&gt; save(Employee employee) async {
    final dto = EmployeeDTO(
      id: employee.id.value,
      name: employee.name,
      teamId: employee.teamId,
      clockInTime: employee.clockInTime?.toIso8601String(),
      clockOutTime: employee.clockOutTime?.toIso8601String(),
    );
    await _remote.updateEmployee(dto);
  }
}
</code></pre>
<h3 id="heading-the-presentation-layer">The Presentation Layer</h3>
<pre><code class="language-csharp">@riverpod
class EmployeeNotifier extends _$EmployeeNotifier {
  @override
  AsyncValue&lt;Employee?&gt; build() =&gt; const AsyncData(null);

  Future&lt;void&gt; clockIn(String employeeId) async {
    state = const AsyncLoading();

    final result = await ref
        .read(clockInUseCaseProvider)
        .execute(employeeId);

    result.fold(
      onSuccess: (employee) =&gt; state = AsyncData(employee),
      onFailure: (error) =&gt; state = AsyncError(error, StackTrace.current),
    );
  }

  Future&lt;void&gt; clockOut(String employeeId) async {
    state = const AsyncLoading();

    final result = await ref
        .read(clockOutUseCaseProvider)
        .execute(employeeId);

    result.fold(
      onSuccess: (employee) =&gt; state = AsyncData(employee),
      onFailure: (error) =&gt; state = AsyncError(error, StackTrace.current),
    );
  }
}
</code></pre>
<pre><code class="language-csharp">class EmployeeDashboardPage extends ConsumerWidget {
  final String employeeId;

  const EmployeeDashboardPage({required this.employeeId, super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(employeeNotifierProvider);

    return Scaffold(
      appBar: AppBar(title: const Text('Employee Dashboard')),
      body: state.when(
        data: (employee) =&gt; employee != null
            ? EmployeeDashboardContent(
                employee: employee,
                onClockIn: () =&gt; ref
                    .read(employeeNotifierProvider.notifier)
                    .clockIn(employeeId),
                onClockOut: () =&gt; ref
                    .read(employeeNotifierProvider.notifier)
                    .clockOut(employeeId),
              )
            : const EmptyDashboard(),
        loading: () =&gt; const Center(child: CircularProgressIndicator()),
        error: (error, _) =&gt; ErrorView(message: error.toString()),
      ),
    );
  }
}
</code></pre>
<p>The widget knows nothing about clocking in, the EmployeeId Value Object, or the DomainException. It renders state and delegates actions to the notifier. Everything else happens in the layers below.</p>
<h2 id="heading-real-world-example-two-the-payment-feature">Real World Example Two: The Payment Feature</h2>
<p>The Payment feature handles the complete payment lifecycle: initiation, processing, verification, and failure handling. This is a domain with complex business rules that benefit enormously from being modeled explicitly.</p>
<h3 id="heading-the-domain">The Domain</h3>
<pre><code class="language-csharp">enum PaymentStatus {
  pending,
  processing,
  completed,
  failed,
  refunded,
}

class PaymentReference {
  final String value;

  PaymentReference(this.value) {
    if (value.isEmpty) {
      throw DomainException('Payment reference cannot be empty');
    }
    if (!RegExp(r'^[A-Z0-9]{8,16}$').hasMatch(value)) {
      throw DomainException('Invalid payment reference format');
    }
  }
}

class Payment {
  final PaymentReference reference;
  final Money amount;
  final String payerId;
  final String recipientId;
  PaymentStatus status;
  String? failureReason;
  DateTime? processedAt;

  Payment({
    required this.reference,
    required this.amount,
    required this.payerId,
    required this.recipientId,
    this.status = PaymentStatus.pending,
  });

  void startProcessing() {
    if (status != PaymentStatus.pending) {
      throw DomainException(
        'Cannot process payment: current status is ${status.name}',
      );
    }
    status = PaymentStatus.processing;
  }

  void complete() {
    if (status != PaymentStatus.processing) {
      throw DomainException(
        'Cannot complete payment: current status is ${status.name}',
      );
    }
    status = PaymentStatus.completed;
    processedAt = DateTime.now();
  }

  void fail(String reason) {
    if (status != PaymentStatus.processing) {
      throw DomainException(
        'Cannot fail payment: current status is ${status.name}',
      );
    }
    status = PaymentStatus.failed;
    failureReason = reason;
  }

  void refund() {
    if (status != PaymentStatus.completed) {
      throw DomainException(
        'Cannot refund payment: only completed payments can be refunded',
      );
    }
    status = PaymentStatus.refunded;
  }

  bool get canBeRefunded =&gt; status == PaymentStatus.completed;
  bool get isTerminal =&gt;
      status == PaymentStatus.completed ||
      status == PaymentStatus.failed ||
      status == PaymentStatus.refunded;
}
</code></pre>
<p>The Payment entity enforces every valid status transition. A payment that's already processing can't start processing again. A payment that hasn't completed can't be refunded. And a payment that has failed can't be completed.</p>
<p>These rules are encoded into the entity and enforced at every state change.</p>
<h3 id="heading-the-application-layer">The Application Layer</h3>
<pre><code class="language-csharp">class InitiatePaymentUseCase {
  final PaymentRepository _repository;

  InitiatePaymentUseCase(this._repository);

  Future&lt;Result&lt;Payment, AppException&gt;&gt; execute({
    required String reference,
    required double amount,
    required String currency,
    required String payerId,
    required String recipientId,
  }) async {
    try {
      final payment = Payment(
        reference: PaymentReference(reference),
        amount: Money(amount: amount, currency: currency),
        payerId: payerId,
        recipientId: recipientId,
      );

      payment.startProcessing();
      await _repository.save(payment);

      return Result.success(payment);
    } on DomainException catch (e) {
      return Result.failure(AppException.businessRule(e.message));
    } catch (e) {
      return Result.failure(AppException.unknown(e.toString()));
    }
  }
}

class VerifyPaymentUseCase {
  final PaymentRepository _repository;
  final PaymentVerificationService _verificationService;

  VerifyPaymentUseCase(this._repository, this._verificationService);

  Future&lt;Result&lt;Payment, AppException&gt;&gt; execute(String reference) async {
    try {
      final ref = PaymentReference(reference);
      final payment = await _repository.findByReference(ref);

      if (payment == null) {
        return Result.failure(
          AppException.notFound('Payment $reference not found'),
        );
      }

      final isVerified = await _verificationService.verify(payment);

      if (isVerified) {
        payment.complete();
      } else {
        payment.fail('Verification failed');
      }

      await _repository.save(payment);
      return Result.success(payment);
    } on DomainException catch (e) {
      return Result.failure(AppException.businessRule(e.message));
    } catch (e) {
      return Result.failure(AppException.unknown(e.toString()));
    }
  }
}
</code></pre>
<h3 id="heading-the-presentation-layer">The Presentation Layer</h3>
<pre><code class="language-csharp">@riverpod
class PaymentNotifier extends _$PaymentNotifier {
  @override
  AsyncValue&lt;Payment?&gt; build() =&gt; const AsyncData(null);

  Future&lt;void&gt; initiatePayment({
    required String reference,
    required double amount,
    required String currency,
    required String payerId,
    required String recipientId,
  }) async {
    state = const AsyncLoading();

    final result = await ref.read(initiatePaymentUseCaseProvider).execute(
          reference: reference,
          amount: amount,
          currency: currency,
          payerId: payerId,
          recipientId: recipientId,
        );

    result.fold(
      onSuccess: (payment) =&gt; state = AsyncData(payment),
      onFailure: (error) =&gt; state = AsyncError(error, StackTrace.current),
    );
  }

  Future&lt;void&gt; verifyPayment(String reference) async {
    state = const AsyncLoading();

    final result = await ref
        .read(verifyPaymentUseCaseProvider)
        .execute(reference);

    result.fold(
      onSuccess: (payment) =&gt; state = AsyncData(payment),
      onFailure: (error) =&gt; state = AsyncError(error, StackTrace.current),
    );
  }
}
</code></pre>
<pre><code class="language-csharp">class PaymentPage extends ConsumerWidget {
  const PaymentPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(paymentNotifierProvider);

    return Scaffold(
      appBar: AppBar(title: const Text('Payment')),
      body: state.when(
        data: (payment) {
          if (payment == null) return const PaymentForm();
          return PaymentStatusView(payment: payment);
        },
        loading: () =&gt; const Center(child: CircularProgressIndicator()),
        error: (error, _) =&gt; PaymentErrorView(message: error.toString()),
      ),
    );
  }
}
</code></pre>
<h2 id="heading-cross-feature-communication">Cross-Feature Communication</h2>
<p>Self-contained features inevitably need to communicate. The Employee feature might need to check if a user is authenticated. The Payment feature might need to notify the Employee feature about a salary disbursement.</p>
<p>The rules for cross-feature communication preserve the self-containment of each feature:</p>
<h3 id="heading-1-features-never-import-directly-from-each-others-internal-layers">1. Features never import directly from each other's internal layers.</h3>
<p>The Employee feature should never import from <code>features/payment/domain/entities/payment.dart</code>. Direct imports between features create tight coupling that defeats the purpose of modularization.</p>
<h3 id="heading-2-shared-domain-concepts-live-in-sharedcore">2. Shared domain concepts live in <code>shared/core</code>.</h3>
<p>If both the Employee feature and the Payment feature need a concept like a <code>UserId</code> or a <code>Money</code> value object, that concept lives in <code>shared/core/domain</code> and both features import from there.</p>
<h3 id="heading-3-cross-feature-communication-happens-through-defined-interfaces">3. Cross-feature communication happens through defined interfaces.</h3>
<p>If the Payment feature needs to know about an employee to process a salary, it depends on an <code>EmployeeService</code> interface defined in <code>shared/core</code>. The Employee feature provides the implementation. The Payment feature never knows which feature provided it.</p>
<h3 id="heading-4-events-and-domain-notifications-use-a-shared-event-bus">4. Events and domain notifications use a shared event bus.</h3>
<p>When a payment completes, the Payment feature publishes a <code>PaymentCompleted</code> domain event. Any feature that needs to react to payment completion subscribes to that event. The Payment feature doesn't know who's listening. The listening features don't import from the Payment feature directly.</p>
<h2 id="heading-patterns-that-enhance-modularization">Patterns That Enhance Modularization</h2>
<p>Certain design patterns work particularly well within a modularized feature structure.</p>
<p><strong>The Prototype Pattern</strong> is useful for creating employee templates in the Employee feature. An organization might have standard employee profiles for different roles. Cloning a template employee creates a new instance with the same configuration, allowing business rules to fire during the clone's state transitions rather than being bypassed.</p>
<pre><code class="language-csharp">class Employee {
  Employee clone() {
    return Employee(
      id: EmployeeId('${id.value}_copy_${DateTime.now().millisecondsSinceEpoch}'),
      name: name,
      teamId: teamId,
    );
  }
}
</code></pre>
<p><strong>The Singleton Pattern</strong> applies to genuinely unique domain objects. The currently authenticated user is a Singleton. The current session is a Singleton. These live in <code>shared/core</code> because they are cross-feature concerns.</p>
<p><strong>The Repository Pattern</strong> is the backbone of infrastructure abstraction in every feature. Domain layers define the interface. Infrastructure layers provide the implementation. Application layers use the interface without knowing which implementation is behind it.</p>
<p><strong>The Observer Pattern,</strong> through domain events, enables cross-feature communication without tight coupling. Each feature publishes events when significant domain state changes. Other features subscribe to those events through a shared event bus.</p>
<h2 id="heading-scaling-to-large-teams">Scaling to Large Teams</h2>
<p>Feature Modularization becomes even more valuable as teams grow. At ten engineers working on different features simultaneously, the self-contained nature of each feature prevents constant merge conflicts. Engineers working on the Employee feature and engineers working on the Payment feature are almost never touching the same files.</p>
<p>At thirty engineers, features can become separate Dart packages. The Employee feature becomes <code>packages/employee</code>. The Payment feature becomes <code>packages/payment</code>. They depend on a shared <code>packages/core</code> package. Each package has its own <code>pubspec.yaml</code>, its own tests, and can be independently versioned. This is the next level of modularization and it follows naturally from the feature-first structure.</p>
<p>At fifty engineers, teams can own entire features. The Employee team owns <code>packages/employee</code>. The Payment team owns <code>packages/payment</code>. The contracts between features, defined through interfaces in <code>packages/core</code>, become the API boundaries between teams. A team can release a new version of their feature package independently, and other teams update to that version when they're ready.</p>
<p>The folder structure you start with today is the same structure that scales to a package-based monorepo at fifty engineers. The concepts don't change. The boundaries just become more explicit.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Feature Modularization isn't a folder structure trick. It's a strategic decision about how to organize software so that it can grow without degrading.</p>
<p>It takes Clean Architecture's layering rules and DDD's domain modeling vocabulary and applies them at the feature level. Each feature owns its domain, business rules, data access, and UI. Nothing leaks out or bleeds in. Each feature can be understood, modified, and tested without understanding the entire application.</p>
<p>The Employee feature enforces attendance rules in its entity. The Payment feature enforces transaction state transitions in its entity. Both features use Value Objects to prevent invalid data from ever entering the domain. Both features use the same Result pattern to propagate failures safely from the domain through the application layer to the presentation layer.</p>
<p>When a new requirement arrives for the Employee feature, you open the employee folder and make the change. The Payment feature is untouched. When a new payment method is added to the Payment feature, you open the payment folder and make the change. The Employee feature is untouched.</p>
<p>This is what self-contained actually means. Not just a folder with a name. A complete vertical slice of your application that owns everything it needs and shares nothing it should not.</p>
<p>That is Feature Modularization. And that's how Flutter apps scale from one engineer to fifty without the codebase becoming a liability.</p>
<p>Happy Coding!!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Observer Design Pattern Handbook: Event-Driven Architecture & Domain-Driven Design in Dart ]]>
                </title>
                <description>
                    <![CDATA[ Every application, at some point, has to deal with a fundamental challenge: something happens, and several other things need to react to it. A user logs in, and the app needs to save a token, cache th ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-observer-design-pattern-handbook-event-driven-architecture-domain-driven-design-in-dart/</link>
                <guid isPermaLink="false">6a59593c2c971321745e7720</guid>
                
                    <category>
                        <![CDATA[ #Domain-Driven-Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Dart ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Mobile Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Observer Pattern ]]>
                    </category>
                
                    <category>
                        <![CDATA[ design patterns ]]>
                    </category>
                
                    <category>
                        <![CDATA[ behavioural patterns ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Engineering ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Riverpod ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Clean Architecture ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oluwaseyi Fatunmole ]]>
                </dc:creator>
                <pubDate>Thu, 16 Jul 2026 22:20:44 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/0621293d-e82e-4f24-bb6e-40dec481c7cd.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Every application, at some point, has to deal with a fundamental challenge: something happens, and several other things need to react to it.</p>
<p>A user logs in, and the app needs to save a token, cache the user profile, fire an analytics event, and navigate to the home screen.</p>
<p>A payment is confirmed, and the inventory needs to update, the user needs a receipt, and the fulfillment system needs to kick off delivery.</p>
<p>A sensor reading changes, and three different UI panels need to reflect the new value simultaneously.</p>
<p>The naïve solution is to write all of that logic in one place. One function that does everything or one class that knows about everything.</p>
<p>This works at first. Then requirements change. A new reaction needs to be added. An existing one needs to be removed. A side effect starts failing and takes everything else down with it. The code becomes a wall of responsibilities that's impossible to test, painful to extend, and dangerous to touch.</p>
<p>The Observer Design Pattern exists to solve exactly this problem. It gives you a structured, production-grade way to say: when this event happens, notify everyone who cares, without the event source knowing who those people are.</p>
<p>In this handbook, you'll learn the Observer pattern from first principles. You'll see how it's implemented in Dart, understand how it connects to Event-Driven Architecture, and discover how it integrates cleanly with Domain-Driven Design and Riverpod in a real Flutter application.</p>
<p>By the end, you won't just understand the pattern. You'll know how to use it deliberately in production code.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-is-the-observer-design-pattern">What is the Observer Design Pattern?</a></p>
</li>
<li><p><a href="#heading-the-problem-it-solves">The Problem It Solves</a></p>
</li>
<li><p><a href="#heading-core-components">Core Components</a></p>
</li>
<li><p><a href="#heading-implementing-observer-in-dart">Implementing Observer in Dart</a></p>
</li>
<li><p><a href="#heading-a-real-world-example-the-login-flow">A Real-World Example: The Login Flow</a></p>
</li>
<li><p><a href="#heading-making-it-production-grade-with-a-generic-eventbus">Making It Production-Grade with a Generic EventBus</a></p>
</li>
<li><p><a href="#heading-observer-is-already-in-your-flutter-code">Observer Is Already in Your Flutter Code</a></p>
</li>
<li><p><a href="#heading-deep-dive-into-event-driven-architecture">Deep Dive Into Event-Driven Architecture</a></p>
</li>
<li><p><a href="#heading-application-in-domain-driven-design">Application in Domain-Driven Design</a></p>
</li>
<li><p><a href="#heading-the-riverpod-hybrid-clean-architecture-in-practice">The Riverpod Hybrid: Clean Architecture in Practice</a></p>
</li>
<li><p><a href="#heading-testing-the-observer-architecture">Testing the Observer Architecture</a></p>
</li>
<li><p><a href="#heading-when-to-use-the-observer-pattern">When to Use the Observer Pattern</a></p>
</li>
<li><p><a href="#heading-when-not-to-use-it">When Not to Use It</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-is-the-observer-design-pattern">What is the Observer Design Pattern?</h2>
<p>The Observer pattern is a behavioural design pattern that defines a one-to-many dependency between objects. When one object changes state or fires an event, all of its dependents are notified and updated automatically.</p>
<p>Think of a newspaper subscription service. The newspaper publisher doesn't know who its individual subscribers are. It doesn't call each reader personally. It publishes the paper, and every subscriber who signed up receives it.</p>
<p>A subscriber can cancel at any time. A new subscriber can join at any time. The publisher's job never changes. It just publishes.</p>
<p>That's the Observer pattern in plain terms.</p>
<p>The publisher is called the <strong>Subject</strong>. The subscribers are called <strong>Observers</strong>. The newspaper is the <strong>event</strong>.</p>
<p>The pattern was formally defined in the Gang of Four book, Design Patterns: Elements of Reusable Object-Oriented Software. It remains one of the most widely used patterns in software engineering, especially in reactive and event-driven systems.</p>
<h2 id="heading-the-problem-it-solves">The Problem It Solves</h2>
<p>Let's look at what happens without the Observer pattern.</p>
<p>Say you have a login feature. When login succeeds, you need to do four things:</p>
<ul>
<li><p>Save the authentication token to secure storage</p>
</li>
<li><p>Cache the user profile data</p>
</li>
<li><p>Navigate to the home screen</p>
</li>
<li><p>Fire an analytics event</p>
</li>
</ul>
<p>The straightforward approach puts all of this inside the login function:</p>
<pre><code class="language-dart">Future&lt;void&gt; login(String email, String password) async {
  final response = await _authRepository.login(email, password);

  await _secureStorage.write(key: 'token', value: response.token);
  await _userCache.save(response.user);
  _navigationService.navigateTo('/home');
  _analytics.track('login_success', {'userId': response.user.id});
}
</code></pre>
<p>This looks fine at first glance. But count how many reasons this single function has to change:</p>
<ul>
<li><p>The token storage strategy changes. You modify this function.</p>
</li>
<li><p>The navigation destination changes. You modify this function.</p>
</li>
<li><p>The analytics event name or payload changes. You modify this function.</p>
</li>
<li><p>The user caching logic changes. You modify this function.</p>
</li>
</ul>
<p>Every single change to any of these four concerns forces you back into this one function. And every time you touch it, you risk breaking all the other three things it's doing.</p>
<p>Now imagine you need to add a fifth thing, such as enrolling the user in push notifications. You open this function again. You add more code. The function grows. Testing it requires mocking four, then five different dependencies. New teammates struggle to understand what this function is actually responsible for. The answer, of course, is everything. And that's the problem.</p>
<p>This is called tight coupling. The login logic is coupled to every single consequence of a successful login.</p>
<p>The Observer pattern breaks these couplings completely. The login logic does one thing: it performs the login and announces the result. Every consequence is handled by a separate, independent observer. Each observer has one job. None of them know about each other. The login logic doesn't know they exist.</p>
<h2 id="heading-core-components">Core Components</h2>
<p>The Observer pattern has four core building blocks. Understanding each one before writing code makes the implementation much easier to follow.</p>
<h3 id="heading-subject">Subject</h3>
<p>The Subject is the object that something happens to. It holds a list of observers and is responsible for notifying them when an event occurs. It exposes methods for observers to register and unregister themselves. The Subject doesn't care what observers do with the notification. It just delivers it.</p>
<h3 id="heading-observer">Observer</h3>
<p>The Observer is an interface or abstract class that defines the contract all observers must follow. It declares the method or methods the Subject will call when notifying. Any class that wants to react to an event must implement this interface.</p>
<h3 id="heading-concrete-subject">Concrete Subject</h3>
<p>The Concrete Subject is the real implementation of the Subject. It manages the actual list of observers, handles subscriptions, and fires notifications at the right moment.</p>
<h3 id="heading-concrete-observers">Concrete Observers</h3>
<p>These are the real classes that implement the Observer interface. Each one has a specific, focused job to do when notified. One saves the token. One navigates. One fires analytics. They don't know about each other and don't need to.</p>
<p>Here's how they relate to each other:</p>
<pre><code class="language-cpp">Subject (LoginService)
    |
    |-- subscribe(observer)    &lt;- observer registers itself
    |-- unsubscribe(observer)  &lt;- observer removes itself
    |-- notifySuccess(data)    &lt;- fires when login succeeds
    |-- notifyFailure(error)   &lt;- fires when login fails
         |
         |-----&gt; TokenObserver.onLoginSuccess()
         |-----&gt; UserObserver.onLoginSuccess()
         |-----&gt; NavigationObserver.onLoginSuccess()
         |-----&gt; AnalyticsObserver.onLoginSuccess()
</code></pre>
<p>The Subject notifies all of them. They each handle their own job independently.</p>
<h2 id="heading-implementing-observer-in-dart">Implementing Observer in Dart</h2>
<p>Let's build the pattern step by step.</p>
<h3 id="heading-step-1-define-the-observer-interface">Step 1: Define the Observer Interface</h3>
<pre><code class="language-dart">abstract class LoginObserver {
  void onLoginSuccess(UserDto user);
  void onLoginFailed(AppException error);
}
</code></pre>
<p>This is the contract that every observer must sign. Any class that wants to react to login events must implement both of these methods.</p>
<p><code>onLoginSuccess</code> is called when the login succeeds and receives the user data. <code>onLoginFailed</code> is called when the login fails and receives the error.</p>
<h3 id="heading-step-2-define-the-subject-interface">Step 2: Define the Subject Interface</h3>
<pre><code class="language-dart">abstract class LoginSubject {
  void subscribe(LoginObserver observer);
  void unsubscribe(LoginObserver observer);
  void notifySuccess(UserDto user);
  void notifyFailure(AppException error);
}
</code></pre>
<p><code>subscribe</code> lets an observer join the notification list. <code>unsubscribe</code> lets an observer leave the notification list. <code>notifySuccess</code> broadcasts a success event with the user data to all registered observers. <code>notifyFailure</code> broadcasts a failure event with the error to all registered observers.</p>
<p>Defining this as an abstract class instead of going straight to a concrete class is important. It means anything that depends on the subject depends on the abstraction, not the implementation. This makes your code testable and swappable.</p>
<h3 id="heading-step-3-implement-the-concrete-subject">Step 3: Implement the Concrete Subject</h3>
<pre><code class="language-dart">class LoginService implements LoginSubject {
  final List&lt;LoginObserver&gt; _observers = [];

  @override
  void subscribe(LoginObserver observer) {
    _observers.add(observer);
  }

  @override
  void unsubscribe(LoginObserver observer) {
    _observers.remove(observer);
  }

  @override
  void notifySuccess(UserDto user) {
    for (final observer in List.of(_observers)) {
      try {
        observer.onLoginSuccess(user);
      } catch (e) {
        debugPrint('Observer error on success: $e');
      }
    }
  }

  @override
  void notifyFailure(AppException error) {
    for (final observer in List.of(_observers)) {
      try {
        observer.onLoginFailed(error);
      } catch (e) {
        debugPrint('Observer error on failure: $e');
      }
    }
  }
}
</code></pre>
<p>There are two important decisions in this implementation that are easy to miss.</p>
<h4 id="heading-1-snapshot-iteration-with-listof">1. Snapshot iteration with <code>List.of()</code></h4>
<p>Instead of iterating directly over <code>_observers</code>, we iterate over <code>List.of(_observers)</code>, which creates a copy of the list before the loop runs.</p>
<p>Why does this matter? Imagine a <code>NavigationObserver</code> that, after navigating to the home screen, unsubscribes itself because it no longer needs to listen. If it calls <code>unsubscribe</code> while the <code>notifySuccess</code> loop is still running over the same list, Dart throws a <code>ConcurrentModificationError</code>. The list is being modified while it's being read.</p>
<p><code>List.of()</code> prevents this entirely. The loop runs over the snapshot. The original list can be modified freely during iteration without any errors.</p>
<h4 id="heading-2-per-observer-trycatch">2. Per-observer try/catch</h4>
<p>Each observer call is wrapped in its own try/catch block. This is a deliberate choice. If <code>TokenObserver</code> throws an exception while writing to secure storage, you don't want <code>NavigationObserver</code> and <code>AnalyticsObserver</code> to silently never fire. Each observer gets its chance to run regardless of what the others do.</p>
<p>Without this, one failing observer would stop the entire notification chain. That's a hidden bug that's extremely difficult to trace in production.</p>
<h2 id="heading-a-real-world-example-the-login-flow">A Real-World Example: The Login Flow</h2>
<p>Now let's build the full login flow using this foundation.</p>
<h3 id="heading-the-login-logic">The Login Logic</h3>
<pre><code class="language-cpp">class LoginLogic {
  final LoginSubject _subject;
  final AuthRepository _repository;

  LoginLogic({
    required LoginSubject subject,
    required AuthRepository repository,
  })  : _subject = subject,
        _repository = repository;

  Future&lt;void&gt; callLogin(LoginRequest request) async {
    try {
      final user = await _repository.login(request);
      _subject.notifySuccess(user);
    } on AppException catch (e) {
      _subject.notifyFailure(e);
    } catch (e) {
      _subject.notifyFailure(AppException.unknown(message: e.toString()));
    }
  }
}
</code></pre>
<p>Let's walk through this carefully.</p>
<p><code>LoginLogic</code> takes two dependencies through its constructor: a <code>LoginSubject</code> and an <code>AuthRepository</code>. Notice it takes <code>LoginSubject</code>, the abstraction, not <code>LoginService</code>, the concrete class. This means you can swap the implementation in tests or in different environments without changing <code>LoginLogic</code> at all.</p>
<p>Inside <code>callLogin</code>, the logic is straightforward. It calls the repository to perform the actual login. If that succeeds, it calls <code>notifySuccess</code> on the subject with the returned user. If it throws an <code>AppException</code>, it calls <code>notifyFailure</code> with that error. If it throws anything unexpected, it wraps it in an <code>AppException.unknown</code> and notifies failure.</p>
<p>Notice what <code>LoginLogic</code> does NOT do. It doesn't save a token. It doesn't navigate anywhere. It doesn't cache anything. It doesn't fire analytics. And it doesn't know how many observers exist or what they do.</p>
<p>Its entire responsibility is: perform the login, announce the result.</p>
<h3 id="heading-the-concrete-observers">The Concrete Observers</h3>
<pre><code class="language-cpp">class TokenObserver implements LoginObserver {
  final SecureStorageService _storage;

  TokenObserver(this._storage);

  @override
  void onLoginSuccess(UserDto user) {
    _storage.write(key: 'auth_token', value: user.token);
  }

  @override
  void onLoginFailed(AppException error) {
    _storage.delete(key: 'auth_token');
  }
}
</code></pre>
<p><code>TokenObserver</code> has one job: manage the authentication token. On success, it saves the token. On failure, it clears any stale token that might be sitting in storage. It knows nothing about navigation, caching, or analytics.</p>
<pre><code class="language-cpp">class UserObserver implements LoginObserver {
  final UserCacheService _cache;

  UserObserver(this._cache);

  @override
  void onLoginSuccess(UserDto user) {
    _cache.save(user);
  }

  @override
  void onLoginFailed(AppException error) {
    _cache.clear();
  }
}
</code></pre>
<p><code>UserObserver</code> has one job: manage the user cache. On success, it saves the user profile. On failure, it clears the cache. It knows nothing about tokens, navigation, or analytics.</p>
<pre><code class="language-cpp">class NavigationObserver implements LoginObserver {
  final NavigationService _navigation;

  NavigationObserver(this._navigation);

  @override
  void onLoginSuccess(UserDto user) {
    _navigation.navigateTo('/home');
  }

  @override
  void onLoginFailed(AppException error) {
    _navigation.showError(error.message);
  }
}
</code></pre>
<p><code>NavigationObserver</code> has one job: handle navigation after a login attempt. It uses an injected <code>NavigationService</code> abstraction rather than a <code>BuildContext</code>. This is intentional. An observer that depends on <code>BuildContext</code> is tied to the widget lifecycle. Using an abstraction keeps this observer completely independent of the UI layer.</p>
<pre><code class="language-cpp">class AnalyticsObserver implements LoginObserver {
  final AnalyticsService _analytics;

  AnalyticsObserver(this._analytics);

  @override
  void onLoginSuccess(UserDto user) {
    _analytics.track('login_success', {'userId': user.id});
  }

  @override
  void onLoginFailed(AppException error) {
    _analytics.track('login_failed', {'reason': error.message});
  }
}
</code></pre>
<p><code>AnalyticsObserver</code> has one job: fire the right analytics event for each outcome. It has no knowledge of storage, navigation, or caching.</p>
<p>Each observer has exactly one responsibility. Each one has exactly one reason to change. When the analytics payload needs to change, you touch only <code>AnalyticsObserver</code>. When navigation logic changes, you touch only <code>NavigationObserver</code>. Nothing else is affected.</p>
<h3 id="heading-wiring-it-together">Wiring It Together</h3>
<pre><code class="language-cpp">void setupLogin() {
  final service = LoginService();

  service
    ..subscribe(TokenObserver(secureStorage))
    ..subscribe(UserObserver(userCache))
    ..subscribe(NavigationObserver(navigationService))
    ..subscribe(AnalyticsObserver(analyticsService));

  final loginLogic = LoginLogic(
    subject: service,
    repository: authRepository,
  );
}
</code></pre>
<p>This is the composition step. All observers are created with their dependencies and registered onto the service. The cascade operator <code>..</code> calls <code>subscribe</code> multiple times on the same <code>service</code> object, which keeps the setup readable.</p>
<p><code>LoginLogic</code> receives the <code>service</code> as its <code>LoginSubject</code>. From this point forward, every time <code>callLogin</code> is called and an outcome occurs, all four observers are notified automatically.</p>
<p>Adding a fifth observer, say a <code>PushNotificationObserver</code>, means creating the class and adding one line here: <code>..subscribe(PushNotificationObserver(pushService))</code>. Nothing else in the entire codebase changes.</p>
<h2 id="heading-making-it-production-grade-with-a-generic-eventbus">Making It Production-Grade with a Generic EventBus</h2>
<p>The login example above works well, but it's specific to login. In a real application, many features have the same fan-out requirement. Payment confirmed, order placed, profile updated, session expired. All of them need one event to trigger multiple independent reactions.</p>
<p>Rewriting the Subject and Observer interfaces per feature is repetitive and unnecessary. The better approach is a generic <code>EventBus</code> that any feature can use.</p>
<pre><code class="language-cpp">abstract class DomainObserver&lt;T&gt; {
  void onSuccess(T data);
  void onFailure(AppException error);
}
</code></pre>
<p><code>DomainObserver&lt;T&gt;</code> is a generic observer. The type parameter <code>T</code> represents the data type the observer expects on success. A login observer would be <code>DomainObserver&lt;UserDto&gt;</code>. A payment observer would be <code>DomainObserver&lt;PaymentDto&gt;</code>. The interface is the same. The data type changes per feature.</p>
<pre><code class="language-cpp">class EventBus&lt;T&gt; {
  final List&lt;DomainObserver&lt;T&gt;&gt; _observers = [];

  void subscribe(DomainObserver&lt;T&gt; observer) {
    _observers.add(observer);
  }

  void unsubscribe(DomainObserver&lt;T&gt; observer) {
    _observers.remove(observer);
  }

  void publishSuccess(T data) {
    for (final observer in List.of(_observers)) {
      try {
        observer.onSuccess(data);
      } catch (e) {
        debugPrint('[EventBus] Observer error on success: $e');
      }
    }
  }

  void publishFailure(AppException error) {
    for (final observer in List.of(_observers)) {
      try {
        observer.onFailure(error);
      } catch (e) {
        debugPrint('[EventBus] Observer error on failure: $e');
      }
    }
  }
}
</code></pre>
<p><code>EventBus&lt;T&gt;</code> is a generic subject. It manages a list of typed observers and notifies them with the same snapshot iteration and per-observer error isolation we established earlier.</p>
<p>Now every feature gets the same infrastructure without duplicating a single line of the pattern:</p>
<pre><code class="language-cpp">final loginBus = EventBus&lt;UserDto&gt;();
final paymentBus = EventBus&lt;PaymentDto&gt;();
final orderBus = EventBus&lt;OrderDto&gt;();
</code></pre>
<p>Each bus is typed to its domain concept. Observers registered on <code>loginBus</code> will never accidentally receive payment events. The type system enforces correctness.</p>
<h2 id="heading-observer-is-already-in-your-flutter-code">Observer Is Already in Your Flutter Code</h2>
<p>Before going further into architecture, here's something worth pausing on. You've been using the Observer pattern all along without calling it by that name.</p>
<p><strong>Streams and StreamController:</strong></p>
<pre><code class="language-cpp">final controller = StreamController&lt;String&gt;();

controller.stream.listen((event) {
  print('Observed: $event');
});

controller.sink.add('Login succeeded');
</code></pre>
<p><code>StreamController</code> is a Subject. <code>stream.listen</code> is <code>subscribe</code>. <code>sink.add</code> is <code>notifyObservers</code>. Every stream subscription is an Observer. The pattern is identical. Flutter just gave it different names.</p>
<p><strong>ChangeNotifier:</strong></p>
<pre><code class="language-cpp">class CounterModel extends ChangeNotifier {
  int _count = 0;

  void increment() {
    _count++;
    notifyListeners();
  }
}
</code></pre>
<p><code>notifyListeners()</code> iterates over every registered listener and calls them. Those listeners are Observers. <code>addListener</code> is <code>subscribe</code>. <code>removeListener</code> is <code>unsubscribe</code>. <code>ChangeNotifier</code> is a concrete Subject.</p>
<p><strong>BLoC:</strong></p>
<p>When a BLoC emits a new state, every widget that wrapped itself in a <code>BlocBuilder</code> or <code>BlocListener</code> reacts. The BLoC is the Subject. The builders and listeners are Observers. The state emission is the notification.</p>
<p>Flutter's entire reactive system (Streams, ChangeNotifier, BLoC, ValueNotifier) is the Observer pattern with lifecycle management built in. Understanding the pattern at this fundamental level means you understand why all of these tools work the way they do. You aren't just using them. You understand them.</p>
<h2 id="heading-deep-dive-into-event-driven-architecture">Deep Dive Into Event-Driven Architecture</h2>
<p>Understanding Observer at the class level is the foundation. The pattern becomes significantly more powerful when applied at the architectural level, and that's where Event-Driven Architecture comes in.</p>
<h3 id="heading-what-is-event-driven-architecture">What is Event-Driven Architecture?</h3>
<p>Event-Driven Architecture is a design paradigm where the flow of the application is determined by events. Instead of components calling each other directly, they communicate by producing and consuming events through a shared bus or channel.</p>
<p>In a traditional request-driven flow, this is what happens:</p>
<pre><code class="language-cpp">Component A calls Component B directly
Component B does its work and returns a result
Component A waits for that result and then continues
</code></pre>
<p>Component A knows about Component B. It depends on it by name. It waits for it to finish. If you want Component C to also react to whatever Component A is doing, you have to go back into Component A and add that call.</p>
<p>But then Component A grows. Component A becomes responsible for orchestrating consequences it should know nothing about.</p>
<p>In an event-driven flow, this is what happens instead:</p>
<pre><code class="language-cpp">Component A publishes an event to the EventBus
EventBus delivers the event to whoever is registered

Component B handles the event
Component C handles the event
Component D handles the event
</code></pre>
<p>Component A doesn't know about B, C, or D. It doesn't wait for them. It publishes what happened and moves on. New handlers can be added without touching Component A at all. This is the Observer pattern scaled to the architectural level.</p>
<h3 id="heading-events-are-facts-not-commands">Events Are Facts, Not Commands</h3>
<p>This distinction is one of the most important concepts in Event-Driven Architecture.</p>
<p>A command says: "do this." It's an instruction that can be rejected. It expects a response.</p>
<p>An event says: "this happened." It's an immutable record of a fact. It doesn't expect a response. It doesn't care who handles it.</p>
<p><code>SaveUserToken</code> is a command. <code>UserLoggedIn</code> is an event.</p>
<p>When you model your system with events as facts, you get a historical record of everything that happened in your application. You can replay events to reconstruct state. You can add new handlers that process historical events. Your system becomes auditable and predictable in ways that command-driven systems are not.</p>
<h3 id="heading-modelling-domain-events-in-dart">Modelling Domain Events in Dart</h3>
<p>Events should be immutable value objects. They're facts. Facts don't change after they happen.</p>
<pre><code class="language-cpp">abstract class DomainEvent {
  final DateTime occurredAt;
  final String eventId;

  const DomainEvent({
    required this.occurredAt,
    required this.eventId,
  });
}
</code></pre>
<p><code>DomainEvent</code> is the base class for all events in the system. Every event has a timestamp (<code>occurredAt</code>) recording when it happened, and a unique identifier (<code>eventId</code>) for traceability.</p>
<pre><code class="language-cpp">class UserLoggedIn extends DomainEvent {
  final UserDto user;

  const UserLoggedIn({
    required this.user,
    required super.occurredAt,
    required super.eventId,
  });
}

class LoginFailed extends DomainEvent {
  final AppException error;

  const LoginFailed({
    required this.error,
    required super.occurredAt,
    required super.eventId,
  });
}
</code></pre>
<p><code>UserLoggedIn</code> carries the user data. <code>LoginFailed</code> carries the error. Both are immutable. Both have timestamps and identifiers. Both are concrete facts about something that happened in the domain.</p>
<h3 id="heading-a-type-safe-domaineventbus">A Type-Safe DomainEventBus</h3>
<p>Now we can build an event bus that's typed to domain events specifically:</p>
<pre><code class="language-cpp">abstract class EventHandler&lt;T extends DomainEvent&gt; {
  void handle(T event);
}
</code></pre>
<p><code>EventHandler&lt;T&gt;</code> is the Observer interface for this architecture. Any class that wants to handle a domain event implements this with the specific event type it cares about.</p>
<pre><code class="language-cpp">class DomainEventBus {
  final _handlers = &lt;Type, List&lt;EventHandler&gt;&gt;{};

  void register&lt;T extends DomainEvent&gt;(EventHandler&lt;T&gt; handler) {
    _handlers.putIfAbsent(T, () =&gt; []).add(handler);
  }

  void publish&lt;T extends DomainEvent&gt;(T event) {
    final handlers = List.of(_handlers[T] ?? []);
    for (final handler in handlers) {
      try {
        (handler as EventHandler&lt;T&gt;).handle(event);
      } catch (e) {
        debugPrint('[DomainEventBus] Handler error for ${T}: $e');
      }
    }
  }
}
</code></pre>
<p>Let's go through <code>DomainEventBus</code> carefully.</p>
<p><code>_handlers</code> is a map where the key is a <code>Type</code> (the event class itself, like <code>UserLoggedIn</code>) and the value is a list of all handlers registered for that event type.</p>
<p><code>register&lt;T&gt;</code> takes a handler and adds it to the list for type <code>T</code>. <code>putIfAbsent</code> ensures the list is created if this is the first handler for that event type.</p>
<p><code>publish&lt;T&gt;</code> looks up all handlers registered for the type of event being published and calls each one's <code>handle</code> method. The snapshot with <code>List.of()</code> and the per-handler try/catch are both present for the same reasons we established earlier.</p>
<p>Here's how you register handlers and publish events:</p>
<pre><code class="language-dart">// Registration happens once at startup
eventBus.register&lt;UserLoggedIn&gt;(TokenHandler(secureStorage));
eventBus.register&lt;UserLoggedIn&gt;(UserCacheHandler(userCache));
eventBus.register&lt;UserLoggedIn&gt;(NavigationHandler(navigationService));
eventBus.register&lt;UserLoggedIn&gt;(AnalyticsHandler(analyticsService));

eventBus.register&lt;PaymentConfirmed&gt;(ReceiptHandler(receiptService));
eventBus.register&lt;PaymentConfirmed&gt;(InventoryHandler(inventoryService));

// Publishing happens at the use case level
eventBus.publish(UserLoggedIn(
  user: user,
  occurredAt: DateTime.now(),
  eventId: const Uuid().v4(),
));
</code></pre>
<p>When <code>UserLoggedIn</code> is published, only its registered handlers fire. Payment handlers aren't touched. Every handler for <code>UserLoggedIn</code> runs independently with full error isolation.</p>
<h2 id="heading-application-in-domain-driven-design">Application in Domain-Driven Design</h2>
<p>Event-Driven Architecture and the Observer pattern find their most structured home inside Domain-Driven Design. DDD gives us the vocabulary and structure to know exactly where events belong, who creates them, and who handles them.</p>
<h3 id="heading-key-ddd-concepts-you-need-to-know">Key DDD Concepts You Need to Know</h3>
<p><strong>Domain Events</strong> are first-class citizens in DDD. They represent something meaningful that happened in the business domain. Not a technical detail, not an HTTP response, but a business fact.</p>
<p><code>UserLoggedIn</code> is a domain event. <code>LoginResponseDto</code> is a data transfer object. The distinction matters deeply. The event belongs to the domain model and expresses business language. The DTO belongs to the data layer and expresses data structure.</p>
<p><strong>Aggregates</strong> are the natural source of domain events. An Aggregate is a cluster of domain objects that form a consistency boundary. The Aggregate enforces business rules and raises domain events when significant state changes occur within it.</p>
<p><strong>Use Cases</strong> are the orchestrators. A use case calls the repository, gets the result, raises the appropriate domain event, and returns the outcome. It doesn't handle side effects directly. It announces what happened and lets the registered handlers take over.</p>
<h3 id="heading-where-everything-lives-in-clean-architecture">Where Everything Lives in Clean Architecture</h3>
<pre><code class="language-plaintext">lib/
  core/
    events/
      domain_event.dart           &lt;- Base DomainEvent class
      domain_event_bus.dart       &lt;- The DomainEventBus
      event_handler.dart          &lt;- Base EventHandler interface

  features/
    auth/
      domain/
        events/
          user_logged_in.dart     &lt;- Domain event (pure Dart, no Flutter)
          login_failed.dart       &lt;- Domain event
        handlers/
          token_handler.dart      &lt;- Handles token storage
          user_cache_handler.dart &lt;- Handles user caching
          analytics_handler.dart  &lt;- Handles analytics
        entities/
          user.dart
        repositories/
          auth_repository.dart    &lt;- Abstract interface only
        usecases/
          login_usecase.dart      &lt;- Orchestrates, publishes events

      data/
        repositories/
          auth_repository_impl.dart
        datasources/
          auth_remote_datasource.dart

      presentation/
        providers/
          login_provider.dart     &lt;- Riverpod notifier (thin)
        pages/
          login_page.dart
</code></pre>
<p>The critical rule: the domain layer is pure Dart. No Flutter imports. No Riverpod imports. No HTTP imports. The <code>DomainEventBus</code>, domain events, handlers, and use cases all live in the domain layer and have zero framework dependencies.</p>
<p>This means that the same domain logic works in Flutter, server-side Dart, or a CLI tool without changing a single line. Framework upgrades, say from Riverpod 2.x to a future version, never touch the domain. Unit tests for the domain run in milliseconds with no widget test overhead.</p>
<h3 id="heading-the-login-use-case-in-ddd">The Login Use Case in DDD</h3>
<pre><code class="language-cpp">class LoginUseCase {
  final AuthRepository _repository;
  final DomainEventBus _eventBus;

  LoginUseCase({
    required AuthRepository repository,
    required DomainEventBus eventBus,
  })  : _repository = repository,
        _eventBus = eventBus;

  Future&lt;Result&lt;UserDto, AppException&gt;&gt; execute(LoginRequest request) async {
    try {
      final user = await _repository.login(request);

      _eventBus.publish(UserLoggedIn(
        user: user,
        occurredAt: DateTime.now(),
        eventId: const Uuid().v4(),
      ));

      return Result.success(user);
    } on AppException catch (e) {
      _eventBus.publish(LoginFailed(
        error: e,
        occurredAt: DateTime.now(),
        eventId: const Uuid().v4(),
      ));

      return Result.failure(e);
    }
  }
}
</code></pre>
<p>Let's walk through this step by step.</p>
<p><code>LoginUseCase</code> receives two dependencies: an <code>AuthRepository</code> abstraction and a <code>DomainEventBus</code>. Neither is a concrete class. Both can be swapped in tests.</p>
<p>Inside <code>execute</code>, it calls the repository to perform the login. If the login succeeds, it publishes a <code>UserLoggedIn</code> event to the bus, which immediately notifies all registered handlers. Then it returns a <code>Result.success</code> wrapping the user data.</p>
<p>If an <code>AppException</code> is caught, it publishes a <code>LoginFailed</code> event to the bus, which notifies all failure handlers. Then it returns a <code>Result.failure</code> wrapping the error.</p>
<p>The use case doesn't know how many handlers are registered. It doesn't know what they do. It performs the operation, publishes the outcome as a domain event, and returns the result.</p>
<p>The <code>Result</code> type is a return value for the caller (the Riverpod notifier) to know the outcome. The domain event is the broadcast for all side effect handlers. Both travel from the same single use case call. This is what makes the architecture clean.</p>
<h2 id="heading-the-riverpod-hybrid-clean-architecture-in-practice">The Riverpod Hybrid: Clean Architecture in Practice</h2>
<p>This is where everything comes together in a real Flutter application.</p>
<h3 id="heading-the-problem-we-are-solving">The Problem We Are Solving</h3>
<p>There are two common pain points in Flutter apps that use Riverpod:</p>
<p>Fat ref.listen in widgets:</p>
<pre><code class="language-cpp">// This is messy
ref.listen&lt;AsyncValue&lt;UserDto?&gt;&gt;(loginProvider, (previous, next) {
  next.whenData((user) {
    if (user != null) {
      secureStorage.write(key: 'token', value: user.token);
      userCache.save(user);
      context.go('/home');
      analytics.track('login_success');
    }
  });
});
</code></pre>
<p>The widget is mounted. If it unmounts before all of this completes, some side effects may never run. Business consequences like token storage and navigation shouldn't depend on whether a widget is still alive. This is fragile architecture.</p>
<p>Fat notifiers:</p>
<pre><code class="language-dart">// Notifier doing too much
Future&lt;void&gt; login(LoginRequest request) async {
  state = const AsyncLoading();
  try {
    final user = await _loginUseCase.execute(request);
    await _secureStorage.write(key: 'token', value: user.token);
    await _userCache.save(user);
    _navigationService.navigateTo('/home');
    _analytics.track('login_success');
    state = AsyncData(user);
  } catch (e, st) {
    state = AsyncError(e, st);
  }
}
</code></pre>
<p>The notifier is violating the Single Responsibility Principle. It's performing the login, saving the token, caching the user, navigating, tracking analytics, and managing UI state. That's six responsibilities in one class. It's impossible to test cleanly and painful to maintain.</p>
<h3 id="heading-the-clean-rule">The Clean Rule</h3>
<p>Before looking at the solution, establish this rule clearly:</p>
<p><strong>The use case owns domain consequences. The notifier owns UI state. Widgets own nothing.</strong></p>
<p>The use case performs the operation and publishes domain events. Handlers fire when those events are published and run completely independently of the widget lifecycle. The notifier receives the result from the use case and emits loading, success, or error state so the UI knows what to display. Widgets read that state and render accordingly.</p>
<p>That's the full picture. And it means this architecture works correctly whether login is triggered from a widget, a biometric prompt, a deep link, or a background service. The use case always publishes. The handlers always fire. The notifier only deals with UI state.</p>
<h3 id="heading-understanding-asyncnotifier">Understanding AsyncNotifier</h3>
<p>Before writing the notifier, let's understand what <code>AsyncNotifier</code> is and how it works.</p>
<p><code>AsyncNotifier</code> is a Riverpod 2.0 class designed specifically for asynchronous state. It holds an <code>AsyncValue&lt;T&gt;</code>, which is a sealed type that can be one of three things:</p>
<p><code>AsyncData&lt;T&gt;</code> means the operation succeeded and data is available. <code>AsyncLoading</code> means an operation is in progress. <code>AsyncError</code> means an operation failed.</p>
<p>When you extend <code>AsyncNotifier&lt;T&gt;</code>, you implement a <code>build</code> method that returns the initial state, and you write methods that mutate <code>state</code> as async operations progress.</p>
<p>With code generation using <code>@riverpod</code>, you annotate your class and run <code>flutter pub run build_runner build</code>. The generator creates the provider and all the boilerplate automatically. You focus entirely on the logic.</p>
<p>Here's the full setup for code generation:</p>
<pre><code class="language-yaml"># pubspec.yaml
dependencies:
  flutter_riverpod: ^2.5.1
  riverpod_annotation: ^2.3.5

dev_dependencies:
  riverpod_generator: ^2.4.0
  build_runner: ^2.4.9
</code></pre>
<h3 id="heading-the-thin-notifier">The Thin Notifier</h3>
<pre><code class="language-cpp">// login_provider.dart
part 'login_provider.g.dart';

@riverpod
class LoginNotifier extends _$LoginNotifier {

  @override
  AsyncValue&lt;UserDto?&gt; build() {
    return const AsyncData(null);
  }

  Future&lt;void&gt; login(LoginRequest request) async {
    state = const AsyncLoading();

    final result = await ref.read(loginUseCaseProvider).execute(request);

    result.fold(
      onSuccess: (user) =&gt; state = AsyncData(user),
      onFailure: (error) =&gt; state = AsyncError(error, StackTrace.current),
    );
  }
}
</code></pre>
<p>Let's go through this line by line.</p>
<p><code>part 'login_provider.g.dart'</code> tells Dart that the generated file is part of this library. The <code>@riverpod</code> annotation and <code>_$LoginNotifier</code> base class come from the generated file.</p>
<p><code>build()</code> is the initialisation method. It runs when the provider is first read. It returns <code>AsyncData(null)</code>, meaning the initial state is a successful state with no user yet. This is correct because no login has been attempted.</p>
<p>Inside <code>login</code>, the first thing we do is set <code>state = const AsyncLoading()</code>. This immediately notifies any widget watching this provider that an operation is in progress. The UI can show a loading indicator.</p>
<p>We then call the use case and <code>await</code> its result. The use case returns a <code>Result&lt;UserDto, AppException&gt;</code>, which is a type that holds either a success value or a failure value, never both. We call <code>fold</code> on it to handle each case.</p>
<p>In the <code>onSuccess</code> branch, we set <code>state = AsyncData(user)</code>. This tells the UI the operation succeeded and here is the user data to render.</p>
<p>In the <code>onFailure</code> branch, we set <code>state = AsyncError(error, StackTrace.current)</code>. This tells the UI something went wrong so it can display the appropriate error state.</p>
<p>That's the entire notifier. It does exactly one thing: reflect the outcome of the use case as UI state.</p>
<p>Notice there's no token saving here. No navigation, caching, or analytics. All of that is already handled. The moment the use case called <code>_eventBus.publish(UserLoggedIn(...))</code> inside <code>execute</code>, every registered handler fired automatically. By the time <code>result</code> is returned to this notifier, all side effects are already done. The notifier just needs to update the UI.</p>
<p>This is the cleanest possible separation. The use case owns domain consequences. The notifier owns render state. Each has exactly one responsibility.</p>
<h3 id="heading-the-widget">The Widget</h3>
<pre><code class="language-cpp">class LoginPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final loginState = ref.watch(loginNotifierProvider);

    return Scaffold(
      body: loginState.when(
        data: (_) =&gt; const LoginForm(),
        loading: () =&gt; const Center(child: CircularProgressIndicator()),
        error: (error, _) =&gt; ErrorView(message: error.toString()),
      ),
    );
  }
}

class LoginForm extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: () {
            ref.read(loginNotifierProvider.notifier).login(
              LoginRequest(email: 'user@example.com', password: 'secret'),
            );
          },
          child: const Text('Login'),
        ),
      ],
    );
  }
}
</code></pre>
<p><code>ref.watch(loginNotifierProvider)</code> subscribes this widget to the notifier's state. Every time <code>state</code> changes inside the notifier, <code>build</code> is called again and the widget re-renders.</p>
<p><code>loginState.when</code> is how you handle each case of <code>AsyncValue</code>. When the state is <code>AsyncData</code>, it renders the login form. When it is <code>AsyncLoading</code>, it renders a loading indicator. When it is <code>AsyncError</code>, it renders the error view.</p>
<p>The widget knows nothing about tokens, navigation, or caching. It renders what it's told to render by the state. That's its entire job.</p>
<h3 id="heading-wiring-the-composition-root">Wiring the Composition Root</h3>
<p>All handler registrations happen once at app startup inside a Riverpod provider:</p>
<pre><code class="language-cpp">@riverpod
DomainEventBus eventBus(EventBusRef ref) {
  final bus = DomainEventBus();

  bus.register&lt;UserLoggedIn&gt;(
    TokenHandler(ref.read(secureStorageProvider)),
  );
  bus.register&lt;UserLoggedIn&gt;(
    UserCacheHandler(ref.read(userCacheProvider)),
  );
  bus.register&lt;UserLoggedIn&gt;(
    NavigationHandler(ref.read(navigationServiceProvider)),
  );
  bus.register&lt;UserLoggedIn&gt;(
    AnalyticsHandler(ref.read(analyticsServiceProvider)),
  );

  bus.register&lt;LoginFailed&gt;(
    AnalyticsFailureHandler(ref.read(analyticsServiceProvider)),
  );

  return bus;
}
</code></pre>
<p><code>eventBus</code> is a provider that creates the <code>DomainEventBus</code> and registers all handlers at the moment it is first read. Because Riverpod providers are lazy by default and cached after first creation, this runs once and the bus lives for the entire app session.</p>
<p>Every handler gets its dependencies injected via <code>ref.read</code>. Nothing is hardcoded. Everything is swappable in tests.</p>
<p>The <code>LoginUseCase</code> receives this event bus as a dependency through its own provider:</p>
<pre><code class="language-cpp">@riverpod
LoginUseCase loginUseCase(LoginUseCaseRef ref) {
  return LoginUseCase(
    repository: ref.read(authRepositoryProvider),
    eventBus: ref.read(eventBusProvider),
  );
}
</code></pre>
<p>This is the only place that connects the use case to the event bus. The notifier receives only the use case. The widget receives only the notifier's state. Each layer knows only about the layer directly below it and nothing else.</p>
<p>Adding a new side effect to login means creating a new handler class and adding one <code>bus.register</code> line in the composition root. The notifier, the use case logic, the widget, and every existing handler remain completely untouched.</p>
<h2 id="heading-testing-the-observer-architecture">Testing the Observer Architecture</h2>
<p>One of the most significant advantages of this architecture is how clearly it separates test concerns. Each layer has its own focused test scope.</p>
<h3 id="heading-testing-the-use-case">Testing the Use Case</h3>
<pre><code class="language-cpp">void main() {
  group('LoginUseCase', () {
    late LoginUseCase useCase;
    late MockAuthRepository mockRepository;
    late MockDomainEventBus mockEventBus;

    setUp(() {
      mockRepository = MockAuthRepository();
      mockEventBus = MockDomainEventBus();
      useCase = LoginUseCase(
        repository: mockRepository,
        eventBus: mockEventBus,
      );
    });

    test('publishes UserLoggedIn event on success', () async {
      final user = UserDto(id: '1', token: 'token123');
      when(() =&gt; mockRepository.login(any())).thenAnswer((_) async =&gt; user);

      await useCase.execute(LoginRequest(email: 'a@b.com', password: '123'));

      verify(() =&gt; mockEventBus.publish(any&lt;UserLoggedIn&gt;())).called(1);
    });

    test('publishes LoginFailed event on error', () async {
      when(() =&gt; mockRepository.login(any()))
          .thenThrow(AppException.unauthorized(message: 'Invalid credentials'));

      await useCase.execute(LoginRequest(email: 'a@b.com', password: 'wrong'));

      verify(() =&gt; mockEventBus.publish(any&lt;LoginFailed&gt;())).called(1);
    });
  });
}
</code></pre>
<p>The use case test mocks the repository and the event bus. It verifies that the correct event type was published for each outcome. It doesn't test what any handler does. That's not the use case's responsibility, so it's not the use case's test.</p>
<h3 id="heading-testing-each-handler">Testing Each Handler</h3>
<pre><code class="language-cpp">void main() {
  group('TokenHandler', () {
    late TokenHandler handler;
    late MockSecureStorageService mockStorage;

    setUp(() {
      mockStorage = MockSecureStorageService();
      handler = TokenHandler(mockStorage);
    });

    test('writes token to secure storage on UserLoggedIn', () {
      final event = UserLoggedIn(
        user: UserDto(id: '1', token: 'abc123'),
        occurredAt: DateTime.now(),
        eventId: 'event-1',
      );

      handler.handle(event);

      verify(
        () =&gt; mockStorage.write(key: 'auth_token', value: 'abc123'),
      ).called(1);
    });
  });
}
</code></pre>
<p>Each handler test is tiny. It creates the handler with a mocked dependency, fires the event, and verifies the exact side effect that handler is responsible for. No other handler is involved, no notifier is involved, and no widget is involved.</p>
<h3 id="heading-testing-the-notifier">Testing the Notifier</h3>
<pre><code class="language-cpp">void main() {
  group('LoginNotifier', () {
    test('transitions from loading to data on success', () async {
      final mockUseCase = MockLoginUseCase();
      final user = UserDto(id: '1', token: 'token123');

      when(() =&gt; mockUseCase.execute(any()))
          .thenAnswer((_) async =&gt; Result.success(user));

      final container = ProviderContainer(overrides: [
        loginUseCaseProvider.overrideWithValue(mockUseCase),
      ]);

      final notifier = container.read(loginNotifierProvider.notifier);

      await notifier.login(LoginRequest(email: 'a@b.com', password: '123'));

      expect(
        container.read(loginNotifierProvider),
        isA&lt;AsyncData&lt;UserDto?&gt;&gt;(),
      );
    });

    test('transitions from loading to error on failure', () async {
      final mockUseCase = MockLoginUseCase();
      final error = AppException.unauthorized(message: 'Invalid credentials');

      when(() =&gt; mockUseCase.execute(any()))
          .thenAnswer((_) async =&gt; Result.failure(error));

      final container = ProviderContainer(overrides: [
        loginUseCaseProvider.overrideWithValue(mockUseCase),
      ]);

      final notifier = container.read(loginNotifierProvider.notifier);

      await notifier.login(LoginRequest(email: 'a@b.com', password: 'wrong'));

      expect(
        container.read(loginNotifierProvider),
        isA&lt;AsyncError&gt;(),
      );
    });
  });
}
</code></pre>
<p>The notifier test only verifies state transitions. It doesn't need to mock the event bus because the notifier no longer touches the event bus. That's the use case's job, and the use case has its own test that verifies events are published correctly. Each layer is tested in complete isolation with no overlap.</p>
<h2 id="heading-when-to-use-the-observer-pattern">When to Use the Observer Pattern</h2>
<p>Use Observer when:</p>
<ul>
<li><p>One event needs to trigger multiple independent reactions</p>
</li>
<li><p>You want to add or remove reactions without modifying the event source</p>
</li>
<li><p>Side effects need to be decoupled from business logic</p>
</li>
<li><p>Each reaction should be independently testable</p>
</li>
<li><p>Multiple parts of the system need to react to the same state change</p>
</li>
<li><p>You are building a feature that will grow in number of side effects over time</p>
</li>
</ul>
<h2 id="heading-when-not-to-use-it">When Not to Use It</h2>
<p>Avoid Observer when:</p>
<ul>
<li><p>You have only one consumer and no realistic expectation of more</p>
</li>
<li><p>The relationship between producer and consumer is simple and direct</p>
</li>
<li><p>The pattern adds structural overhead without meaningful benefit</p>
</li>
<li><p>Streams, ChangeNotifier, or Riverpod's built-in reactivity already solve the problem naturally</p>
</li>
<li><p>Strict ordering of side effects is critical and fan-out makes that hard to guarantee</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The Observer Design Pattern is one of the most important tools in a software engineer's arsenal. This isn't because it's clever, but because it solves a problem every growing application faces: how do you let one event trigger many reactions without turning your codebase into a tightly coupled mess?</p>
<p>You started by understanding the pattern at its core. A Subject holds a list of Observers and notifies them when events occur. You saw it built step by step in Dart, with snapshot iteration to prevent concurrent modification errors, per-observer try/catch to prevent failure cascades, and dependency inversion to keep everything testable.</p>
<p>You discovered that the Observer pattern is already embedded in Flutter's Streams, ChangeNotifier, and BLoC. Understanding its foundations means you understand why those tools work the way they do.</p>
<p>You then took the pattern into Event-Driven Architecture, where events become immutable domain facts and the system is composed of producers and consumers with no direct coupling between them.</p>
<p>You applied it inside Domain-Driven Design, giving events a proper home in a pure Dart domain layer that is framework-independent, fully portable, and fully testable.</p>
<p>And you saw how it integrates with Riverpod through a hybrid architecture with a clear and enforced rule: handlers own side effects, the notifier owns UI state, and widgets own nothing.</p>
<p>The result is a codebase that scales gracefully. When a new side effect needs to be added, you create one handler and register it in one place. Nothing else changes. That's the promise of the Observer pattern. And as you've seen throughout this handbook, it's a promise it keeps.</p>
<p>Happy Coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How (and why) to embed domain concepts in code ]]>
                </title>
                <description>
                    <![CDATA[ Code should clearly reflect the problem it’s solving, and thus openly expose that problem’s domain. Embedding domain concepts in code requires thought and skill, and doesn't drop out automatically from TDD. However, it is a necessary step on the road... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/embedding-domain-concepts-in-code/</link>
                <guid isPermaLink="false">66bb925a867a396452a80286</guid>
                
                    <category>
                        <![CDATA[ Quality Software ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Code Quality ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #Domain-Driven-Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Cedd Burge ]]>
                </dc:creator>
                <pubDate>Tue, 12 Nov 2019 07:48:19 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/11/2015-Gran-Paradiso-007.JPG" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Code should clearly reflect the problem it’s solving, and thus openly expose that problem’s domain. Embedding domain concepts in code requires thought and skill, and doesn't drop out automatically from TDD. However, it is a necessary step on the road to writing easily understandable code.</p>
<p>I was at a software craftsmanship meetup recently, where we formed pairs to solve a simplified Berlin Clock Kata. A Berlin Clock displays the time using rows of flashing lights, which you can see below (although in the kata we just output a text representation, and the lights in a row are all the same colour).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/11/berlin-clock-2.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-initial-test-driven-solution">Initial Test Driven solution</h2>
<p>Most pairs used inside out TDD, and there were a lot of solutions that looked something like this (complete <a target="_blank" href="https://github.com/ceddlyburge/berlin-clock-initial-tdd-solution/blob/master/BerlinClock.py">code available on GitHub</a>).</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">berlin_clock_time</span>(<span class="hljs-params">julian_time</span>):</span>
    hours, minutes, seconds = list(map(int, julian_time.split(<span class="hljs-string">":"</span>)))

    <span class="hljs-keyword">return</span> [
        seconds_row_lights(seconds % <span class="hljs-number">2</span>)
        , five_hours_row_lights(hours)
        , single_hours_row_lights(hours % <span class="hljs-number">5</span>)
        , five_minutes_row_lights(minutes)
        , single_minutes_row_lights(minutes % <span class="hljs-number">5</span>)
    ]

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">five_hours_row_lights</span>(<span class="hljs-params">hours</span>):</span>
    lights_on = hours // <span class="hljs-number">5</span>
    lights_in_row = <span class="hljs-number">4</span>
    <span class="hljs-keyword">return</span> lights_for_row(<span class="hljs-string">"R"</span>, lights_on, lights_in_row)

<span class="hljs-comment"># ...</span>
</code></pre>
<p>This type of solution drops out naturally from applying inside out TDD to the problem. You write some tests for the seconds row, then some tests for the five hours row, and so on, and then you put it all together and do some refactoring. This solution does expose some of the domain concepts at a glance:</p>
<ul>
<li>There are 5 rows</li>
<li>There is one second row, 2 hour rows and 2 minute rows</li>
</ul>
<p>Some more concepts are available after a bit of digging, but aren't immediately obvious. The rows are made up of lights that can be on (or presumably off), and that the number of lights on is an indication of the time.</p>
<p>However there are some big parts of the problem that are not exposed. And since I haven't yet explained it, you probably don't know exactly how the Berlin Clock works yet.</p>
<h2 id="heading-elevate-the-concepts">Elevate the concepts</h2>
<p>To improve this we can bring some of the details that are buried in the helper functions (such as <code>get_five_hours</code>) closer to the top of the file. This brings you to something like the following (complete <a target="_blank" href="https://github.com/ceddlyburge/berlin-clock-elevated-concepts/blob/master/BerlinClock.py">code available on GitHub</a>), although the downside is that it breaks nearly all of the tests. Solutions like this are rarer on GitHub, but do exist.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">berlin_clock_time</span>(<span class="hljs-params">julian_time</span>):</span>
    hours, minutes, seconds = list(map(int, julian_time.split(<span class="hljs-string">":"</span>)))

    single_seconds = seconds_row_lights(seconds % <span class="hljs-number">2</span>)
    five_hours = row_lights(
        light_colour=<span class="hljs-string">"R"</span>,
        lights_on=hours // <span class="hljs-number">5</span>,
        lights_in_row=<span class="hljs-number">4</span>)
    single_hours = row_lights(
        light_colour=<span class="hljs-string">"R"</span>,
        lights_on=hours % <span class="hljs-number">5</span>,
        lights_in_row=<span class="hljs-number">4</span>)
    five_minutes = row_lights(
        light_colour=<span class="hljs-string">"Y"</span>,
        lights_on=minutes // <span class="hljs-number">5</span>,
        lights_in_row=<span class="hljs-number">11</span>)
    single_minutes = row_lights(
        light_colour=<span class="hljs-string">"Y"</span>,
        lights_on=minutes % <span class="hljs-number">5</span>,
        lights_in_row=<span class="hljs-number">4</span>)

    <span class="hljs-keyword">return</span> [
        single_seconds,
        five_hours,
        single_hours,
        five_minutes,
        single_minutes
    ]

<span class="hljs-comment"># ...</span>
</code></pre>
<p>This improves the concepts that are now exposed at a glance:</p>
<ul>
<li>There are 5 rows</li>
<li>The seconds row is a special case</li>
<li>There are 2 hour rows and 2 minute rows</li>
<li>The rows use different colour lights</li>
<li>The rows have a different number of lights</li>
</ul>
<p>This is pretty good, and is already better that most of the solutions out there. However, it's still a bit mysterious how the rows are related to each other (there are 2 rows to display the hours and the minutes, so presumably these are linked). It's also not obvious what amount of time each light represents.</p>
<h2 id="heading-name-implicit-concepts">Name implicit concepts</h2>
<p>At the moment some of the concepts (such as the amount of time each light represents) are implicit in the code. Making these explicit, and naming them, forces us to understand them and to embed that understanding in the code.</p>
<p>In order to make the amount of time each light represents explicit, it seems like it would be sensible to pass a <code>time_per_light</code> value to <code>row_lights</code>. This means we have to push the calculation of <code>lights_on</code> down into <code>row_lights</code>.</p>
<p>This in turn makes it obvious that there are two kinds of rows: one related to the quotient (<code>\\</code>) of the time value, and one related to the remainder / modulus (<code>%</code>). If we look at the quotient case, we see that the 2nd parameter to the operation is the <code>time_per_light</code>, which is 5 in both cases (5 hours in one case and 5 minutes in the other).</p>
<p>This allows us to write these rows like this:</p>
<pre><code class="lang-python">five_hour_row = row_lights(
    time_per_light=<span class="hljs-number">5</span>,
    value=hours, 
    light_colour=<span class="hljs-string">"R"</span>,
    lights_in_row=<span class="hljs-number">4</span>)
</code></pre>
<p>If we now turn our attention to the remainder case, we realise that <code>time_per_light</code> is always singular (one hour or one minute), as it is filling in the gaps in the quotient case. </p>
<p>For example, the five hours row can represent 0, 5, 10, 15, or 20 hours, but nothing in between. In order to represent any hour, there must be another row to represent +1, +2, +3 and +4. This means that this row must have exactly 4 lights, and that each light must represent 1 hour.</p>
<p>This implies that the remainder case is dependent on the quotient one, which most people would describe as a parent / child relationship.</p>
<p>With this knowledge in hand, we can now create a function for the child remainder rows, and the solution now looks like this (complete <a target="_blank" href="https://github.com/ceddlyburge/berlin-clock">code on GitHub</a>):</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">berlin_clock_time</span>(<span class="hljs-params">julian_time</span>):</span>
    hours, minutes, seconds = list(map(int, julian_time.split(<span class="hljs-string">":"</span>)))

    <span class="hljs-keyword">return</span> [
        seconds_row_lights(
            seconds % <span class="hljs-number">2</span>),
        parent_row_lights(
            time_per_light=<span class="hljs-number">5</span>,
            value=hours, 
            light_colour=<span class="hljs-string">"R"</span>,
            lights_in_row=<span class="hljs-number">4</span>),
        child_remainder_row_lights(
            parent_time_per_light=<span class="hljs-number">5</span>,
            value=hours,
            light_colour=<span class="hljs-string">"R"</span>),
        parent_row_lights(
            time_per_light=<span class="hljs-number">5</span>,
            value=minutes, 
            light_colour=<span class="hljs-string">"Y"</span>,
            lights_in_row=<span class="hljs-number">11</span>),
        child_remainder_row_lights(
            parent_time_per_light=<span class="hljs-number">5</span>,
            light_colour=<span class="hljs-string">"Y"</span>,
            value=minutes)
    ]

<span class="hljs-comment"># ...</span>
</code></pre>
<p>A quick glance at this code now reveals nearly all the domain concepts</p>
<ul>
<li>The first row represents the seconds and is a special case</li>
<li>On the second row each "R" light represents 5 hours</li>
<li>The third row shows the remainder from the second</li>
<li>On the fourth row each "Y" light represents 5 hours</li>
<li>The fifth row shows the remainder from the fourth</li>
</ul>
<p>This took something thinking about, which will have cost us some time / money. But we increased our understanding of the problem while we did it, and most importantly we embedded that knowledge in to the code. This means that the next person to read the code will not have to do this, which will save some time / money. Since we spend about 10 times longer reading code than we do writing it, this is probably a worthwhile endeavour.</p>
<p>Embedding this understanding has also made it harder for future programmers to make mistakes. For example, the concept of parent / child rows didn't exist in earlier examples, and it would be easy to mismatch them. Now the concept is plain to see, and the values are mostly worked out for you. It is also easier to refactor to support new clock variants, for example where lights in the first hours row represent 6 hours.</p>
<h2 id="heading-how-far-should-you-take-it">How far should you take it?</h2>
<p>There are things we can do to take this further. For example the <code>parent_time_per_light</code> of a child row must match the <code>time_per_light</code> of its parent, and there is nothing enforcing this. There is also a relationship between <code>time_per_light</code> and <code>lights_in_row</code> for the parent rows, and again it is not enforced. </p>
<p>However, at the moment we are only required to support one clock variant, so these probably aren't worth doing. When a change is required for the code, we should refactor so that the change is easy (which might be hard) and then make the easy change.</p>
<h2 id="heading-conclusions">Conclusions</h2>
<p>Embedding domain concepts in code requires thought and skill, and TDD won't necessarily do it for you. It takes longer than a naive solution, but makes the code easier to understand, and will very likely save time in the medium term. Time is money, and finding the right balance of spending time now versus saving time later is also an important skill for a professional programmer to have. </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Event sourcing essentials you need to know when starting out ]]>
                </title>
                <description>
                    <![CDATA[ By Noël Widmer Event Sourcing is a thought challenge when starting out. In this story, I will describe my experiences from the perspective of an engineer. My goal is to help you decide if you want to invest the resources to get started with Event Sou... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/event-sourcing-essentials-you-need-to-know-when-starting-out-13af35d9f932/</link>
                <guid isPermaLink="false">66c349e89972b7c5c7624e3b</guid>
                
                    <category>
                        <![CDATA[ #Domain-Driven-Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Event Sourcing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Productivity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technology ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Fri, 15 Mar 2019 18:47:14 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*6-84rquRE2oH4sJXRIabKA.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Noël Widmer</p>
<p>Event Sourcing is a thought challenge when starting out. In this story, I will describe my experiences from the perspective of an engineer.</p>
<p>My goal is to help you decide if you want to invest the resources to get started with Event Sourcing.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/vRIbN9ppLB4ixj1hzxbGQ6AcoQKdy-8Fi-xn" alt="Image" width="800" height="330" loading="lazy">
_[The Matrix](https://www.amazon.com/Complete-Trilogy-Reloaded-Revolutions-Blu-ray/dp/B001CEE1YE/ref=sr_1_6?keywords=the+matrix&amp;qid=1550756146&amp;s=movies-tv&amp;sr=1-6" rel="noopener" target="<em>blank" title="): Neo is offered a choice.</em></p>
<h3 id="heading-about-the-author">About The Author</h3>
<p>Hi ? I’m Noël. I live near Zurich (Switzerland) and am proudly working on Switzerland’s largest E-Commerce platform. My team and I have applied Event Sourcing throughout the last 12 months, and w<strong>e learned a lot.</strong></p>
<p>I want to share four essentials with you that I wish we knew one year ago.</p>
<h3 id="heading-a-tiny-introduction-to-event-sourcing">A Tiny Introduction To Event Sourcing</h3>
<p>This article is not about introducing you to the concepts of Event Sourcing.<br>I still feel like I should take <strong>one step</strong> back though — to paint a clearer picture.</p>
<p>In state-oriented applications, you store the result of some computation in your data store. You might also keep a log where you archive old states for auditing or debugging purposes. But by storing state you lose the information about the transition from one state to the other. If a user’s username has disappeared, you’ll be wondering how that could have happened.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/w-5nI8fUU3m3DDBwt8QBX-g1g5YtG5F7A4OU" alt="Image" width="605" height="235" loading="lazy">
<em>Storing state in a state-oriented application.</em></p>
<p>Sourcing events preserves that information. This is achieved by storing state transitions rather than the resulting state.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/H9prmbzan1lBxdV6YTG2H9xzWvxT8iLzrXZl" alt="Image" width="605" height="239" loading="lazy">
<em>Storing state transitions (events) in an event sourced application.</em></p>
<p>The current state can be restored by applying all events to an empty canvas. That means we can still access the current state but need to invest computational resources to do so.</p>
<p>There are many articles on the web that go into more detail. Martin Fowler <a target="_blank" href="https://martinfowler.com/eaaDev/EventSourcing.html">wrote one about it</a>. And Greg Young <a target="_blank" href="https://youtu.be/kZL41SMXWdM?t=2">talks a lot about Event Sourcing</a>. Greg is so obsessed with event sourcing that <a target="_blank" href="https://eventstore.org/">he implemented a data store which is specifically designed for event sourcing</a>. My team is using Greg’s event store — it’s great!</p>
<h3 id="heading-1-event-sourcing-is-not-a-silver-bullet">1) Event Sourcing Is Not A Silver Bullet</h3>
<p>Once you fall for the bitter sweet taste of Event Sourcing it becomes compelling to apply the concept to all your problems.</p>
<p>This will provide you with loads of data to analyze and you will have a powerful foundation to detect interesting correlations in it. You’ll be able to detect inefficient processes and make them more efficient.</p>
<p>And every engineer’s (my) favorite:</p>
<p>Tracking down a bug becomes much easier when you can “time travel” to the exact moment when the bug happened. In the end you will save time and money.</p>
<p>Well — not quite. ☝️</p>
<p>Sourcing your events sure allows you to do the analysis. <strong>But you still need to do it.</strong> This will cost time. Luckily, “time travel” comes for free. Enhanced debugging is thus guaranteed from day one.</p>
<p>Seth Godin wrote a <a target="_blank" href="https://seths.blog/2019/02/the-am-pm-problem-the-curse-of-too-much-data/">great blog post</a> on the subject. It’s a 2 minute read, so check it out.</p>
<p>Now you know about the price you’ll pay if you use your events for analytical purposes which is the main business benefit of Event Sourcing after all.</p>
<p>There is another cost though. Event Sourcing will increase the complexity of your application. Instead of dealing with your application’s current state you’ll have to deal with everything that has ever happened since it went live. Events that are no longer used will remain in your data store and you’ll have to support them for a long time.</p>
<p>It’s great if you deploy a feature and keep iterating on it. Just be aware that there are now multiple versions of that feature’s events in your data store and you’ll have to support all of them. Even if you no longer create new instances of those events.</p>
<p>Event Sourcing imposes additional time complexities that you will have to get used to. Apply it where you see a non-zero chance that the collected data becomes relevant. Where I’d define “relevant” as:</p>
<ul>
<li>the data could give you more insight into your domain</li>
<li>the data could help your business to improve their processes on their own</li>
<li>the data could help you find bugs faster</li>
</ul>
<p>Note that I added the word “could” in each of the last three bullet points. It’s likely you won’t know the exact benefit of Event Sourcing in advance. Make your best guess.</p>
<h3 id="heading-2-recognize-the-functional-nature-of-event-sourcing">2) Recognize The Functional Nature Of Event Sourcing</h3>
<p>The authors of the famous <a target="_blank" href="https://www.amazon.com/Patterns-Principles-Practices-Domain-Driven-Design/dp/1118714709">Patterns, Principles, and Practices of Domain-Driven Design</a> recommend object-oriented programming languages like C# or Java.</p>
<p>I assume that the implicit reasoning behind that recommendation is the number of people using such languages. It’s true. New team members will likely have an easier start into the domain when confronted with familiar languages.</p>
<p>But I disagree. <a target="_blank" href="https://youtu.be/kZL41SMXWdM?t=2">So does Greg Young</a>. <strong>Event Sourcing is not an object-oriented concept.</strong></p>
<p>One might argue that state transitions are objects too. And indeed. You can model everything as objects. Just because you can does not imply that this is the best model to use.</p>
<p>Consider using a language that supports functional paradigms. Especially <a target="_blank" href="https://en.wikipedia.org/wiki/Tagged_union">tagged unions</a> and <a target="_blank" href="https://en.wikipedia.org/wiki/Pattern_matching">pattern matching</a> are extremely valuable. Working with your events will feel natural and you won’t have to fight your language’s type system. If your team has no experience with the functional world it might be best to stick to familiar languages though.</p>
<p>Even more important to understand is that <strong>choosing a relational data store will be a painful experience</strong>. You’re not dealing with relational data when sourcing your events. Each event has its own schema which can change over time. Using a relational data store will hurt.</p>
<blockquote>
<p><a target="_blank" href="https://youtu.be/kZL41SMXWdM?t=1597">SQL is the master of nothing but it sucks at nothing.</a> - Greg Young</p>
</blockquote>
<h3 id="heading-3-expect-a-steep-learning-curve">3) Expect A Steep Learning Curve</h3>
<p>As with all new things there will be learning involved. Don’t try to sidestep it. That won’t work.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/0jNwGHRNDnjJDzwwr2h76zYC7XkT91P1oIv2" alt="Image" width="800" height="331" loading="lazy">
_[The Matrix](https://www.amazon.com/Complete-Trilogy-Reloaded-Revolutions-Blu-ray/dp/B001CEE1YE/ref=sr_1_6?keywords=the+matrix&amp;qid=1550756146&amp;s=movies-tv&amp;sr=1-6" rel="noopener" target="<em>blank" title="): Neo learns something special.</em></p>
<p>A great way to learn is to prototype. Try this:</p>
<ul>
<li>build at least one prototype of your first event sourced application</li>
<li>run and observe your prototype for a while</li>
<li>implement new features and get used to iterating on what you’ve built</li>
</ul>
<p>Iterate as often as possible before your go live. Once you are live it won’t be as easy to implement new learnings.</p>
<p>Go live once your team feels confident with maintaining the application.</p>
<p>Also think about how your team will introduce new team members to Event Sourcing. New joiners are already in an overwhelming position and learning new concepts won’t make it any easier for them.</p>
<p>Figure out a way to introduce them to Event Sourcing in a soft and safe way. This is important to figure out once you start getting new team members. It’s fine to delay it until that happens.</p>
<h3 id="heading-4-prepare-for-political-debates">4) Prepare for political debates</h3>
<p>Have you realized that your company wants <strong>cheap</strong> and <strong>quality</strong> results <strong>today</strong>?</p>
<p>Oh my, who am I talking to — of course you did. ?</p>
<p>Will <em>they</em> like it when you experiment with a mind-bending concept <em>they</em> might never heard of? And what is your company’s tech stack like? Do you usually use object-oriented programming languages in combination with relational data stores? Will <em>they</em> like it when you switch to a functional tech stack?</p>
<p>Depending on your companies culture you might have to fight your colleagues on multiple fronts. Act as an example. Tell the truth. You know it will be hard, especially in the beginning. Share your concerns. Make sure everyone involved knows the risks and what you’ll do to prevent them.</p>
<p>And don’t leave out how you picture the Event Sourcing paradise. Get the business on board by allowing them to build reports based on your valuable events. They’ll love it.</p>
<p>Get the concerned engineering people on board by sharing the improved debugging experience. Engineers are stubborn, they might still not like it.</p>
<p>I found it useful to practice such debates with my colleagues. Establish a safe environment for training and encourage your team members to take part.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/Y7d79Cv8qbHMWOoWken3t-86JsVOnSWRJ-n3" alt="Image" width="800" height="330" loading="lazy">
_[The Matrix](https://www.amazon.com/Complete-Trilogy-Reloaded-Revolutions-Blu-ray/dp/B001CEE1YE/ref=sr_1_6?keywords=the+matrix&amp;qid=1550756146&amp;s=movies-tv&amp;sr=1-6" rel="noopener" target="<em>blank" title="): Neo trains in a safe environment.</em></p>
<p>Be ready and know your stuff. You’ll make it! ?</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>Let me summarize.</p>
<ul>
<li>More data isn’t always good.</li>
<li>Additional data without the time to analyze it is useless.</li>
<li>Analysis without the intention and time to act on the result is useless.</li>
<li>Event Sourcing enhances the ability to debug your application.</li>
<li>Event Sourcing is a functional paradigm.</li>
<li>Expect a steep learning curve.</li>
<li>Not everybody will be happy about your plans.</li>
</ul>
<p>That’s it. My intention was to prepare your expectations to my experiences. Don’t worry. Be determined and <strong>choose the red pill!</strong></p>
<h3 id="heading-the-matrix">The Matrix</h3>
<p>All images in this story are borrowed from the movie <a target="_blank" href="https://www.amazon.com/Complete-Trilogy-Reloaded-Revolutions-Blu-ray/dp/B001CEE1YE/ref=sr_1_6?keywords=the+matrix&amp;qid=1550756146&amp;s=movies-tv&amp;sr=1-6">The Matrix</a>.</p>
<p>I’ve spent 9 years writing object-oriented code and working with relational data stores. About two years ago I started to experiment with functional code and non-relational data stores. The mind shift has been one of the most important lessons in my career to date.</p>
<p>Question your environment. Find the flaws. Exit the Matrix and get to experience a whole new world.</p>
<p>Farewell. Until next time. And good luck!</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/nCTTljApCmGwNVYDCtgOabSoZ6V7wjQIvAC0" alt="Image" width="800" height="331" loading="lazy">
_[The Matrix](https://www.amazon.com/Complete-Trilogy-Reloaded-Revolutions-Blu-ray/dp/B001CEE1YE/ref=sr_1_6?keywords=the+matrix&amp;qid=1550756146&amp;s=movies-tv&amp;sr=1-6" rel="noopener" target="<em>blank" title="): Neo receives a goodbye gift.</em></p>
<p><strong>I only write about programming and technology. If you follow me, I won’t waste your time.</strong> ?</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
