Skip to main content

Architectural Pattern

SimulationBank follows Hexagonal Architecture (also known as Ports and Adapters) combined with Domain-Driven Design (DDD) principles. This architecture ensures:
  • Clean separation of concerns
  • Testability and maintainability
  • Technology independence at the core
  • Clear dependency flow from outside-in

System Architecture

Layer Responsibilities

Domain Layer (Core)

The innermost layer contains:
  • Entities: Customer, Teller, DiscreteEventSimulation
  • Value Objects: SimulationConfig, Priority, TransactionType
  • Domain Events: SimulationEvent with event types
  • Ports: Interfaces like CustomerGenerator, MetricsRepository
The domain layer has zero dependencies on external frameworks. It contains pure business logic.

Application Layer

Orchestrates domain logic through use cases:
  • run_simulation.py - Execute simulation
  • generate_customer.py - Create customer instances
  • record_customer_served.py - Track metrics
  • get_simulation_report.py - Generate reports

Infrastructure Layer

Provides concrete implementations:
  • HTTP Adapters: Flask blueprints and controllers
  • Generators: ConfigurableGenerator (Poisson distribution)
  • Persistence: InMemoryMetricsRepository
  • Frontend: React components and state management

Bounded Contexts

The system is organized into bounded contexts:

Simulation

Core simulation engine, event processing, and lifecycle management

Customer

Customer generation, attributes, and state transitions

Teller

Teller resources, service operations, and utilization

Queue

Priority queue implementation and ordering logic

Metrics

Metrics collection, aggregation, and reporting

Shared

Shared kernel with common utilities and types

Dependency Rule

Dependencies flow inward only:
Infrastructure → Application → Domain
  • Domain layer depends on nothing
  • Application layer depends only on domain
  • Infrastructure layer depends on both application and domain
Never let the domain layer depend on infrastructure concerns like Flask, databases, or external APIs.

Technology Stack

Backend

  • Language: Python 3.x
  • Web Framework: Flask
  • CORS: flask-cors
  • Event Queue: Python heapq (min-heap)
  • Random Generation: NumPy (exponential distribution)

Frontend

  • Language: JavaScript (ES6+)
  • Framework: React 19.x
  • Build Tool: Vite 7.x
  • State Management: React hooks (useState, useEffect)
  • HTTP Client: Fetch API

Directory Structure

SimulationBank/
├── backend/
│   ├── main.py
│   ├── requirements.txt
│   └── src/
│       ├── simulation/
│       │   ├── domain/
│       │   ├── application/
│       │   └── infrastructure/
│       ├── customer/
│       ├── teller/
│       ├── queue/
│       ├── metrics/
│       └── shared/
└── frontend/
    ├── package.json
    ├── index.html
    └── src/
        ├── simulation/
        ├── customer/
        ├── teller/
        ├── queue/
        ├── metrics/
        └── shared/

Design Principles

Separation of Concerns

Each module has a single, well-defined responsibility:
  • Simulation manages time and event processing
  • Customer handles arrival and service logic
  • Teller manages resource allocation
  • Queue implements priority ordering
  • Metrics tracks performance indicators

Dependency Inversion

The domain defines interfaces (ports), and infrastructure provides implementations (adapters):
# Domain defines the interface
class CustomerGenerator(ABC):
    @abstractmethod
    def get_next_arrival_interval(self) -> float:
        pass

# Infrastructure implements it
class ConfigurableGenerator(CustomerGenerator):
    def get_next_arrival_interval(self) -> float:
        return np.random.exponential(1.0 / self.arrival_rate)

Immutability

Value objects are immutable:
@dataclass(frozen=True)
class SimulationConfig:
    num_tellers: int = 3
    arrival_config: Dict[str, Any] = field(default_factory=dict)
    service_config: Dict[str, Any] = field(default_factory=dict)

Communication Patterns

Frontend ↔ Backend

  • Protocol: HTTP/REST
  • Format: JSON
  • CORS: Enabled for local development

Event Flow

  1. Frontend sends configuration via POST /api/simulation/start
  2. Backend creates DiscreteEventSimulation instance
  3. Simulation processes events in chronological order
  4. Frontend polls GET /api/simulation/state for updates
  5. Metrics accumulate in MetricsRepository
  6. Frontend fetches GET /api/metrics/report for analysis

Next Steps

Domain Model

Explore entities, value objects, and domain events

Simulation Engine

Deep dive into the discrete event simulation implementation

Frontend Architecture

Learn about React components and visualization

API Design

Understand the REST API structure

Build docs developers (and LLMs) love