Files
aether/.product-strategy/CONTEXT_MAP_DIAGRAM.md
Hugo Nijhuis 271f5db444
Some checks failed
CI / build (push) Successful in 21s
CI / integration (push) Failing after 2m1s
Move product strategy documentation to .product-strategy directory
Organize all product strategy and domain modeling documentation into a
dedicated .product-strategy directory for better separation from code.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-12 23:57:20 +01:00

18 KiB

Aether Bounded Contexts - Visual Map

Context Relationship Diagram

┌─────────────────────────────────────────────────────────────────────────────┐
│                           AETHER SYSTEM                                     │
│                                                                             │
│                                                                             │
│  ┌──────────────────────┐                                                  │
│  │   EVENT SOURCING     │◄─────────────────┐                               │
│  │  (Source of Truth)   │                  │                               │
│  │                      │        writes    │                               │
│  │ • Events (immutable) │       events     │                               │
│  │ • Versions          │                  │ Application                    │
│  │ • Snapshots         │                  │ Business Logic                │
│  │ • Replay            │                  │                               │
│  └──────────┬───────────┘                  │                               │
│             │                              │                               │
│             │ publishes                    │                               │
│             │ events                       └──────────────────────────────┘
│             │
│             ▼
│  ┌──────────────────────────────┐
│  │      EVENT BUS               │  ◄────────────┐
│  │   (Pub/Sub Distribution)     │              │
│  │                              │   uses       │
│  │ • Local subscriptions        │   namespace  │
│  │ • NATS cross-node            │   patterns   │
│  │ • Event filtering            │              │
│  │ • Non-blocking delivery      │              │
│  └──────────┬───────────────────┘              │
│             │                      ┌───────────┴──────────────┐
│             │                      │ NAMESPACE ISOLATION      │
│             ▼ distributes           │  (Logical Boundaries)   │
│             to                      │                         │
│      ┌─────────────────┐          │ • Namespace patterns     │
│      │  Subscribers    │          │ • Stream prefixing      │
│      │  (Listeners)    │          │ • Wildcard matching     │
│      └─────────────────┘          │ • Storage isolation     │
│                                    └─────────────────────────┘
│
│
│  ┌──────────────────────────────┐
│  │ OPTIMISTIC CONCURRENCY       │
│  │  (Conflict Detection)        │
│  │                              │
│  │ • Version validation         │
│  │ • Conflict detection         │
│  │ • Detailed error info        │
│  │ • (App handles retry)        │
│  └──────────▲───────────────────┘
│             │
│             │ nested in
│             │ EventStore.SaveEvent()
│             │
│  ┌──────────┴────────────────────┐
│  │  CLUSTER COORDINATION         │
│  │  (Distributed Control)        │
│  │                               │
│  │ • Node discovery              │
│  │ • Leader election             │
│  │ • Consistent hash ring        │
│  │ • Shard assignment            │
│  │ • Rebalancing logic           │
│  │                               │
│  │ Coordinates via:              │
│  │ - LeaderElection (NATS KV)    │
│  │ - ShardManager (placement)    │
│  │ - EventBus (topology changes) │
│  └───────────────────────────────┘
│
│
└─────────────────────────────────────────────────────────────────────────────┘

Detailed Context Interactions

Single-Node System (Testing/Development)

Application
     │
     ├─► SaveEvent()  ──► InMemoryEventStore
     │                         │
     │                         └─► GetLatestVersion() [Optimistic Concurrency]
     │
     └─► Publish() ──► EventBus (local subscriptions)
                            │
                            └─► Subscriber 1 receives event

Multi-Node Cluster

Node A                                Node B
┌─────────────────────────┐         ┌─────────────────────────┐
│ ClusterManager          │         │ ClusterManager          │
│  - NodeInfo: A          │◄─NATS──►│  - NodeInfo: B          │
│  - LeaderElection       │ KV      │  - LeaderElection       │
│  - ShardMap: {...}      │         │  - ShardMap: {...}      │
│                         │         │                         │
└─────────────────────────┘         └─────────────────────────┘
         │                                    │
         │ publishes to                       │ publishes to
         │ ShardAssigned event                │ ShardAssigned event
         │                                    │
         ▼                                    ▼
┌─────────────────────────┐         ┌─────────────────────────┐
│ JetStreamEventStore     │         │ JetStreamEventStore     │
│  - Stream: events       │         │  - Stream: events       │
│  - GetEvents()          │         │  - GetEvents()          │
│  - SaveEvent()          │         │  - SaveEvent()          │
└─────────────────────────┘         └─────────────────────────┘
         │                                    │
         │ SaveEvent                          │ SaveEvent
         ▼ triggers                           ▼ triggers
