Introduction

Tout d'abord, je ne suis pas l'autorité sur les DTO, les POCO, l'architecture orientée objet, ou quoi que ce soit. Cependant, j'utilise une architecture centrée sur les DTO/POCO dès que je peux et il y a au moins un des mes anciens clients qui possède maintenant une classe d'entité nommée DevicePoco (il y avait déjà un objet d'entité nommé Device qui suivait le modèle Active Record, sinon je n'aurais jamais nommé un objet XXXPoco). Quand mon client a vu le nouvel objet avec cet affreux nom dans sa BAL (NDT : couche métier), sa première réaction a été bien sûr de se demander: « Diable, mais qu'est-ce qu'un POCO ? ».

Il n'y a pas si longtemps, j'étais à une réunion du groupe d'utilisateurs de Visual Studio, où la question des POCO, et en quoi ils sont différents des DTO, est venue. Le présentateur qui, en toute honnêteté, est un développeur bien meilleur que moi, a déclaré avec assurance que POCO et DTO sont la même chose. J'ai immédiatement serré les deux mains sur ma bouche pour m'empêcher de crier « Ils ne le sont pas ! ». Donc il semble y avoir un manque d'information dans la communauté .Net de ce que sont ces objets. Je vais essayer de clarifier la question.

Qu'est-ce qu'un Data Transfer Object (DTO) ?

Normalement, c'est là que je dirais que Wikipedia définit un DTO tel que... Malheureusement, la définition actuelle de Wikipedia est assez horrible, sauf pour la ligne suivante :

Image personnelle La différence entre les Data Transfer Objects et les Business Objects (NDT : objets métier) ou les Data Access Objects (NDT : objets d'accès aux données) est qu'un DTO n'a pas de comportement, sauf pour le stockage et la récupération de ses propres données (accesseurs et mutateurs).

C'est le concept clé. Un DTO stocke des données. Il n'a pas de méthode (comportement) autre que les accesseurs et mutateurs qui ne sont utilisés que pour lire et modifier les données. Pourquoi faire un objet aussi simple que cela ? Parce qu'il fait un excellent conteneur de données, léger et fortement typé, lorsque vous souhaitez déplacer les données de votre DAL (NDT : couche d'accès aux données) à votre BAL ou entre les je-ne-sais-quelles couches de votre architecture n-tiers.

