web-dev-qa-db-fra.com

dans DDD, les référentiels devraient-ils exposer une entité ou des objets de domaine?

Si je comprends bien, en DDD, il convient d'utiliser un modèle de référentiel avec une racine agrégée. Ma question est, dois-je renvoyer les données en tant qu'objets entité ou domaine/DTO?

Peut-être qu'un code expliquera ma question plus en détail:

Entité

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

Dois-je faire quelque chose comme ça?

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

Ou quelque chose comme ça?

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

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

Question supplémentaire:

  1. Dans un référentiel, dois-je retourner IQueryable ou IEnumerable?
  2. Dans un service ou un référentiel, dois-je faire quelque chose comme .. GetCustomerByLastName, GetCustomerByFirstName, GetCustomerByEmail? ou simplement créer une méthode qui ressemble à GetCustomerBy(Func<string, bool> predicate)?
11
codefish

dois-je retourner les données en tant qu'entités ou objets de domaine/DTO

Eh bien, cela dépend entièrement de vos cas d'utilisation. La seule raison pour laquelle je peux penser à retourner un DTO au lieu d'une entité complète est si votre entité est énorme et que vous n'avez besoin de travailler que sur un sous-ensemble de celle-ci.

Si tel est le cas, vous devriez peut-être reconsidérer votre modèle de domaine et diviser votre grande entité en entités plus petites connexes.

  1. Dans un référentiel, dois-je retourner IQueryable ou IEnumerable?

Une bonne règle d'or consiste à toujours renvoyer le type le plus simple (le plus élevé dans la hiérarchie d'héritage) possible. Donc, retournez IEnumerable sauf si vous voulez laisser le consommateur du référentiel travailler avec un IQueryable.

Personnellement, je pense que retourner un IQueryable est une abstraction qui fuit mais j'ai rencontré plusieurs développeurs qui soutiennent avec passion que ce n'est pas le cas. Dans mon esprit, toute logique d'interrogation devrait être contenue et cachée par le référentiel. Si vous autorisez le code appelant à personnaliser leur requête, quel est l'intérêt du référentiel?

  1. Dans un service ou un référentiel, dois-je faire quelque chose comme .. GetCustomerByLastName, GetCustomerByFirstName, GetCustomerByEmail? ou simplement créer une méthode qui ressemble à GetCustomerBy (prédicat Func)?

Pour la même raison que je l'ai mentionné au point 1, certainement ne pas utiliser GetCustomerBy(Func<string, bool> predicate). Cela peut sembler tentant au premier abord, mais c'est exactement pourquoi les gens ont appris à détester les référentiels génériques. C'est qui fuit.

Des choses comme GetByPredicate(Func<T, bool> predicate) ne sont utiles que lorsqu'elles sont cachées derrière des classes concrètes. Donc, si vous aviez une classe de base abstraite appelée RepositoryBase<T> Qui exposait protected T GetByPredicate(Func<T, bool> predicate) qui n'était utilisée que par des référentiels concrets (par exemple, public class CustomerRepository : RepositoryBase<Customer>), Ce serait parfait.

8
MetaFight

Il existe une importante communauté de personnes qui utilisent CQRS pour implémenter leurs domaines. Mon sentiment est que, si l'interface de votre référentiel est analogue aux meilleures pratiques utilisées par eux, vous ne vous égarerez pas trop.

D'après ce que j'ai vu ...

1) Les gestionnaires de commandes utilisent généralement le référentiel pour charger l'agrégat via un référentiel. Les commandes ciblent une seule instance spécifique de l'agrégat; le référentiel charge la racine par ID. Il n'y a pas, comme je peux le voir, un cas où les commandes sont exécutées sur une collection d'agrégats (à la place, vous devez d'abord exécuter une requête pour obtenir la collection d'agrégats, puis énumérer la collection et émettre une commande pour chacune.

Par conséquent, dans des contextes où vous allez modifier l'agrégat, je m'attendrais à ce que le référentiel retourne l'entité (aka la racine d'agrégat).

2) Les gestionnaires de requêtes ne touchent pas du tout aux agrégats; au lieu de cela, ils travaillent avec des projections des agrégats - objets de valeur qui décrivent l'état de l'agrégat/agrégats à un moment donné. Pensez donc à ProjectionDTO, plutôt qu'à AggregateDTO, et vous avez la bonne idée.

Dans des contextes où vous allez exécuter des requêtes sur l'agrégat, le préparer pour l'affichage, etc., je m'attends à voir un DTO, ou une collection DTO, renvoyé, plutôt qu'une entité.

Tous vos appels getCustomerByProperty ressemblent à des requêtes pour moi, donc ils entrent dans cette dernière catégorie. Je voudrais probablement utiliser un seul point d'entrée pour générer la collection, donc je chercherais à voir si

getCustomersThatSatisfy(Specification spec)

est un choix raisonnable; les gestionnaires de requêtes construiraient alors la spécification appropriée à partir des paramètres donnés et passeraient cette spécification au référentiel. L'inconvénient est que la signature suggère vraiment que le référentiel est une collection en mémoire; il n'est pas clair pour moi que le prédicat vous achète beaucoup si le référentiel n'est qu'une abstraction de l'exécution d'une instruction SQL sur une base de données relationnelle.

Cependant, il existe certains modèles qui peuvent aider. Par exemple, au lieu de construire la spécification à la main, passez au référentiel une description des contraintes et laissez l'implémentation du référentiel décider quoi faire.

Avertissement: Java comme la frappe détectée

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);
    }
}

En conclusion: le choix entre fournir un agrégat et fournir un DTO dépend de ce que vous attendez du consommateur. Je suppose qu'une implémentation concrète prend en charge une interface pour chaque contexte.

6
VoiceOfUnreason

Sur la base de ma connaissance de DDD, vous devriez avoir ceci,

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

Dans un référentiel, dois-je retourner IQueryable ou IEnumerable?

Cela dépend, vous trouverez des vues mixtes de différents peuples pour cette question. Mais je crois personnellement que si votre référentiel sera utilisé par un service comme CustomerService, vous pouvez utiliser IQueryable, sinon restez avec IEnumerable.

Les référentiels doivent-ils renvoyer IQueryable?

Dans un service ou un référentiel, dois-je faire quelque chose comme .. GetCustomerByLastName, GetCustomerByFirstName, GetCustomerByEmail? ou simplement créer une méthode qui ressemble à GetCustomerBy (prédicat Func)?

Dans votre référentiel, vous devriez avoir une fonction générique comme vous l'avez dit GetCustomer(Func predicate) mais dans votre couche de service, ajoutez trois méthodes différentes, car il est possible que votre service soit appelé à partir de différents clients et qu'ils nécessitent des DTO différents.

Vous pouvez également utiliser Generic Repository Pattern pour CRUD commun, si vous n'êtes pas déjà au courant.

1
Muhammad Raja