Aggregate Root Design
Table of Contents
- Introduction
- Project Structure
- Core Components
- Architecture Overview
- Detailed Component Analysis
- Dependency Analysis
- Performance Considerations
- Troubleshooting Guide
- Conclusion
- Appendix
Introduction
This document focuses on "aggregate roots" in domain-driven design, combining the implementation of the Session aggregate root in the codebase to systematically explain aggregate root boundary division principles, relationships with entities, consistency guarantee mechanisms, transaction boundary management, business rule encapsulation, lifecycle and state synchronization, concurrency control, responsibility separation, dependency injection, and testing strategies. The target audience is architects and senior developers.
Project Structure
This project adopts a clean architecture layering: interface adapter layer (HTTP), use case layer (business services), entity layer (entities and aggregate roots), and infrastructure layer (repositories, event storage, message buses). The Session aggregate root is located in the entity layer, the SessionService is located in the use case layer, the HTTP handler is located in the interface adapter layer, and the repository and event storage are located in the infrastructure layer.
- Aggregate Root Interface and Base Implementation
- The aggregate root interface defines aggregate identification, version control, event sourcing support, state loading, and business validation capabilities.
- The base aggregate root provides common logic for version numbers, uncommitted event lists, event appending, and marking.
- Entity and Base Entity
- The entity interface unifies ID, creation/update time; the base entity provides default fields and methods.
- Session Entity
- The session entity embeds the base entity, contains a data dictionary and expiration time, and provides expiration judgment and data reading/writing methods.
- Use Case Service
- The session service encapsulates business processes: create, get, save, delete, update data, clean up expired sessions.
- Repository and Event Store
- The base repository defines common CRUD and query interfaces; the memory repository provides a concurrency-safe implementation.
- The event store interface defines event persistence, query, snapshot, and batch operation capabilities.
- HTTP Handler
- Provides session HTTP APIs: create, get, update data, delete, get specific data.
- Dependency Injection Container and Application Startup
- The container supports anonymous/named registration and resolution; the application is responsible for starting the HTTP service, child processes, and resource cleanup.
The Session aggregate root follows DDD aggregate boundaries: with the session as the core, coordinating repositories and event storage through the service layer to ensure business rules and consistency; the HTTP layer is responsible for request access and response output.
Aggregate Root Interface and Base Implementation
- Aggregate Root Interface
- Aggregate Identification: Aggregate type and aggregate ID.
- Version Control: Version number acquisition, setting, incrementing for optimistic concurrency control.
- Event Sourcing: Process commands to generate events, apply events, get uncommitted events, mark as committed, load state from event streams/snapshots.
- Business Validation: Reserved Validate interface.
- Base Aggregate Root
- Maintains BaseEntity, version number, and uncommitted event list.
- Provides AddEvent/AddUncommittedEvents, MarkEventsAsCommitted, HasUncommittedEvents methods.
- Performs strict validation on event ID, type, aggregate ID, and duplicate events to prevent event stream corruption.
- Session Entity
- Embeds the base entity, contains a data dictionary and expiration time.
- Provides methods for expiration judgment, updating expiration time, setting/getting/deleting data.
- Session Service
- Business processes: create session, get session and check expiration, save session (automatically update expiration time), delete session, update data, get data, clean up expired sessions.
- Implements persistence through the repository, records errors with logs.
- Transaction Boundary
- The service layer uses a single business operation as the transaction boundary to ensure atomicity and consistency under the same business semantics.
- Expiration checks and deletion are completed when getting the session to avoid dirty reads.
- Event Model
- The domain event interface contains event ID, type, aggregate ID/type, version, and timestamp.
- The base event struct provides default implementation and serialization assistance.
- Event Store Interface
- Basic capabilities: save events, read events, load aggregate state, close.
- Query capabilities: query by version, type, time range, and pagination.
- Snapshot capabilities: save/get latest snapshot.
- Batch capabilities: batch save events.
- Consistency and Concurrency Control
- Aggregate root version numbers are used for optimistic locking; versions are incremented and set for events when appending.
- Event ID, type, and aggregate ID validation prevent duplicates and mismatches.
- Uncommitted event list is used for event storage and publishing, cleared after commit.
- Base Repository
- Defines a generic entity interface, providing save, find, update, delete, pagination, statistics, and conditional query methods.
- Performs null ID validation in multiple places to avoid illegal operations.
- Memory Repository
- Implements concurrency-safe CRUD and batch operations based on read-write locks.
- Automatically sets creation/update times, supports conditional queries and random selection.
- Provides Exists, Clear and other helper methods for testing and operations.
- Container
- Supports anonymous and named dependency registration and resolution, automatic constructor parameter resolution, and instance caching (singleton).
- Application Startup
- Initializes configuration and logging, creates Gin engine, registers container, starts child processes and HTTP server.
- Provides convenient parsing methods like GetSessions, supports NATS connection and event bus/publisher.
-
Unit Tests
- Use memory repository instead of real database, leveraging concurrency-safe and conditional query capabilities to quickly verify business logic.
- Assert key paths of the session service (create, get, save, delete, update data, clean up expired).
-
Integration Tests
- Assemble end-to-end processes through container registration and resolution, verifying the HTTP -> service -> repository -> event storage link.
-
Behavior Tests
- Use mock aggregate roots and event buses to verify the correctness of event publishing, replay, and snapshot recovery.
-
Component Coupling
- The session service depends on the repository interface, decoupling from specific implementations; the HTTP handler depends on the service interface, facilitating replacement and testing.
- The aggregate root interface and base aggregate root provide unified event sourcing and version control capabilities, reducing implementation costs for each aggregate root.
-
External Dependencies
- The event store interface supports both database and event stream implementations; event stream mode focuses on read capabilities and publishing.
- NATS connection and event bus are used for event publishing and subscription, supporting eventual consistency.
- Event Store
- Database-based event storage is suitable for strong consistency scenarios, requiring attention to concurrent write and batch operation performance.
- Event stream-based storage delegates concurrency control to the platform, suitable for high throughput and eventual consistency scenarios.
- Repository
- Memory repository is suitable for testing and low-latency scenarios; production environments recommend using relational/document databases or key-value stores.
- Conditional queries and pagination in memory repositories are implemented through reflection; complex queries should be migrated to dedicated databases.
- Concurrency Control
- Optimistic locking based on version numbers reduces lock contention; strict validation during event appending avoids duplicate events.
- Event Replay and Snapshot
- For aggregate roots with many historical events, using snapshots can significantly improve reconstruction performance.
Troubleshooting Guide
- Common Errors
- Session not found: Failed to get session or returned not found after expired deletion.
- Invalid session: Return invalid session error when saving empty session.
- Repository error: Empty ID, entity not found, already exists, etc.
- Troubleshooting Steps
- Check if HTTP request parameters and path variables are correct.
- View service logs to locate error sources (create, get, save, delete).
- Verify if the repository implementation (memory/database) correctly sets timestamps and IDs.
- If using event storage, confirm event ID/type and aggregate ID consistency, check version numbers and uncommitted event list.
This design takes the Session aggregate root as an example, demonstrating the responsibilities and implementation points of DDD aggregate roots in clean architecture: clear aggregate boundaries, strict business rule encapsulation, optimistic concurrency control based on version numbers, event sourcing and eventual consistency event storage solutions, and a replaceable and testable architecture implemented through dependency injection and containers. This pattern can be extended to other aggregate roots, and it is recommended to combine event streams and database-based event storage in actual projects to balance consistency and performance.
Appendix
- Key Interfaces and Method Paths
- Aggregate root interface: pkg/entity/agg_root.go
- Base aggregate root: pkg/entity/base_agg_root.go
- Event interface and base event: pkg/entity/domain_event.go, pkg/entity/domain_base_event.go
- Session entity: pkg/entity/session.go
- Session service: pkg/usecase/session.go
- Base repository and memory repository: pkg/usecase/base_repo.go, pkg/persistence/repo/memory.go
- Event store interface: pkg/usecase/eventstore.go
- HTTP handler: pkg/adapter/http/handlers/session.go
- Container and application startup: pkg/bootstrap/container.go, pkg/bootstrap/app.go