Ci-dessous le code du PersonDTO dont je me sers dans plusieurs de mes posts récents. Vous remarquerez qu'il ne fait vraiment rien d'autre que de stocker des données :

 
Sélectionnez
public class PersonDTO : DTOBase
{
    public Guid PersonGuid { get;set; }
    public int PersonId { get; set; }
    public DateTime UtcCreated { get; set; }
    public DateTime UtcModified { get; set; }
    public string Password { get; set; }
    public string Name { get; set; }
    public string Nickname { get; set; }
    public string PhoneMobile { get; set; }
    public string PhoneHome { get; set; }
    public string Email { get; set; }
    public string ImAddress { get; set; }
    public int ImType { get; set; }
    public int TimeZoneId { get; set; }
    public int LanguageId { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public int ZipCode { get; set; }
 
    // Constructeur
    // Pas de paramètre et tous les types valeurs sont initialisés à des
    // valeurs NULL définies dans CommonBase.
    public PersonDTO()
    {         
        PersonGuid = Guid_NullValue;
        PersonId = Int_NullValue;
        UtcCreated = DateTime_NullValue;
        UtcModified = DateTime_NullValue;  
        Name = String_NullValue;
        Nickname = String_NullValue;
        PhoneMobile = String_NullValue;
        PhoneHome = String_NullValue;
        Email = String_NullValue;
        ImAddress = String_NullValue;
        ImType = Int_NullValue;
        TimeZoneId = Int_NullValue;
        LanguageId = Int_NullValue;
        City = String_NullValue;
        State = String_NullValue;
        ZipCode = Int_NullValue;
        IsNew = true;
    }
}

Donc pour résumer, un DTO est juste une collection de propriétés. Il n'a pas de validation, pas de logique métier, pas de logique d'aucune sorte. C'est juste un simple conteneur de données léger, utilisé pour déplacer les données entre les couches.

Alors qu'est-ce qu'un POCO ?

Un POCO n'est pas un DTO. POCO signifie Plain Old CLR Objet, ou Plain Old C # Object. C'est essentiellement la version .Net d'un POJO, Plain Old Java Object. Un POCO est votre objet métier. Il dispose de données, de logique de validation et de toute autre logique métier que vous voulez y mettre. Mais il y a une chose qu'un POCO n'a pas et c'est ce qui en fait un POCO : il ne dispose pas de méthode de persistance. Si vous avez un POCO de type Person, vous ne pouvez pas avoir de méthode Person.GetPersonById(), ni Person.Save(). Les POCO ne contiennent que des données et la logique métier, pas de logique de persistance d'aucune sorte. Le terme que vous entendrez pour ce concept est « Persistance Ignorance » (PI). Les POCO ignorent la logique de persistance.

Ci-dessous le code de ma classe BAL.Person qui est un POCO. Vous remarquerez qu'il ne contient pas de logique de persistance d'aucune sorte, juste des données et des méthodes de validation. Vous remarquerez également que je ne recrée pas un tas d'accesseurs et de mutateurs pour mes données de personnes. Ce serait encombrer ma classe Personne, et ils seraient redondants car ils ont déjà été définis dans PersonDTO. Au lieu de cela, je n'ai qu'une seule propriété nommée Data qui est de type PersonDTO. Cette approche permet de lire et modifier une personne très facilement. Lors de la lecture d'une personne, je récupère seulement un PersonDTO de ma DAL et j'affecte la propriété person.Data = personDTO. Lors de la sauvegarde, mes méthodes prennent toutes un PersonDTO comme paramètre de telle sorte que je peux utiliser ma propriété person.Data pour cela.

 
Sélectionnez
public class Person : BALBase
{
        // Data
        // Cette propriété existe pour tous les objets de la BAL, et elle est
        // du type DTO correspondant. Il s'agit du mécanisme
        // que nous utilisons pour mettre en oeuvre un héritage "a un"
        // (NDT : aussi appelé composition) au lieu d'un héritage "est un".
        public PersonDTO Data { get; set; }
 
 
        // Person - constructeur par défaut
        public Person() {this.Data = new PersonDTO();}
        // Person - prend un DTO
        public Person(PersonDTO dto) {this.Data = dto;}
 
 
        // Validation
        public override List<ValidationError> Validate()
        {
            // Appelle toutes les fonctions de validation
            Val_Name();
            Val_Email();
            Val_Password();
            Val_TimeZone();
            Val_City();
            Val_State();
            Val_ZipCode();
            Val_ImType();
 
            // Si la liste ValidationErrors est vide, alors
            // nous avons passé la validation avec succès.
            return this.ValidationErrors;
        }
 
 
        // Méthodes de validation :
        // Il y a seulement 2 exigences sur les méthodes de validation.
        // - Elles doivent gérer l'ajout d'une erreur de validation à la
        //   liste ValidationErrors si elles trouvent une erreur.
        // - Vous devez ajouter manuellement l'appel à toutes les méthodes de
        //   validation à la fonction Validate().
        // Lorsque vous créez un objet ValidationError, souvenez-vous
        // que le premier paramètre est le nom exact du champ
        // qui a la mauvaise valeur, et le message d'erreur ne doit pas
        // contenir le nom du champ, mais plutôt le tag <FieldName>,
        // qui sera remplacé par l'interface utilisateur ou par l'application appelante.
 
 
        // Val_Name
        public bool Val_Name()
        {
            // Nom requis
            if (this.Data.Name == DTOBase.String_NullValue)
            {
                this.ValidationErrors.Add(new ValidationError("Person.Name", "<FieldName> est requis"));
                return false;
            }
            else
            {
                return true;
            }
        }

