176
architectural DDD scalability

Self-Contained Systems

Self-Contained Systems — component diagram
Plate 176 component diagram

Self-Contained Systems is an architectural pattern where an application is structured as a suite of independently deployable services, each with its own database and logic. These systems are designed to be loosely coupled, communicating with each other via well-defined APIs, but without sharing databases or internal state. This approach promotes autonomy, allowing teams to develop, deploy, and scale individual systems independently.

The core principle is to minimize dependencies between components. Each system is responsible for its own data consistency and availability. This contrasts with monolithic architectures or shared-database approaches, where changes in one part of the system can have cascading effects on others. This pattern is often used in microservice architectures, but can be applied at a coarser granularity as well.

Usage

This pattern is commonly used in:

  • Microservice Architectures: The most prevalent use case, where each microservice embodies a self-contained system.
  • Large-Scale Applications: Breaking down a large application into smaller, manageable systems improves maintainability and scalability.
  • Organizations with Multiple Teams: Allows teams to own and operate their systems independently, fostering agility and ownership.
  • Systems Requiring High Availability: Isolating failures within a single system prevents them from impacting the entire application.
  • Event-Driven Architectures: Systems can react to events published by other systems without direct coupling.

Examples

  • Netflix: Netflix famously adopted a microservice architecture built on self-contained systems. Each component, like the recommendation engine, video encoding pipeline, or user account management, operates as an independent service with its own data store. This allows Netflix to scale individual features based on demand and deploy updates without disrupting the entire platform.
  • Amazon: Amazon’s e-commerce platform is composed of numerous self-contained systems. For example, the ordering system, the payment processing system, and the shipping system each have their own databases and logic. This separation allows Amazon to handle massive transaction volumes and maintain high availability, even during peak shopping seasons.
  • Shopify: Shopify utilizes self-contained systems for different aspects of its platform, such as the storefront, order management, and payment gateway integrations. This allows for independent scaling and development of each feature, catering to the diverse needs of its merchants.

Specimens

15 implementations
Specimen 176.01 Dart View specimen ↗

The Self-Contained System pattern focuses on encapsulating all dependencies within a single unit (like a class or function). This eliminates external dependencies, making the system portable, testable, and easier to reason about. It promotes loose coupling and simplifies deployments.

Here, we implement a simple calculator as a self-contained system using a class. All calculator logic, including parsing and performing operations, is encapsulated within the Calculator class, without reliance on external state or libraries beyond Dart’s core functionality. This illustrates how to achieve modularity and reduce dependencies, fitting Dart’s class-based structure and promoting code organization. The calculate method handles all operation logic, ensuring a clear entry point and contained behavior.

// main.dart
class Calculator {
  String calculate(String expression) {
    try {
      // Basic parsing and evaluation.  Could use a more robust library
      // if complexity increased, but keeping it contained.
      final List<String> parts = expression.split(' ');
      if (parts.length != 3) {
        return "Invalid expression";
      }

      final num operand1 = double.parse(parts[0]);
      final String operator = parts[1];
      final num operand2 = double.parse(parts[2]);

      num result;
      switch (operator) {
        case '+':
          result = operand1 + operand2;
          break;
        case '-':
          result = operand1 - operand2;
          break;
        case '*':
          result = operand1 * operand2;
          break;
        case '/':
          if (operand2 == 0) {
            return "Division by zero";
          }
          result = operand1 / operand2;
          break;
        default:
          return "Invalid operator";
      }

      return result.toString();
    } catch (e) {
      return "Invalid input";
    }
  }
}

void main() {
  final calculator = Calculator();
  print(calculator.calculate('10 + 5'));
  print(calculator.calculate('20 / 4'));
  print(calculator.calculate('3 * 7'));
  print(calculator.calculate('12 - 6'));
  print(calculator.calculate('5 + ')); // Invalid
  print(calculator.calculate('10 / 0')); // Division by zero
}