All checks were successful
CI / build (push) Successful in 1m25s
- Update manifesto links to use absolute Gitea URLs Co-Authored-By: Claude <noreply@anthropic.com>
117 lines
3.0 KiB
Markdown
117 lines
3.0 KiB
Markdown
# Aether
|
|
|
|
Distributed actor system with event sourcing for Go, powered by NATS.
|
|
|
|
## Organization Context
|
|
|
|
This repo is part of Flowmade. See:
|
|
- [Organization manifesto](https://git.flowmade.one/flowmade-one/architecture/src/branch/main/manifesto.md) - who we are, what we believe
|
|
- [Repository map](https://git.flowmade.one/flowmade-one/architecture/src/branch/main/repos.md) - how this fits in the bigger picture
|
|
- [Vision](./vision.md) - what this specific product does
|
|
|
|
## Setup
|
|
|
|
```bash
|
|
git clone git@git.flowmade.one:flowmade-one/aether.git
|
|
cd aether
|
|
go mod download
|
|
```
|
|
|
|
Requires NATS server for integration tests:
|
|
```bash
|
|
# Install NATS
|
|
brew install nats-server
|
|
|
|
# Run with JetStream enabled
|
|
nats-server -js
|
|
```
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
aether/
|
|
├── event.go # Event, ActorSnapshot, EventStore interface
|
|
├── eventbus.go # EventBus, EventBroadcaster interface
|
|
├── nats_eventbus.go # NATSEventBus - cross-node event broadcasting
|
|
├── store/
|
|
│ ├── memory.go # InMemoryEventStore (testing)
|
|
│ └── jetstream.go # JetStreamEventStore (production)
|
|
├── cluster/
|
|
│ ├── manager.go # ClusterManager
|
|
│ ├── discovery.go # NodeDiscovery
|
|
│ ├── hashring.go # ConsistentHashRing
|
|
│ ├── shard.go # ShardManager
|
|
│ ├── leader.go # LeaderElection
|
|
│ └── types.go # Cluster types
|
|
└── model/
|
|
└── model.go # EventStorming model types
|
|
```
|
|
|
|
## Development
|
|
|
|
```bash
|
|
make build # Build the library
|
|
make test # Run tests
|
|
make lint # Run linters
|
|
```
|
|
|
|
## Architecture
|
|
|
|
### Event Sourcing
|
|
|
|
Events are the source of truth. State is derived by replaying events.
|
|
|
|
```go
|
|
// Create an event
|
|
event := &aether.Event{
|
|
ID: uuid.New().String(),
|
|
EventType: "OrderPlaced",
|
|
ActorID: "order-123",
|
|
Version: 1,
|
|
Data: map[string]interface{}{"total": 100.00},
|
|
Timestamp: time.Now(),
|
|
}
|
|
|
|
// Persist to event store
|
|
store.SaveEvent(event)
|
|
|
|
// Replay events to rebuild state
|
|
events, _ := store.GetEvents("order-123", 0)
|
|
```
|
|
|
|
### Namespace Isolation
|
|
|
|
Namespaces provide logical boundaries for events and subscriptions:
|
|
|
|
```go
|
|
// Subscribe to events in a namespace
|
|
ch := eventBus.Subscribe("tenant-abc")
|
|
|
|
// Events are isolated per namespace
|
|
eventBus.Publish("tenant-abc", event) // Only tenant-abc subscribers see this
|
|
```
|
|
|
|
### Clustering
|
|
|
|
Aether handles node discovery, leader election, and shard distribution:
|
|
|
|
```go
|
|
// Create cluster manager
|
|
manager := cluster.NewClusterManager(natsConn, nodeID)
|
|
|
|
// Join cluster
|
|
manager.Start()
|
|
|
|
// Leader election happens automatically
|
|
if manager.IsLeader() {
|
|
// Coordinate shard assignments
|
|
}
|
|
```
|
|
|
|
## Key Patterns
|
|
|
|
- **Events are immutable** - Never modify, only append
|
|
- **Snapshots for performance** - Periodically snapshot state to avoid full replay
|
|
- **Namespaces for isolation** - Not multi-tenancy, just logical boundaries
|
|
- **NATS for everything** - Events, pub/sub, clustering all use NATS
|