C# – Separation of concerns between repository and service in DDD with complex entities

asp.net-mvccdomain-driven-designsql

This probably seems as an example of opinion-based question, but I'm actually looking for rationale on how to decide correctly, I believe there is a correct solution that can be backed by solid arguments (that's why I chose SO and hope is really not only opinion based, but I can be wrong; if you'd feel so, please leave a note in a comment). I can't decode how far to go with SoC in DDD and whether is ok to have mostly empty domain objects.

We have .NET, DDD, mainly use EF as our ORM in repositories, our application lives in Azure. For complex queries, we use stored procedures that return multisets (because of Azure SQL) – so one query = one round trip to the server (is's worth it, the query is performed usually on > 10 separate tables, no joins, so it would require many round trips).

For simplicity, imagine domain object called NavigationItem, that have multiple titles (in different languages), items are stored in one table, localized titles are stored in another table, where foreign key is the item title contains culture id.

The navigation object might look like this:

public class NavigationItem
{
    public int FileRecordId { get; set; }

    public int? ParentId { get; set; }

...

    public Dictionary<int, string> Title { get; set; }
}

So the domain object, returned by repository, is constructed (in repository) this way:

// load list of navigation items
var reader = command.ExecuteReader();
navigation.Items = ((IObjectContextAdapter)_context).ObjectContext.Translate<NavigationItem>(reader).ToList();

// process localized titles
reader.NextResult();
var titles = ((IObjectContextAdapter)_context).ObjectContext.Translate<TitleInfo>(reader).ToList();

// associate titles, so the whole NavigationItem is constructed
foreach (var item in navigation.Items)
{
    item.Title = titles.Where(t => t.FileRecordId == item.FileRecordId).ToDictionary(key => key.CultureId, value => value.Title);
}

This is simple example, just to get the point – my question is, wouldn't it be better to return just list of navigation items (where I should omit the Title property) and list of titles and combine that in a service layer, when creating DTD object? Repository is responsible for persistence and retrieving persisted data, and I'm not sure, whether this is simply "too much". On the other hand, the requirement on repository might be just "give me whole entity with titles". Separation of concerns is naturally a good way, but I'm not sure if this is still concern of the repository.

Please take this just as a simple example, my question is more general – in this example, each domain object NavigationItem contains Title, so it feels it's OK to do this in repository. But when we have more complex etities, there is another problem – e.g., each File has common properties, like Name. Some files have image properties, same have also pdf document properties, some have localized titles… some don't. In this case, it seems much more "natural" to have one entity (common File), and return List, List, List, … from repository and combine it in the service. Or on the other hand, do all the translation in repository, but it would mean that domain object (aggregate root) File will contain entities like ImageInfo, TitleInfo, etc., which will be in most cases null.

What approach – and mainly, why – would you recommend?

EDIT: To be more specific – from DDD perspective, is it ok keep Entity partially empty and sometimes in repository just retrieve few properties (or sometimes all of them), or is better approach to separate such entity to smaller ones and return one entity that aggregates lists of all possible entities? I believe that there can be reasonable answer, typically "yes, partly initialized entity is ok", or "no, it's wrong and you should always use as granular entities as possible", etc.

Best Answer

Sounds like you are using domain entities for both domain operations (commands, which modify application state) and queries (which don't modify application state, but return data for the user). The problems you describe are a direct result of trying to get that single domain entity to do both jobs, which don't mix very well.

My recommendation is to read on CQRS (Command Query Responsibility Segragation) e.g. CQRS by Martin Fowler and try to limit your domain entities to only retrieve what data they need to complete the domain operation. Even though the amount of classes and interfaces will grow, your application will become much more simple and more maintainable due to more adherence to the Single Responsibility Principle.

Also, your choice of domain objects and especially Aggregate roots leads me to question the validity of your domain model. Is a File really such an entity, that it's of dire meaning to the business people? What kind of operations do you do on the file, and what's their value to the business? The whole idea in Domain Driven Design is to identify the actual business entities on which you should act, as if you wouldn't have a computer at all. An Order is a common example. Operations on that Aggragate root would be "Submit", "Cancel" etc. File is just an infrastructural concern, which most often has no value or place in the domain logic.