DDD架构下Player聚合地址修正的用例设计疑问
Great question—this cuts straight to how DDD ties technical design to real business language and intent. Here's how to approach it:
1. Yes, create a dedicated AdjustPlayerAddress use case
DDD’s rule of using business-aligned use cases isn’t just about avoiding generic UpdateX methods—it’s about making each use case map to a specific, meaningful business action.
RelocatePlayerTorepresents a deliberate business decision: the player is moving to a new location (think: they just moved apartments, changed cities, etc.).AdjustPlayerAddressrepresents a distinct scenario: fixing a mistake in an existing address (typo in street name, wrong unit number). These are two different business events with different intent, even if they both modify the address.
If you tried to reuse RelocatePlayerTo for corrections, you’d blur the line between intentional moves and error fixes, making your domain model less expressive and harder to reason about later.
2. Yes, add a PlayerAddressHasBeenAdjusted domain event
Domain events should mirror the business actions they represent, just like use cases. Here’s why this matters:
- Downstream systems might react differently to each event:
- A
PlayerRelocatedevent could trigger logistics updates (e.g., re-routing ongoing shipments). - A
PlayerAddressHasBeenAdjustedevent might trigger error logging, a notification to customer support to verify the correction, or a request to amend a previously sent shipment.
- A
- Tracking these events separately gives you auditability: you can easily see when a player moved vs. when an address was fixed, which is critical for compliance or business analytics.
Example implementation snippets
To make this concrete, here’s how you might structure the use case and event (using C#-style pseudocode):
AdjustPlayerAddress Use Case
public class AdjustPlayerAddressUseCase { private readonly IPlayerRepository _playerRepo; private readonly IDomainEventPublisher _eventPublisher; public AdjustPlayerAddressUseCase(IPlayerRepository playerRepo, IDomainEventPublisher publisher) { _playerRepo = playerRepo; _eventPublisher = publisher; } public async Task Execute(Guid playerId, Address correctedAddress, string correctionReason = null) { var player = await _playerRepo.GetById(playerId); // Encapsulate business rules for address correction in the aggregate player.AdjustAddress(correctedAddress, correctionReason); await _playerRepo.Save(player); _eventPublisher.Publish(new PlayerAddressHasBeenAdjusted(playerId, correctedAddress, correctionReason)); } }
Player Aggregate Method
public class Player : AggregateRoot { public Address CurrentAddress { get; private set; } private List<AddressCorrection> AddressCorrections { get; } = new(); // ... other properties and methods ... public void AdjustAddress(Address correctedAddress, string correctionReason) { // Add business rules here: e.g., ensure correction is meaningful, validate new address format if (correctedAddress.Equals(CurrentAddress)) throw new InvalidOperationException("Corrected address matches current address"); CurrentAddress = correctedAddress; // Track correction history directly in the aggregate for auditability AddressCorrections.Add(new AddressCorrection(DateTime.UtcNow, correctionReason, correctedAddress)); } }
Domain Event
public record PlayerAddressHasBeenAdjusted( Guid PlayerId, Address CorrectedAddress, string? CorrectionReason) : IDomainEvent;
Edge case to consider
If your business truly doesn’t distinguish between intentional moves and error fixes (e.g., every address change is just a "update" with no separate business process), you could argue for reusing the existing use case—but this is rare. Most businesses care about why an address changed, and DDD’s strength is capturing those nuances.
Always align your design with the language your business team uses. If they say "fix the player’s address" instead of "relocate the player again," your use cases should reflect that.
内容的提问来源于stack exchange,提问作者DmitriBodiu