┌─────────────────────────┐         ┌─────────────────────────┐
│ NATSEventBus            │         │ NATSEventBus            │
│ Publish(ns, event)      │◄─NATS──►│ Subscribe(ns, filter)   │
│                         │ subject  │                         │
│ aether.events.{ns}      │ routing  │                         │
└─────────────────────────┘         └─────────────────────────┘

Shared (via NATS JetStream):
- Event persistence (durable across nodes)
- Leader election state (KV store)
- Event distribution (NATS pub/sub)

Multi-Tenant Scenario

Application manages multiple namespaces:

Tenant A                               Tenant B
  │                                      │
  ├─ JetStreamEventStore                ├─ JetStreamEventStore
  │  namespace: "tenant-a"              │  namespace: "tenant-b"
  │  stream: "tenant-a_events"          │  stream: "tenant-b_events"
  │                                     │
  └─► EventBus                          └─► EventBus
     Publish("tenant-a", event)            Publish("tenant-b", event)
           │                                      │
           ▼                                      ▼
     Subscribe("tenant-a")               Subscribe("tenant-b")
     (sees only tenant-a events)        (sees only tenant-b events)

Optional: Admin wildcard subscription receives both:
     Subscribe("*") or Subscribe(">")
     (sees tenant-a AND tenant-b events)
     ⚠️  SECURITY: Only grant to trusted components

Context Ownership Matrix

Context Internal Owner Data Owner Responsibility
Event Sourcing EventStore Application (writes events); Aether (persists) Event persistence, versioning, replay
Optimistic Concurrency SaveEvent() Version tracking Conflict detection; app retries
Namespace Isolation EventBus + JetStreamEventStore Application (defines semantics) Isolation routing; wildcard warnings
Cluster Coordination ClusterManager + LeaderElection ClusterManager Discovery, election, shard assignment
Event Bus EventBus/NATSEventBus Aether Event distribution; filtering; delivery

Lifecycle Timelines

Event Lifetime (per context)

Event Sourcing Context:
  Create ──SaveEvent()─► Store ──Replay()─► Rebuild State ──Forever──► Retention Expires
    │                        │
    │                        └─► GetEvents() (available for subscribers)
    │                        └─► GetLatestVersion() (for new writers)
    │
Optimistic Concurrency (during SaveEvent):
    └─► Check version ──Conflict?── Yes ──► VersionConflictError (app retries)
                              │
                              No ──► Success

Event Bus Context (after SaveEvent succeeds):
    Event ──Publish(namespace, event)── Matched Subscribers ──► Each gets event
             │                                    │
             Namespace pattern match              Filter match (EventType, ActorPattern)
             (exact or wildcard)                  │
                                                  Non-blocking delivery

Shard Lifetime (per context)

Cluster Coordination Context:

Cluster Formation:
  Node Joins ──LeaderElection()─► Leader Elected ──ShardAssignment()─► ShardMap Created
                                         │
                                         └─► LeadershipLease (TTL: 10s)
                                             │
                                             ├─ Heartbeat every 3s (renew lease)
                                             │
                                             └─ Lease expires ──► New election

Node Failure:
  Node Fails ──Detection timeout──► ShardMap Updated ──Rebalancing─► NewShardMap
             (90s implied)              │                  │
                                        └─► ShardMigrated events ─► Actors replay on new nodes

Event Sourcing Integration (during shard migration):
  Source Node      Destination Node
      │                   │
      └─ Stop serving ◄───┘ Start accepting
      │                   │
      └─ Events still in JetStream (durable)
                           │
                           └─► Replay from snapshot/event 1

Subscription Lifetime (per context)

Event Bus Context:

Application calls Subscribe(namespace, filter):
  ────────────────────────────────────────────
  Channel created (100-element buffer)
  │
  ├─ Exact match: added to exactSubscribers[namespace]
  │
  └─ Wildcard: added to wildcardSubscribers

  ├─ If first subscriber for pattern:
  │  └─ NATSEventBus: create NATS subscription (aether.events.{pattern})
  │
  Return channel to caller


Event arrives (via Publish or NATS):
  ────────────────────────────────────────────
  EventBus.Publish(namespace, event)
  │
  ├─ Deliver to exactSubscribers[namespace] (if matches filter)
  │
  └─ Deliver to wildcardSubscribers (if pattern matches; if filter matches)

  ├─ Non-blocking send (may drop if channel full)
  │
  └─ Metrics recorded (delivered or dropped)