        // Vous avez compris l'idée. Je ne mets pas le reste du code de 
        // validation pour que vous ne deveniez pas aveugle en lisant toutes ces lignes.
}

Aucune logique de persistance ici, juste les données et la logique de validation. Donc vous pensez sans doute que si la logique de persistance ne va pas dans ma classe d'entité, alors où va-t-elle ? La réponse est : dans une autre classe. Les POCO doivent être alimentés par une autre classe qui encapsule la logique de persistance de cette entité, comme un Repository ou un Contrôleur de Données. En général, j'utilise un Repository. Pour cet exemple, j'ai utilisé une classe PersonRepository qui encapsule la logique pour obtenir un nouvel objet personne, obtenir un personDTO de la DAL, puis affecter person.Data = personDTO. Même chose pour la sauvegarde. Ma classe PersonRepository a une méthode SavePerson() qui prend un objet personne et envoie ensuite la valeur de person.Data à la DAL pour être sauvegardé. Le code pour obtenir et définir une entité personne dans mon interface utilisateur ressemble à ceci :

 
Sélectionnez
// Alimenter l'instance à partir de la base de données :
Person person = PersonRepository.GetPersonByEmail(email);
 
// Sauvegarder l'instance en base de données :
PersonRepository.SavePerson(ref person, true);

Pourquoi devrais-je faire tout ça ?

La question que vous pourriez poser est : quel est le but ? Pourquoi devrais-je utiliser ces modèles au lieu de me contenter d'utiliser des DataTables et de mettre toute ma logique de persistance dans mes entités ? La réponse n'est pas si simple. Je préfère une architecture POCO/Repository/DTO, mais ce n'est pas la seule bonne façon de concevoir une application. Je pense que les avantages sont que c'est une architecture très propre et très facile à maintenir. Elle assure la séparation de la logique métier et de la logique de persistance, qui est plus conforme au Principe de Responsabilité Unique (Single Responsibility Principle). Utiliser des POCO avec des DTO et une DAL bien conçue est à peu près la meilleure architecture que vous puissiez construire (voir ma série sur l'architecture d'une DAL haute performance). Mais, je pense que la plupart des développeurs. NET seront amenés à utiliser des POCO et des Repositories (mais pas de DTO) via les ORM (NDT : mapping objet-relationnel). Entity Framework, NHibernate, et beaucoup de ces ORM exigent ou partent du principe que l'architecture est de type POCO. En fait, Entity Framework a introduit une interface IPOCO dont j'ai du mal à trouver de la documentation, mais cela ressemble à quelque chose de bien. D'autre part, si vous voulez vous mettre au Domain Driven Design, vous devrez adopter les POCO. Dans son excellent livre « Applying Domain-Driven Design and Patterns » (NDT : « Appliquer le Domain-Driven Design et ses modèles »), Jimmy Nilsson a même une section intitulée « POCO as a Lifestyle » (NDT : « POCO en tant que mode de vie »).

Conclusion

Donc, en conclusion, apprenez à aimer les POCO et assurez-vous de ne pas propager de fausses informations à leur sujet en les assimilant à des DTO. Les DTO sont de simples conteneurs de données, utilisés pour transférer des données entre les couches d'une application. Les POCO sont des objets métier à part entière avec la caractéristique qu'ils ignorent la logique de persistance (aucune méthode get() ni save() ). Enfin, si vous n'avez pas encore lu le livre de Jimmy Nilsson, empruntez-le dans les stocks de votre université préférée. Il y a des exemples en C # et c'est une excellente lecture.

Liens utiles

Remerciements

Je tiens à remercier Rudy Lacovara pour son aimable autorisation de traduire l'article et jacques_jean pour sa relecture.