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>
18 KiB
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