13

As I understand it, in DDD, it is appropriate to use a repository pattern with an aggregate root. My question is, should I return the data as an entity or domain objects/DTO?

Maybe some code will explain my question further:

Entity

public class Customer
{
  public Guid Id { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
}

Should I do something like this?

public Customer GetCustomerByName(string name) { /*some code*/ }

Or something like this?

public class CustomerDTO
{
  public Guid Id { get; set; }
  public FullName { get; set; }
}

public CustomerDTO GetCustomerByName(string name) { /*some code*/ }

Additional question:

  1. In a repository, should I return IQueryable or IEnumerable?
  2. In a service or repository, should I do something like.. GetCustomerByLastName, GetCustomerByFirstName, GetCustomerByEmail? or just make a method that is something like GetCustomerBy(Func<string, bool> predicate)?
amon
  • 132,749
  • 27
  • 279
  • 375
codefish
  • 155
  • 1
  • 1
  • 5
  • What will `GetCustomerByName('John Smith')` return if you have twenty John Smiths in your database? It looks like you're assuming no two people have the same name. – bdsl Oct 16 '17 at 23:30

3 Answers3

9

should I return the data as an entity or domain objects/DTO

Well that entirely depends on your use cases. The only reason I can think of returning a DTO instead of a full entity is if your entity is huge and you only need to work on a subset of it.

If this is the case, then maybe you should reconsider your domain model and split your big entity into related smaller entities.

  1. In a repository, should I return IQueryable or IEnumerable?

A good rule of thumb is to always return the simplest (highest in the inheritence hierarchy) type possible. So, return IEnumerable unless you want to let the repository consumer work with an IQueryable.

Personally, I think returning an IQueryable is a leaky abstraction but I've met several developers who argue passionately that it isn't. In my mind all querying logic should be contained and concealed by the Repository. If you allow calling code to customise their query then what's the point of the repository?

  1. In a service or repository, should I do something like.. GetCustomerByLastName, GetCustomerByFirstName, GetCustomerByEmail? or just make a method that is something like GetCustomerBy(Func predicate)?

For the same reason as I mentioned in point 1, definitely do not use GetCustomerBy(Func<string, bool> predicate). It may seem tempting at first, but this is exactly why people have learned to hate generic repositories. It's leaky.

Things like GetByPredicate(Func<T, bool> predicate) are only useful when they're hidden behind concrete classes. So, if you had an abstract base class called RepositoryBase<T> which exposed protected T GetByPredicate(Func<T, bool> predicate) which was only used by concrete repositories (eg, public class CustomerRepository : RepositoryBase<Customer>) then that would be fine.

MetaFight
  • 11,549
  • 3
  • 44
  • 75
  • So your saying, its okay to have DTO classes in domain layer? – codefish Jan 06 '16 at 15:30
  • That's not what I was trying to say. I'm not a DDD guru so I can't say if that's acceptable. Out of curiosity, why don't you wasn't to return the full entity? Is it too large? – MetaFight Jan 06 '16 at 15:34
  • Oh not really. I just want to know what is the right and acceptable thing to do. Is it to return a full entity or just the subset of it. I guess it depends base on your answer. – codefish Jan 06 '16 at 15:53
  • "The only reason I can think of returning a DTO instead of a full entity is if your entity is huge and you only need to work on a subset of it." -> another good (maybe stronger) reason is when you're returning a complex entity (like a join) like this example [here](https://softwareengineering.stackexchange.com/a/375451/214020) where repository can return SalesOrder or SalesOrder+Items. – drizin Sep 13 '20 at 00:52
6

There's a sizable community of folks who use CQRS to implement their domains. My feeling is that, if the interface of your repository is analogous to the best practices used by them, that you won't go too far astray.

Based on what I've seen...

1) Command handlers usually use the repository to load the aggregate via a repository. Commands target a single specific instance of the aggregate; the repository loads the root by ID. There isn't, that I can see, a case where the commands are run against a collection of aggregates (instead, you would first run a query to get the collection of aggregates, then enumerate the collection and issue a command to each.

Therefore, in contexts where you are going to be modifying the aggregate, I would expect the repository to return the entity (aka the aggregate root).

2) Query handlers don't touch the aggregates at all; instead, they work with projections of the aggregates -- value objects that describe the state of the aggregate/aggregates at some point in time. So think ProjectionDTO, rather than AggregateDTO, and you have the right idea.

