33 KiB
Domain-Driven Design Analysis: ORLY Relay
This document provides a comprehensive Domain-Driven Design (DDD) analysis of the ORLY Nostr relay codebase, evaluating its alignment with DDD principles and identifying opportunities for improvement.
Analysis Version: v0.56.8 Last Updated: 2026-01-24
Key Recommendations Summary
| # | Recommendation | Impact | Effort | Status |
|---|---|---|---|---|
| 1 | Domain Events & Dispatcher | High | Medium | Implemented |
| 2 | Domain-Specific Error Types | Medium | Low | Implemented |
| 3 | Application Service Extraction | Medium | High | Implemented |
| 4 | Value Object Immutability | Low | Low | Implemented |
| 5 | Ubiquitous Language Glossary | Medium | Low | Implemented |
| 6 | Aggregate Boundary Strengthening | High | Medium | Implemented |
| 7 | Document Context Map | Medium | Low | This Document |
| 8 | Handler Simplification | Medium | Medium | Implemented |
| 9 | Injectable ACL Registry | Medium | Low | Implemented |
| 10 | Rule Value Object Decomposition | Low | Medium | Implemented |
Table of Contents
- Executive Summary
- Strategic Design Analysis
- Tactical Design Analysis
- Anti-Patterns Identified
- Remaining Recommendations
- Implementation Checklist
- Appendix: File References
Executive Summary
ORLY demonstrates exemplary DDD adoption with sophisticated modular architecture. The codebase has evolved from a single-binary relay to a multi-process system with clear bounded context separation, gRPC-based inter-process communication, and pluggable implementations for database, ACL, and sync services.
DDD Maturity Score: 10/10
Recent Improvements (v0.56.5-v0.56.8):
- Explicit domain event types with pub/sub dispatcher
- Typed domain errors with categories (validation, authorization, processing, policy, storage)
- Event ingestion service layer extracting orchestration from handlers
- Special kinds registry for extensible event routing
- Stack-allocated value objects (IdPkTs with [32]byte arrays)
- Handler simplification to thin protocol adapters
- Injectable ACL registry (eliminated global singleton)
- Rule value object decomposition into focused sub-components
Strengths:
- Clear separation between
app/(application layer) andpkg/(domain/infrastructure) - Repository pattern with four interchangeable backends (Badger, Neo4j, WasmDB, gRPC)
- Interface-based ACL system with four implementations (None, Follows, Managed, Curating)
- Driver Registry pattern for runtime component selection
- Process Supervisor pattern for split IPC mode deployment
- Domain Event infrastructure with sync/async publishing
- Typed domain errors with categorization and retryability
- Event Ingestion Service for clean orchestration
- Thin protocol adapter handlers delegating to domain services
- Injectable dependencies (no global singletons in critical paths)
- Decomposed value objects (Rule split into AccessControl, Constraints, TagValidationConfig)
- Per-connection aggregate isolation in
Listener - Immutable
EventRefand stack-optimizedIdPkTsvalue objects - Comprehensive protocol extensions (NIP-43, NIP-77, NIP-86, Blossom, Graph Queries)
Strategic Design Analysis
Bounded Contexts
ORLY organizes code into 11 distinct bounded contexts, each with its own model and language:
1. Event Storage Context (pkg/database/)
- Responsibility: Persistent storage of Nostr events with indexing and querying
- Key Abstractions:
Databaseinterface,Subscription,Payment,NIP43Membership - Implementations: Badger (embedded), Neo4j (graph), WasmDB (browser), gRPC (remote)
- gRPC Server:
pkg/database/server/- DatabaseService with 250+ RPC methods
2. Access Control Context (pkg/acl/)
- Responsibility: Authorization decisions for read/write operations
- Key Abstractions:
Iinterface,Registry, access levels (none/read/write/admin/owner) - Implementations:
None,Follows,Managed,Curating - gRPC Server:
pkg/acl/server/- ACLService
3. Event Policy Context (pkg/policy/)
- Responsibility: Event filtering, validation, rate limiting rules, follows-based whitelisting
- Key Abstractions:
Rule,Kinds,P(PolicyManager) - Invariants: Whitelist/blacklist precedence, size limits, tag requirements, protected events
4. Event Processing Context (pkg/event/) NEW
- Responsibility: Event validation, authorization, routing, and processing orchestration
- Key Abstractions:
validation.Service- Multi-layer validation (raw JSON, signature, timestamp)authorization.Service- Authorization decisions combining ACL + policyrouting.Router- Kind-based routing with handler registrationprocessing.Service- Event persistence and deliveryspecialkinds.Registry- Extensible special kind handlingingestion.Service- Full pipeline orchestration
5. Connection Management Context (app/)
- Responsibility: WebSocket lifecycle, message routing, authentication, flow control
- Key Abstractions:
Listener,Server, message handlers,messageRequest - Handlers: 25+ message type handlers
6. Domain Layer (pkg/domain/) NEW
- Responsibility: Cross-cutting domain concerns
- Key Abstractions:
events.DomainEvent- Base event interfaceevents.Dispatcher- Pub/sub with sync/async publishingerrors.DomainError- Typed error categories
7. Protocol Extensions Context (pkg/protocol/)
- Responsibility: NIP implementations beyond core protocol
- Subcontexts:
- NIP-43 Membership (
pkg/protocol/nip43/): Invite-based access control - Graph Queries (
pkg/protocol/graph/): BFS traversal for social graphs - NWC Payments (
pkg/protocol/nwc/): Nostr Wallet Connect - Blossom (
pkg/protocol/blossom/): BUD protocol definitions
- NIP-43 Membership (
8. Blob Storage Context (pkg/blossom/)
- Responsibility: Binary blob storage following BUD-09 specifications
- Key Abstractions:
Server,Storage,Blob,BlobMeta
9. Rate Limiting Context (pkg/ratelimit/)
- Responsibility: Adaptive throttling based on system load using PID controller
- Key Abstractions:
Limiter,Config,OperationType
10. Distributed Sync Context (pkg/sync/)
- Responsibility: Federation and replication between relay peers
- Implementations: Negentropy, Cluster, Distributed, RelayGroup
11. Process Supervision Context (cmd/orly-launcher/)
- Responsibility: Multi-process lifecycle management for split IPC mode
- Key Abstractions:
Supervisor,Config, process state machine
Context Map
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Process Supervision (orly-launcher) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Supervisor │───▶│ DB Process │───▶│ ACL Process │───▶│Relay Process│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────┘
│
┌───────────────────────────────┼───────────────────────────────┐
│ │ │
▼ ▼ ▼
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ Event Storage │ │ Access Control│ │ Event Policy │
│ (pkg/database/)│ │ (pkg/acl/) │ │ (pkg/policy/) │
│ │ │ │ │ │
│ Badger│Neo4j │◀─[gRPC]─────▶│Follows│Managed │◀─[Conformist]│ Manager │
│ WasmDB│gRPC │ │Curating│None │ │ │
└────────────────┘ └────────────────┘ └────────────────┘
│ │ │
│ [Shared Kernel] │ │
▼ ▼ │
┌────────────────────────────────────────────────────────────┐ │
│ Event Entity │ │
│ (git.mleku.dev/mleku/nostr) │◀─────────┘
│ Filter, Tag, Subscription types │
└────────────────────────────────────────────────────────────┘
│ │
│ [Customer-Supplier] │ [Customer-Supplier]
▼ ▼
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ Event │ │ Domain │ │ Rate Limiting │
│ Processing │◀────────────▶│ Events │ │ (pkg/ratelimit)│
│ (pkg/event/) │ │(pkg/domain/) │ │ │
└────────────────┘ └────────────────┘ └────────────────┘
Integration Patterns Identified:
| Upstream | Downstream | Pattern | Notes |
|---|---|---|---|
| nostr library | All contexts | Shared Kernel | Event, Filter, Tag types |
| Database | ACL, Policy, Blossom, Sync | Customer-Supplier | Query for follows, permissions |
| Policy | Handlers, Sync | Conformist | All respect policy decisions |
| Domain Events | Processing, ACL | Pub/Sub | Event dispatcher decouples contexts |
| gRPC Layer | DB, ACL, Sync | Published Language | Protocol buffer contracts |
Subdomain Classification
| Subdomain | Type | Justification |
|---|---|---|
| Event Storage | Core | Central to relay's value proposition |
| Access Control | Core | Key differentiator (WoT, follows-based, managed, curating) |
| Event Policy | Core | Enables complex filtering rules |
| Event Processing | Core | Clean orchestration of event pipeline |
| Graph Queries | Core | Unique social graph traversal capabilities |
| NIP-43 Membership | Core | Unique invite-based access model |
| Blob Storage (Blossom) | Core | Media hosting differentiator |
| Domain Events | Supporting | Cross-cutting concern infrastructure |
| Connection Management | Supporting | Standard WebSocket infrastructure |
| Rate Limiting | Supporting | Operational concern with PID controller |
| Process Supervision | Generic | Standard process management |
Tactical Design Analysis
Entities
Entities are objects with identity that persists across state changes.
Listener (Connection Entity)
// app/listener.go:24-52
type Listener struct {
conn *websocket.Conn // Identity: connection handle
connID string // Unique identifier
challenge atomicutils.Bytes // Auth challenge state
authedPubkey atomicutils.Bytes // Authenticated identity
subscriptions map[string]context.CancelFunc
messageQueue chan messageRequest // Async message processing
}
- Identity: WebSocket connection pointer and unique connID
- Lifecycle: Created on connect, destroyed on disconnect
- Invariants: Only one authenticated pubkey per connection
InviteCode (NIP-43 Entity)
// pkg/protocol/nip43/
type InviteCode struct {
Code string // Identity: unique code
ExpiresAt time.Time
UsedBy []byte // Tracks consumption
CreatedAt time.Time
}
- Identity: Unique code string
- Lifecycle: Created -> Valid -> Used/Expired
- Invariants: Cannot be reused once consumed
Process (Supervisor Entity)
// cmd/orly-launcher/supervisor.go
type Process struct {
Name string // Identity: service name
Cmd *exec.Cmd // Running process
State ProcessState // Stopped/Starting/Running/Stopping
Restarts int // Crash counter
}
- Lifecycle: Stopped -> Starting -> Running -> Stopping
- Invariants: Dependency ordering; health check before dependent startup
Value Objects
Value objects are immutable and defined by their attributes, not identity.
EventRef (Immutable Event Reference) - Cache-Line Optimized
// pkg/interfaces/store/store_interface.go:99-153
type EventRef struct {
id ntypes.EventID // 32 bytes, unexported
pub ntypes.Pubkey // 32 bytes, unexported
ts int64 // 8 bytes
ser uint64 // 8 bytes
}
// Total: 80 bytes - fits in L1 cache line
- Equality: By all fields (fixed-size arrays)
- Immutability: Unexported fields, accessor methods return copies
- Performance: Stack-allocated, zero allocations on copy
IdPkTs (Stack-Allocated Event Reference) UPDATED v0.56.6
// pkg/interfaces/store/store_interface.go:67-97
type IdPkTs struct {
Id ntypes.EventID // [32]byte - fixed array, stack-allocated
Pub ntypes.Pubkey // [32]byte - fixed array, stack-allocated
Ts int64
Ser uint64
}
// Constructor copies slices into fixed arrays
func NewIdPkTs(id, pub []byte, ts int64, ser uint64) IdPkTs
// Accessors for slice views when needed
func (i *IdPkTs) IDSlice() []byte { return i.Id[:] }
func (i *IdPkTs) PubSlice() []byte { return i.Pub[:] }
- Equality: By all fields
- Immutability: Fixed arrays enable copy-on-assignment semantics
- Performance: 0 B/op, 0 allocs/op for copy operations (benchmark verified)
Decision (Authorization Result)
// pkg/event/authorization/authorization.go:10-20
type Decision struct {
Allowed bool
AccessLevel string // none/read/write/admin/owner/blocked/banned
IsAdmin bool
IsOwner bool
IsPeerRelay bool
SkipACLCheck bool
DenyReason string
RequireAuth bool
}
Kinds (Policy Specification)
// pkg/policy/policy.go:58-63
type Kinds struct {
Whitelist []int
Blacklist []int
}
Rule Sub-Components (UPDATED v0.56.8)
// pkg/policy/policy.go:79-174
type AccessControl struct {
WriteAllow []string // Pubkeys allowed to write
WriteDeny []string // Pubkeys denied write
ReadAllow []string // Pubkeys allowed to read
ReadDeny []string // Pubkeys denied read
WriteAllowFollows bool // Grant access to admin follows
ReadFollowsWhitelist []string
WriteFollowsWhitelist []string
ReadAllowPermissive bool
WriteAllowPermissive bool
}
type Constraints struct {
MaxExpiry *int64 // Maximum expiry time
MaxExpiryDuration string // ISO-8601 duration
SizeLimit *int64 // Max serialized size
ContentLimit *int64 // Max content size
RateLimit *int64 // Write rate limit
MaxAgeOfEvent *int64 // Max age for created_at
MaxAgeEventInFuture *int64 // Max future offset
ProtectedRequired bool // Require NIP-70 "-" tag
Privileged bool // Restrict to authenticated parties
}
type TagValidationConfig struct {
MustHaveTags []string // Required tag keys
TagValidation map[string]string // Tag -> regex pattern
IdentifierRegex string // Regex for "d" tags
}
type Rule struct {
Description string
Script string
AccessControl // Embedded - who can access
Constraints // Embedded - what limits apply
TagValidationConfig // Embedded - tag requirements
}
- Equality: By all fields
- Immutability: Embedded components enable focused updates
- JSON Compatibility: Flat serialization for backward compatibility
Aggregates
Aggregates are clusters of entities/value objects with consistency boundaries.
Listener Aggregate
- Root:
Listener - Members: Subscriptions map, auth state, write channel, message queue
- Boundary: Per-connection isolation
- Invariants:
- Subscriptions must exist before receiving matching events
- AUTH must complete before authenticated operations
- Message processing uses RWMutex for pause/resume during policy updates
Event Aggregate (External)
- Root:
event.E(from nostr library) - Members: Tags, signature, content
- Invariants:
- ID must match computed hash
- Signature must be valid
- Timestamp must be within bounds
Supervisor Aggregate
- Root:
Supervisor - Members: Process map, config, dependency graph
- Invariants:
- Dependency ordering (DB -> ACL -> Sync -> Relay)
- Health checks before dependent startup
- Reverse shutdown ordering
Repositories
The Repository pattern abstracts persistence for aggregate roots.
Database Interface (Primary Repository)
// pkg/database/interface.go:17-109
type Database interface {
// Core lifecycle
Init(path string) error
Close() error
Ready() <-chan struct{}
// Event persistence
SaveEvent(c context.Context, ev *event.E) (exists bool, err error)
QueryEvents(c context.Context, f *filter.F) (evs event.S, err error)
DeleteEvent(c context.Context, eid []byte) error
// Membership management
AddNIP43Member(pubkey []byte, inviteCode string) error
IsNIP43Member(pubkey []byte) (isMember bool, err error)
// ... 30+ more methods
}
Repository Implementations:
| Backend | Type | Use Case | Location |
|---|---|---|---|
| Badger | LSM Tree | Primary - high throughput | pkg/database/ |
| Neo4j | Property Graph | Social queries | pkg/neo4j/ |
| WasmDB | IndexedDB | Browser WASM | pkg/wasmdb/ |
| gRPC | Remote service | Split IPC mode | pkg/database/grpc/ |
Driver Registry Pattern
// Runtime driver selection
database.HasDriver("badger")
database.NewFromDriver("badger", config)
acl.HasDriver("follows")
acl.NewACLFromDriver("follows", config)
Domain Services
Domain services encapsulate logic that doesn't belong to any single entity.
Event Validation Service NEW
// pkg/event/validation/validation.go
type Service struct {
cfg *Config
}
func (s *Service) ValidateRawJSON(msg []byte) Result
func (s *Service) ValidateEvent(ev *event.E) Result
func (s *Service) ValidateProtectedTag(ev *event.E, authedPubkey []byte) Result
Event Authorization Service NEW
// pkg/event/authorization/authorization.go
type Service struct {
cfg *Config
acl ACLRegistry
policy PolicyManager
sync SyncManager
}
func (s *Service) Authorize(ev *event.E, authedPubkey []byte, remote string, kind uint16) Decision
Event Router Service NEW
// pkg/event/routing/routing.go
type Router interface {
Route(ev *event.E, authedPubkey []byte) Result
Register(kind uint16, handler Handler)
RegisterKindCheck(name string, check func(uint16) bool, handler Handler)
}
Event Processing Service NEW
// pkg/event/processing/processing.go
type Service struct {
db Database
publishers *publisher.P
eventDispatch *events.Dispatcher
}
func (s *Service) Process(ctx context.Context, ev *event.E, authedPubkey []byte) Result
Event Ingestion Service NEW
// pkg/event/ingestion/service.go
type Service struct {
validator *validation.Service
authorizer *authorization.Service
router routing.Router
processor *processing.Service
sprocketChecker SprocketChecker
specialKinds *specialkinds.Registry
}
func (s *Service) Ingest(ctx context.Context, ev *event.E, connCtx *ConnectionContext) Result
ACL Registry (Access Decision Service)
// pkg/acl/acl.go
func (s *S) GetAccessLevel(pub []byte, address string) string
func (s *S) CheckPolicy(ev *event.E) (bool, error)
Policy Manager (Rule Evaluation Service)
// pkg/policy/policy.go
func (p *P) CheckPolicy(action string, ev *event.E, pubkey []byte, remote string) (bool, error)
Domain Events
Status: IMPLEMENTED (v0.56.5)
Domain Event Interface
// pkg/domain/events/events.go
type DomainEvent interface {
OccurredAt() time.Time
EventType() string
}
type Base struct {
occurredAt time.Time
eventType string
}
Concrete Event Types
// Event Storage Events
type EventSaved struct {
Base
Event *event.E
Serial uint64
IsAdmin bool
IsOwner bool
}
type EventDeleted struct {
Base
EventID []byte
DeletedBy []byte
Serial uint64
}
// Access Control Events
type FollowListUpdated struct {
Base
AdminPubkey []byte
AddedFollows [][]byte
RemovedFollows [][]byte
}
type ACLMembershipChanged struct {
Base
Pubkey []byte
PrevLevel string
NewLevel string
Reason string
}
// Policy Events
type PolicyConfigUpdated struct {
Base
UpdatedBy []byte
Changes map[string]interface{}
}
// Connection Events
type ConnectionOpened struct {
Base
ConnID string
Remote string
}
// Authentication Events
type UserAuthenticated struct {
Base
Pubkey []byte
ConnID string
Method string
}
// Plus 10+ more event types...
Event Dispatcher
// pkg/domain/events/dispatcher.go
type Subscriber interface {
Handle(event DomainEvent)
Supports(eventType string) bool
}
type Dispatcher struct {
subscribers []Subscriber
asyncChan chan DomainEvent
}
func (d *Dispatcher) Publish(event DomainEvent) // Sync
func (d *Dispatcher) PublishAsync(event DomainEvent) // Async
func (d *Dispatcher) Subscribe(s Subscriber)
Error Handling
Status: IMPLEMENTED (v0.56.5)
Typed Domain Errors
// pkg/domain/errors/errors.go
type DomainError interface {
error
Code() string // e.g., "INVALID_ID"
Category() string // e.g., "validation"
IsRetryable() bool
}
// Error Categories
type ValidationError struct { Base; Field string }
type AuthorizationError struct { Base; Pubkey []byte; AccessLevel string; RequireAuth bool }
type ProcessingError struct { Base; EventID []byte; Kind uint16 }
type PolicyError struct { Base; RuleName string; Action string }
type StorageError struct { Base }
type ServiceError struct { Base; ServiceName string }
Error Helpers
func Is(err error, target DomainError) bool
func Code(err error) string
func Category(err error) string
func IsRetryable(err error) bool
func NeedsAuth(err error) bool
Anti-Patterns Identified
1. Remaining Handler Complexity (RESOLVED v0.56.8)
Location: app/handle-event.go
Status: Handlers are now thin protocol adapters. The HandleEvent method delegates to the ingestion service which orchestrates validation, authorization, routing, and processing. Connection-specific concerns (NIP-43, policy config) remain in handlers where they belong.
2. Global Singleton Registry (RESOLVED v0.56.8)
Location: pkg/acl/acl.go:10
Status: ACL registry is now injectable via the acliface.Registry interface. The Server struct holds an aclRegistry field with an accessor method ACLRegistry(). Tests and main.go inject the registry during construction. The global Registry variable remains for backward compatibility but is not required.
3. Partial Event Dispatcher Usage (RESOLVED v0.56.7)
Status: Event infrastructure is now fully wired up. The processing service dispatches EventSaved domain events, and a LoggingSubscriber handles analytics logging at configurable verbosity levels.
4. Oversized Rule Value Object (RESOLVED v0.56.8)
Location: pkg/policy/policy.go
Status: The Rule struct is now composed of three focused sub-value objects:
type Rule struct {
Description string
Script string
AccessControl // WriteAllow, WriteDeny, ReadAllow, ReadDeny, etc.
Constraints // SizeLimit, ContentLimit, MaxAgeOfEvent, Privileged, etc.
TagValidationConfig // MustHaveTags, TagValidation, IdentifierRegex
}
Each sub-component handles a specific concern:
AccessControl: Who can read/write (pubkey lists, follows whitelists, permissive flags)Constraints: Event limits and restrictions (size, age, rate, privileged access)TagValidationConfig: Tag presence and format validation
JSON serialization remains flat for backward compatibility (embedded structs).
Completed Recommendations
6. Aggregate Boundary Strengthening (RESOLVED v0.56.7)
Problem: Some aggregates exposed mutable state via public fields.
Solution Applied:
pkg/acl/acl.go- Fields privatized:acl(wasACL),active(wasActive)- Accessor methods:
ACLs(),GetMode(),GetActiveACL(),GetACLByType() app/server.go- Accessor methods exist:GetConfig(),Database(),IsAdmin(),IsOwner()
Migration:
// Instead of: acl.Registry.ACL → acl.Registry.ACLs()
// Instead of: acl.Registry.Active.Load() → acl.Registry.GetMode()
8. Handler Simplification (RESOLVED v0.56.8)
Problem: Handlers contained orchestration logic mixed with protocol concerns.
Solution Applied:
app/handle-event.go- Reduced from ~320 lines to ~130 lines- Delegates to
ingestion.Service.Ingest()for full pipeline orchestration - Handlers only responsible for: raw JSON extraction, envelope creation, response formatting
- Connection-specific logic (NIP-43, policy config) remains in handlers where appropriate
- Created
app/specialkinds.gofor special kind handler registration
9. Injectable ACL Registry (RESOLVED v0.56.8)
Problem: Global acl.Registry singleton made testing difficult.
Solution Applied:
- Created
pkg/interfaces/acl/acl.gowithRegistryinterface - Added
aclRegistryfield toServerstruct - Accessor method
ACLRegistry()for dependency retrieval main.goinjectsacl.Registryduring server construction- Test files use injected mock registries
10. Rule Value Object Decomposition (RESOLVED v0.56.8)
Problem: Rule struct had 25+ fields, violating single responsibility.
Solution Applied:
- Created three focused sub-value objects in
pkg/policy/policy.go:AccessControl: WriteAllow/Deny, ReadAllow/Deny, follows whitelists, permissive flagsConstraints: SizeLimit, ContentLimit, RateLimit, MaxAgeOfEvent, Privileged, ProtectedRequiredTagValidationConfig: MustHaveTags, TagValidation, IdentifierRegex
Rulenow embeds these three components- JSON serialization remains flat for backward compatibility
- All tests updated to use proper struct initialization with embedded types
Implementation Checklist
All Items Complete ✓
- Bounded contexts identified with clear boundaries
- Repositories abstract persistence for aggregate roots
- Multiple repository implementations (Badger/Neo4j/WasmDB/gRPC)
- Interface segregation prevents circular dependencies
- Configuration centralized (
app/config/config.go) - Per-connection aggregate isolation
- Access control as pluggable strategy pattern
- Value objects have immutable implementations (
EventRef,IdPkTs) - Context map documented
- Driver registry pattern for runtime selection
- gRPC layer for distributed deployment
- Process supervision for split mode
- Domain events capture important state changes (v0.56.5)
- Typed domain errors with categories (v0.56.5)
- Application services extract orchestration (v0.56.5)
- Ubiquitous language documented (
docs/GLOSSARY.md) - Stack-allocated value objects (v0.56.6)
- Domain event subscribers wired up (v0.56.7)
- Aggregate internal state privatized (v0.56.7)
- Handler simplification to thin adapters (v0.56.8)
- Injectable ACL registry (v0.56.8)
- Rule value object decomposed (v0.56.8)
Appendix: File References
Domain Layer Files (NEW)
| File | Purpose |
|---|---|
pkg/domain/errors/errors.go |
Typed domain error system |
pkg/domain/events/events.go |
Domain event type definitions |
pkg/domain/events/dispatcher.go |
Event pub/sub dispatcher |
pkg/domain/events/subscribers/logging.go |
Analytics logging subscriber |
Event Processing Layer (NEW)
| File | Purpose |
|---|---|
pkg/event/validation/validation.go |
Multi-layer event validation |
pkg/event/authorization/authorization.go |
Authorization service |
pkg/event/routing/routing.go |
Kind-based event routing |
pkg/event/processing/processing.go |
Event processing orchestration |
pkg/event/ingestion/service.go |
Full pipeline ingestion service |
pkg/event/specialkinds/registry.go |
Special kind handler registry |
Core Domain Files
| File | Purpose |
|---|---|
pkg/database/interface.go |
Repository interface |
pkg/interfaces/acl/acl.go |
ACL interface definition |
pkg/interfaces/store/store_interface.go |
Store interfaces, IdPkTs, EventRef |
pkg/policy/policy.go |
Policy rules and evaluation |
pkg/protocol/nip43/ |
NIP-43 invite management |
pkg/protocol/graph/executor.go |
Graph query execution |
Application Layer Files
| File | Purpose |
|---|---|
app/server.go |
HTTP/WebSocket server setup |
app/listener.go |
Connection aggregate |
app/handle-event.go |
EVENT message handler |
app/handle-*.go |
25+ message type handlers |
Infrastructure Files
| File | Purpose |
|---|---|
pkg/database/database.go |
Badger implementation |
pkg/database/server/ |
gRPC database server |
pkg/neo4j/ |
Neo4j implementation |
pkg/blossom/server.go |
Blossom blob storage |
pkg/ratelimit/limiter.go |
PID-based rate limiting |
pkg/sync/ |
Distributed sync implementations |
Command Binaries
| Binary | Purpose |
|---|---|
cmd/orly-launcher/ |
Process supervisor with admin UI |
cmd/orly-db-badger/ |
Standalone Badger database server |
cmd/orly-acl-follows/ |
Follows-based ACL server |
cmd/orly-sync-negentropy/ |
NIP-77 negentropy sync service |
Documentation
| File | Purpose |
|---|---|
docs/GLOSSARY.md |
Ubiquitous language definitions |
docs/POLICY_CONFIGURATION_REFERENCE.md |
Policy configuration guide |
DDD_ANALYSIS.md |
This document |
Generated: 2026-01-24 Analysis based on ORLY codebase v0.56.8