ddv
ddv

Reputation: 135

Domain Logic leaking into Queries in MVC.NET application using DDD

I am trying to implement a query to fetch some projection of data to MVC view from DB managed by domain model.

I've read that MVC controllers returning static views should request DTOs from Query handlers or so-called read model repositories rather than using aggregate root repositories returning full fledged domain objects. This way we maximize performance (optimizing queries for needed data) and reduce a risk of domain model misuse (we can't accidentally change model with DTOs).

The problem is that some DTO properties can't directly map to DB Table field and may be populated based on some business rule or be a result of some condition that is not implicitly stated in DB. That means that the query acts upon some logic leaking from domain. I heard that it's not right and that queries should directly filter, order, project and aggregate data from DB tables (using linq queries and EF in my case).

I envision 2 solutions so far:

1) Read model repositories internally query full domain model objects, use them to populate DTO properties (importantly those requiring some business logic from them to use). Here we don't gain performance benefits as we act upon instantiated domain models.

2) The other solution is cache all ever required data in DB through probably domain repositories (dealing with aggregate roots) so queries act upon data fields (with cached values) without addressing to domain logic. The consistency of the cached data then will be maintained by domain repositories and that results in some overhead as well.

Examples:

1) business rule can be as simple as string representation of certain objects or data (across the system) i.e. formatting;

2) Business rule can be calculated field returning bool as in the simple domain model below:

// first aggregate root
public class AssignedForm
{
    public int Id {get;set}
    public string FormName {get;set}
    public ICollection<FormRevision> FormRevisions {get;set}
    public bool HasTrackingInformation
    {
        get
        {
           return FormRevisions.Any(
                      fr=>   fr.RevisionType==ERevisionType.DiffCopy 
                             && fr.FormRevisionItems.Any)
        }
    }

    public void CreateNextRevision()
    {
         if(HasTrackingInformation)
         {
         .......
         }
         .......
    }
}

public enum ERevisionType { FullCopy=0,DiffCopy=1 }

public class FormRevision
{
   public int Id {get;set}
   public ERevisionType RevisionType {get;set}
   public ICollection<FormRevisionItem> FormRevisionItems {get;set}
}

And then we have a read model repository, say IFormTrackingInfoReader returning collection of objects

public class FormTrackingInfo
{
   public int AssignedFormId {get;set}
   public int AssignedFormName {get;set}
   public bool HasTrackingInformation {get;set}
}

The question is how to implement IFormTrackingInfoReader and populate HasTrackingInformation property sticking to DRY principle and without domain leaking into query. I saw people just return domain objects and use mapping to populate view model. Probably this is way to go. Thank you for your help.

Upvotes: 1

Views: 837

Answers (1)

Yugang Zhou
Yugang Zhou

Reputation: 7283

I don't like solution 1, the domain model is not persistent ignorant.

Personally, I prefer solution2. But the "ever required data" may be a problem. If new query requirement emerges, perhaps you'll need some data migration(I heard events replaying will do the trick when using event sourcing). So I'm thinking is there a hybrid solution: Use value objects to implement the derivations. And we can create new value object instances with dto.

public class SomeDto {

    public String getSomeDerivation() {
         return new ValueObject(some data in this dto).someDerivation();
    }
}

In this case, I think domain logic is protected and seperated from persistence. But I haven't tried this in real projects.

UPDATE1:

The hybrid solution does not fit your particular FormTrackingInfo case, but your solution two does. One example is (sorry, I'm not a .net guy, in Java)

public class CustomerReadModel {
     private String phoneNumber;
     private String ....//other properties

     public String getPhoneNumber() {
         return phoneNumber;
     }

     public String getAreaCode() {
         return new PhoneNumber(this.phoneNumber).getAreaCode();//PhoneNumber is a value object.
     }
}

But like I said, I haven't tried it in real projects, I think it's at most an interim solution when the cached data is not ready.

Upvotes: 0

Related Questions