Move product strategy documentation to .product-strategy directory
Some checks failed
CI / build (push) Successful in 21s
CI / integration (push) Failing after 2m1s

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>
This commit is contained in:
2026-01-12 23:57:11 +01:00
parent 18ea677585
commit 271f5db444
26 changed files with 16521 additions and 0 deletions

View File

@@ -0,0 +1,365 @@
# 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