In contexts where you are going to be running queries against the aggregate, preparing it for display, and so on, I'd expect to see a DTO, or a DTO collection, returned, rather than an entity.

All of your getCustomerByProperty calls look like queries to me, so they would fall into the latter category. I'd probably want to use a single entry point to generate the collection, so I would be looking to see if

getCustomersThatSatisfy(Specification spec)

is a reasonable choice; the query handlers would then construct the appropriate specification from the parameters given, and pass that specification to the repository. The downside is that signature really suggests that the repository is a collection in memory; it's not clear to me that the predicate buys you much if the repository is just an abstraction of running a SQL statement against a relational database.

There are some patterns that can help out, though. For example, instead of building the specification by hand, pass to the repository a description of the constraints, and allow the implementation of the repository to decide what to do.

Warning: java like typing detected

interface CustomerRepository {
    interface ConstraintBuilder {
        void setLastName();
        void setFirstName();
    }

    interface ConstraintDescriptor {
        void copyTo(ConstraintBuilder builder);
    }

    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor);
}

SQLBackedCustomerRepository implements CustomerRepository {
    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor) {
        WhereClauseBuilder builder = new WhereClauseBuilder();
        descriptor.copyTo(builder);
        Query q = createQuery(builder.build());
        //...
     }
}

CollectionBackedCustomerRepository implements CustomerRepository {
    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor) {
        PredicateBuilder builder = new PredicateBuilder();
        descriptor.copyTo(builder);
        Predicate p = builder.build();
        // ...
}

class MatchLastName implements CustomerRepository.ConstraintDescriptor {
    private final lastName;
    // ...

    void copyTo(CustomerRepository.ConstraintBuilder builder) {
        builder.setLastName(this.lastName);
    }
}

In conclusion: the choice between providing an aggregate and providing a DTO depends on what you are expecting the consumer to do with it. My guess would be one concrete implementation supporting an interface for each context.

VoiceOfUnreason
  • 32,131
  • 2
  • 42
  • 79
  • That's all nice and good, but the asker doesn't mention using CQRS. You're right though, his problem would be non-existent if he did. – MetaFight Jan 06 '16 at 18:12
  • I have no knowledge on CQRS tho I heard about it. As I understand it, when thinking on what to return if AggregateDTO or ProjectionDTO, I'll return ProjectionDTO. Then on the `getCustomersThatSatisfy(Specification spec)` is I'll just list the properties I needed for search options. Am I getting it right? – codefish Jan 07 '16 at 01:44
  • Unclear. I don't believe AggregateDTO should exist. The point of an aggregate is to ensure that all changes satisfy the business invariant. It's encapsulation of the domain rules that need to be satisfied -- ie, it is behavior. Projections, on the other hand, are representations of some snapshot of the state that was acceptable to the business. See edit for attempt to clarify the specification. – VoiceOfUnreason Jan 07 '16 at 04:54
  • I agree with the VoiceOfUnreason that an AggregateDTO shouldn't exist - I think of the ProjectionDTO as a viewmodel for data only. It can adapt it's shape to what's required by the caller, pulling in data from various sources as necessary. The Aggregate Root should be a complete representation of all related data so all referential rules can be tested before saving. It only changes shape under more drastic circumstances, such as DB table changes or modified data relationships. – Brad Irby Jun 06 '17 at 16:17
1

Based on my knowledge of DDD you should have this,

public CustomerDTO GetCustomerByName(string name) { /*some code*/ }

In a repository, should I return IQueryable or IEnumerable?

It depends, you would find mix views from different peoples for this question. But I personally believe that if your repository will be used by a service like CustomerService then you may use IQueryable otherwise stick with IEnumerable.

Should Repositories return IQueryable?

In a service or repository, should I do something like.. GetCustomerByLastName, GetCustomerByFirstName, GetCustomerByEmail? or just make a method that is something like GetCustomerBy(Func predicate)?

In your repository you should have a generic function like you said GetCustomer(Func predicate) but in your service layer add three different methods, this is because there is a chance your service being called from different clients and they need different DTOs.

You may also use Generic Repository Pattern for common CRUD, if you are not already aware.

Muhammad Raja
  • 284
  • 1
  • 7