Question
Handling very large collections in DDD
I've seen this kind of question asked before, but the business requirements were slightly different, so the suggested solutions don't work well for me. I've been learning Domain Driven Design (DDD) recently, and I figured the best way to do it is to rewrite one of my past real-world projects the DDD way. Here is one requirement I'm struggling with, simplified a bit to omit some unnecessary details:
- The user can create an "Incident" record in the application.
- The incident is initially created as a draft, and while it is a draft the user can then add affected servers to the incident.
- There can be up to 30 000 servers added - but no more than that
- The drafts can exist for a long time (days, even weeks), and new servers can be added at any point
There are 2 ways to implement this logic that I came up with, however they both have some downsides and I'm not sure if they follow the DDD principles correctly:
1.
The initially obvious way to model it is to create an Aggregate Root called Incident, which will contain a collection of AffectedServer entities/aggregates. Then, when a user adds a new server, we retrieve the Incident from a repository and attempt to add the server. An example in C#:
public class Incident
{
//Ommited the constructor and other fields/properties/methods
private List<AffectedServer> _servers;
public IReadOnlyList<AffectedServer Servers => _servers;
public void AddServer(AffectedServer server)
{
if (_servers.Count > 30_000)
{
throw new InvalidOperationException("The number of servers cannot exceed 30 000");
}
_servers.Add(server);
// publish the domain events here
}
}
However, when adding a new server we have to retrieve the server collection as well, which will cause a noticeable performance hit.
- The other solution is to instead extract AffectedServer to a separete aggregate root itself and handle the domain logic (ensuring that there are no more than 30 000 servers) within a Domain Service. The new aggregate root would still reside in the same bounded context as the Incident aggregate root, and would refer to the incident only by an identifier. An example in C#:
public class AffectedServerDomainService
{
//Ommited the constructor and other fields/properties/methods
public async Task AddServerToIncident(AffectedServer affectedServer)
{
int serverCount = _serverRepository.GetServerCountForIncident(affectedServer.IncidentId);
if (serverCount >= 30_000)
{
throw new InvalidOperationException("The number of servers cannot exceed 30 000");
}
_serverRepository.AddServer(affectedServer);
// publish the domain events here
}
}
This prevents us from retrieving a potentially very large collection from the repository, but since it is no longer a part of the Incident aggregate root (where it feels like it should naturally belong) it feels like the domain logic becomes a bit fractured.
I feel like the #2 solution is the better one, but am I correct? Does it violate DDD principles, or is it still a valid approach?