DDD聚合根Building的子实体Room如何在模型外实现数据库持久化?
Great question—this is a classic DDD pain point where we need to keep our domain model free from persistence dependencies and maintain the encapsulation of child entities like Room. Let’s break down two robust solutions that align with DDD principles:
1. Use Domain Events to Delegate Persistence
The core idea here is that your domain model emits events when state changes, and an external layer (like an application service or infrastructure handler) listens for these events to handle persistence. This keeps your domain logic pure while ensuring persistence stays in sync with domain actions.
Step 1: Define a Domain Event
First, create a simple event class to represent the room addition (this stays in the pure domain layer):
// Pure domain event, no persistence dependencies class RoomAddedEvent { private final Building building; private final Room newRoom; public RoomAddedEvent(Building building, Room newRoom) { this.building = building; this.newRoom = newRoom; } // Getters only for external handlers, not domain logic public Building getBuilding() { return building; } public Room getNewRoom() { return newRoom; } }
Step 2: Modify the Aggregate Root to Emit Events
Update the Building class to track events instead of calling persistence directly. We’ll add an internal list to hold pending events:
class Building { private List<Room> rooms = new ArrayList<>(); private List<RoomAddedEvent> domainEvents = new ArrayList<>(); public void addRoom(String name) { Room room = new Room(name); rooms.add(room); // Keep domain logic pure—only update in-memory state domainEvents.add(new RoomAddedEvent(this, room)); // Emit event for persistence } // Expose events to external layers (clear after retrieval to avoid reprocessing) public List<RoomAddedEvent> getDomainEvents() { List<RoomAddedEvent> events = new ArrayList<>(domainEvents); domainEvents.clear(); return events; } } class Room { private String name; // Room remains fully encapsulated—only Building can instantiate it Room(String name) { this.name = name; } // Minimal getter only for persistence needs public String getName() { return name; } }
Step 3: Handle Events in an External Layer
An application service or repository will now process the events to persist the room. This layer is allowed to depend on persistence infrastructure:
// Application service (depends on repositories and domain model) class BuildingService { private final BuildingRepository buildingRepo; private final RoomRepository roomRepo; public BuildingService(BuildingRepository buildingRepo, RoomRepository roomRepo) { this.buildingRepo = buildingRepo; this.roomRepo = roomRepo; } public void addRoomToBuilding(Building building, String roomName) { building.addRoom(roomName); // Trigger pure domain logic first // Process events to handle persistence for (RoomAddedEvent event : building.getDomainEvents()) { roomRepo.create(event.getNewRoom()); // Persist the new room buildingRepo.update(building); // Optional: for Building metadata/versioning } } }
This approach keeps your domain model completely decoupled from persistence, and Room stays fully encapsulated—no external code creates or modifies it directly.
2. Track Changes in the Aggregate Root
For simpler scenarios, you can have the aggregate root track its own pending changes (like newly added rooms), so the repository can batch-process these changes when saving the aggregate.
Step 1: Add Change Tracking to the Aggregate
Update Building to track rooms that need persistence:
class Building { private List<Room> rooms = new ArrayList<>(); private List<Room> newlyAddedRooms = new ArrayList<>(); public void addRoom(String name) { Room room = new Room(name); rooms.add(room); newlyAddedRooms.add(room); // Track rooms pending persistence } // Expose changes to repository (clear after retrieval) public List<Room> getNewlyAddedRooms() { List<Room> changes = new ArrayList<>(newlyAddedRooms); newlyAddedRooms.clear(); return changes; } }
Step 2: Let the Repository Handle Persistence
The BuildingRepository will now handle saving both the aggregate and its child entities:
class BuildingRepository { private final DbConnection db; public void save(Building building) { // First persist all newly added rooms for (Room room : building.getNewlyAddedRooms()) { db.create(room); } // Update the Building if needed (e.g., for versioning or metadata) db.update(building); } }
This is a lightweight solution that still keeps the domain model pure and preserves Room’s encapsulation.
Key Benefits of Both Approaches
- Pure Domain Model: No persistence code cluttering
BuildingorRoom—they focus solely on business rules. - Preserved Encapsulation:
Roomis only created and managed byBuilding; external code never interacts with it directly. - Consistency: Persistence is triggered after the domain state is updated, eliminating mismatches between in-memory state and the database.
内容的提问来源于stack exchange,提问作者user12019412

