You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

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

  1. Executive Summary
  2. Strategic Design Analysis
  3. Tactical Design Analysis
  4. Anti-Patterns Identified
  5. Remaining Recommendations
  6. Implementation Checklist
  7. 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) and pkg/ (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 EventRef and stack-optimized IdPkTs value 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: Database interface, 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: I interface, 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 + policy
    • routing.Router - Kind-based routing with handler registration
    • processing.Service - Event persistence and delivery
    • specialkinds.Registry - Extensible special kind handling
    • ingestion.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 interface
    • events.Dispatcher - Pub/sub with sync/async publishing
    • errors.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

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 (was ACL), active (was Active)
  • 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.go for 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.go with Registry interface
  • Added aclRegistry field to Server struct
  • Accessor method ACLRegistry() for dependency retrieval
  • main.go injects acl.Registry during 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 flags
    • Constraints: SizeLimit, ContentLimit, RateLimit, MaxAgeOfEvent, Privileged, ProtectedRequired
    • TagValidationConfig: MustHaveTags, TagValidation, IdentifierRegex
  • Rule now 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