1
votes

Quel est le bon endroit pour interroger les projections DTO?

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?

  • Référentiels: non, ils ne doivent pas renvoyer de DTO
  • Contrôleurs: j'aimerais les garder aussi minces que possible et éviter de les faire effectuer des requêtes et / ou des mappages

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?


0 commentaires

3 Réponses :


0
votes

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


2 commentaires

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 ...



0
votes

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:

  • Application gérant les données sur les employés, les services, etc. d'une entreprise.
  • Cas d'utilisation: donnez-moi une liste d'employés (juste le nom, l'e-mail, le départ, le salaire) des employés de moins de 30 ans avec un salaire supérieur à 2 000 euros.

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.


2 commentaires

Salut, merci pour votre réponse, mais elle ne répond pas à la question: 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.



0
votes

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:

https://github.com/ VaughnVernon / IDDD_Samples / tree / master / iddd_collaboration / src / main / java / com / saasovation / collaboration / application / forum / data

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


0 commentaires