Pour différentes raisons (séparation des préoccupations, performances), je souhaite arrêter d'envoyer des entités de domaine à mes vues et utiliser les projections DTO à la place.
Je souhaite utiliser des requêtes ORM pour créer mes DTO, en sélectionnant uniquement les champs dont j'ai besoin à partir d'une ou plusieurs entités.
Quel est le bon endroit pour faire cela?
Je pense qu'il devrait y avoir un endroit centralisé (similaire aux référentiels pour les entités) pour interroger et créer des DTO, mais je n'ai pas réussi à trouver un modèle ou un nom pour cette pratique.
J'ai rencontré le terme assembleur DTO , mais il semble que ce modèle sert à mapper une ou plusieurs entités de domaine à un DTO, alors que dans mon cas, je souhaite ignorer le chargement d'entités complètes et traduire directement les requêtes de base de données en DTO.
Y a-t-il un modèle pour cela?
3 Réponses :
Vos DTO représentent un modèle de lecture . Pour cela, j'utilise généralement une requête "couche" (même si j'ai tendance à penser davantage en termes de préoccupations que de couches). J'utilise généralement I {Aggregate} Query
de la même manière que j'ai I {Aggregate} Repository
.
L'interface renvoie les données dans un format aussi simple que possible:
namespace Company.Project.DataAccess.Query { public class Customer { public class Specification { public string Name { get; private set; } public Specification WithName(string name) { Name = name; return this; } } public string Name { get; set; } public string Address { get; set; } } }
Je crée également un espace de noms Query
qui contient mes DTO. Cela signifie que le domaine contiendrait, par exemple, le Customer
AR et que l'espace de noms Query
contiendrait également un Customer
mais comme il est dans le Query
namespace il n'y a pas d'ambiguïté et je n'ai pas besoin d'ajouter un suffixe DTO
:
namespace Company.Project.DataAccess { public interface ICustomerQuery { int CountMatching(Query.Customer.Specification specification); int Count(); IEnumerable<DataRow> RowsMatching(Query.Customer.Specification specification); // perhaps OK for simple cases IEnumerable<Query.Customer> Matching(Query.Customer.Specification specification); // for something more complex } }
Merci pour votre réponse! Je suis un peu mal à l'aise avec le terme «requête» dans ICustomerQuery
: requête me semble comme l'objet que vous envoyez au magasin de données (vous envoyez une requête et obtenez un DTO en retour), pas comme l'objet qui encapsule les méthodes pour interroger le magasin de données. Je m'attendrais à quelque chose comme ICustomerQuerier
à la place? À part cela, votre séparation des préoccupations semble bonne!
Jamais vraiment pensé à cela de cette façon et j'achète totalement l'inconfort :) --- J'ai en fait une interface IQuery
qui représente une vraie Query
et j'aurais une requête
associée code> ICustomerQueryFactory qui serait injecté dans la classe CustomerQuery
afin de fournir des instances IQuery
pertinentes. La QueryFactory
est l'endroit où l'on échangerait des implémentations pour différentes bases de données si cela devait arriver (ce que je n'ai jamais vu). Je suppose que j'ai toujours considéré ICustomerQuery
comme une forme verbale plutôt que comme un nom --- étrange que vraiment ...
Un dto est un objet de la couche d'application. Ce que vous voulez, c'est de le remplir directement à partir de la base de données. C'est le côté requête de cqrs, où vous n'avez pas de modèle de domaine riche comme le côté commande, vous avez juste des projections (dtos) adaptées au client. Il s'agit du modèle de requête (lecture).
MISE À JOUR:
Ce sont les objets du modèle que j'utilise, similaire à la commande, mais une requête a un résultat:
public <QR extends QueryResult,Q extends Query<QR>> QR executeQuery(Q query);
DTO simple (ou une liste d'entre eux), avec les données de sortie pour les clients.
class EmployeeDto { private String name; private String email; private String departmentName; private double salary; ... <<getters and setters>> ... } class EmployeeDtoList implements QueryResult { private List<EmployeeDto> employeeDtos; ... <<getter and setter>> ... } class EmployeesByAgeAndSalary implements Query<EmployeeDtoList> { private Calendar maxAge; private double minSalary; ... <<getters and setters>> ... } class EmployeesByAgeAndSalaryHandler implements QueryHandler<EmployeeDtoList,EmployeesByAgeAndSalary> { ... @Override public EmployeeDtoList handle(EmployeesByAgeAndSalary query) { ... <<retrieve from the database the data we need to return, applying the criteria for the age and salary given in the "query" arg>> ... } }
DTO simple avec les données d'entrée (critères de recherche) pour exécuter la requête.
public interface QueryHandler <QR extends QueryResult, Q extends Query<QR>> { public QR handle ( Q query ); }
L'objet qui exécute la requête.
EXEMPLE:
Code:
public interface Query<QR extends QueryResult> {}
- La façade que le client utilise est un médiateur (interface avec cette méthode):
public interface QueryResult {}
Le médiateur serait implémenté par une classe qui gère un registre de gestionnaires de requêtes, afin qu'il redirige la requête adressée au gestionnaire de requêtes associé à la requête donnée.
Elle est similaire au modèle de commande, mais avec des requêtes.
Salut, merci pour votre réponse, mais elle ne répond pas à la question: où dois-je les interroger (dans quel type d’objet)?
Salut, j'ai répondu à "Quel est le bon endroit pour faire ça?". Je pensais que tu voulais dire quelle couche. Quel que soit le type d'objet, il est appelé "requête", un objet de requête. Vous avez des commandes et vous avez des requêtes. Eh bien, exactement, l'objet qui effectue la requête est un gestionnaire de requêtes.
C'est une excellente question,
Vous les mettez dans la couche application. Le modèle que vous recherchez s'appelle un service de requête.
Regardez comment Vaughn Vernon (l'auteur de Implementation Domain Driven Design) a fait dans son dépôt github:
Ensuite, il les remplit directement à partir de la base de données dans un service de requête (CQRS):
public ForumDiscussionsData forumDiscussionsDataOfId(String aTenantId, String aForumId) { return this.queryObject( ForumDiscussionsData.class, "select " + "forum.closed, forum.creator_email_address, forum.creator_identity, " + "forum.creator_name, forum.description, forum.exclusive_owner, forum.forum_id, " + "forum.moderator_email_address, forum.moderator_identity, forum.moderator_name, " + "forum.subject, forum.tenant_id, " + "disc.author_email_address as o_discussions_author_email_address, " + "disc.author_identity as o_discussions_author_identity, " + "disc.author_name as o_discussions_author_name, " + "disc.closed as o_discussions_closed, " + "disc.discussion_id as o_discussions_discussion_id, " + "disc.exclusive_owner as o_discussions_exclusive_owner, " + "disc.forum_id as o_discussions_forum_id, " + "disc.subject as o_discussions_subject, " + "disc.tenant_id as o_discussions_tenant_id " + "from tbl_vw_forum as forum left outer join tbl_vw_discussion as disc " + " on forum.forum_id = disc.forum_id " + "where (forum.tenant_id = ? and forum.forum_id = ?)", new JoinOn("forum_id", "o_discussions_forum_id"), aTenantId, aForumId); }