Application calls Unsubscribe(namespace, channel):
  ────────────────────────────────────────────
  Remove from exactSubscribers or wildcardSubscribers
  │
  └─ If last subscriber for pattern:
     └─ NATSEventBus: unsubscribe from NATS

  Close channel

Key Invariants per Context

Context Invariant How Enforced Verified
Event Sourcing Version strictly monotonic per actor SaveEvent checks version > current Error if violated
Event Sourcing Event immutable after persist Event struct only read after store Code review
Namespace Isolation No cross-namespace reads Separate JetStream streams Integration test
Cluster Coordination Only one leader at a time Lease-based election (NATS KV) Lease expiry
Cluster Coordination All nodes have consistent shard map Published via ShardAssigned event Periodic sync
Event Bus Non-blocking delivery Select with default (drop) Code review
Optimistic Concurrency Conflict detected synchronously Version check in SaveEvent Fast-fail

Dependency Summary

Aether Core (no dependencies between contexts; all dependencies are inbound):

├─ Application
│  ├─ Writes to Event Sourcing (SaveEvent)
│  ├─ Publishes to Event Bus (Publish)
│  ├─ Subscribes to Event Bus (Subscribe)
│  └─ Handles Optimistic Concurrency errors (retry logic)
│
├─ Event Sourcing
│  ├─ Provides events to Event Bus
│  ├─ Provides events to Cluster Coordination (on failover)
│  └─ Implements Optimistic Concurrency (SaveEvent validation)
│
├─ Event Bus
│  ├─ Routes events from Event Sourcing
│  ├─ Routes events from Cluster Coordination
│  ├─ Uses Namespace Isolation patterns
│  └─ Delivers to all subscribers
│
├─ Namespace Isolation
│  ├─ Scopes Event Sourcing (JetStreamConfig.Namespace)
│  ├─ Scopes Event Bus (Subscribe pattern)
│  └─ Optional; can be omitted (single global namespace)
│
├─ Cluster Coordination
│  ├─ Reads from Event Sourcing (GetLatestVersion, replay on failover)
│  ├─ Publishes to Event Bus (ShardAssigned, etc.)
│  ├─ Can use Namespace Isolation (optional)
│  └─ Independent election loop
│
└─ Optimistic Concurrency
   ├─ Embedded in Event Sourcing (SaveEvent)
   ├─ Errors drive Application retry logic
   └─ Version state managed by Event Sourcing

When Contexts Interact

Interaction Trigger Duration Frequency
App → Event Sourcing Domain event occurs Synchronous (ms) Per business event
Event Sourcing → Event Bus SaveEvent succeeds Async (app controls) Per business event
Optimistic Concurrency → App Version conflict Synchronous (us) Per concurrent write
Cluster → Event Sourcing Node fails / rebalances Async (minutes) Per topology change
Cluster → Event Bus Shard assignment changes Async (seconds) Per election or failure
Event Bus → Subscribers Event published Non-blocking (drops) Per business event
Namespace Isolation → All Routing decision Synchronous (us) Per publish/subscribe

Testing Strategy by Context

Event Sourcing (Unit + Integration)

Unit: SaveEvent with various versions, GetLatestVersion, GetEvents
Integration: Snapshot + replay, corruption recovery, concurrent writes

Optimistic Concurrency (Unit)

Unit: Conflict detection, error details, version semantics
Integration: Concurrent writers, contention metrics

Namespace Isolation (Integration)

Integration: Multi-tenant isolation, wildcard safety, cross-namespace verification

Cluster Coordination (Integration)

Integration: Node join/fail, leader election, shard rebalancing, split-brain prevention

Event Bus (Unit + Integration)

Unit: Subscribe/unsubscribe, filtering, exact vs wildcard
Integration: NATS distribution, cross-node delivery, dropped event handling

Non-Interactions (Important!)

The following are NOT direct dependencies (avoid creating them):

  • Namespace Isolation ↔ Cluster Coordination: Cluster works across all namespaces; namespace is orthogonal
  • Event Sourcing ↔ Cluster Coordination: Cluster doesn't own event storage; queries it, doesn't manage it
  • Optimistic Concurrency ↔ Event Bus: Conflicts are events; not bus subscribers
  • Application ↔ Cluster: App doesn't directly manage cluster; uses ClusterManager API
  • Event Bus ↔ Event Sourcing: One-way (sourcing publishes; bus delivers); no feedback loop