X

Comment organiser les espaces de noms de classe


Dans Une brève introduction à PHP Namespacing, j’ai donné une introduction rapide à la fois aux espaces de noms et à la façon dont PHP les gère.

Mais il y a des problèmes architecturaux de plus haut niveau concernant les espaces de noms que beaucoup de gens m’ont signalés récemment, alors j’ai pensé que j’allais sortir un peu sur “papier”.

J’ai vu quelques façons principales d’organiser les espaces de noms. Je vais discuter des avantages et des inconvénients de chacun.

Remarques avant de commencer

  • Il ne s’agit pas d’un article pédagogique, car il se peut qu’il n’y ait pas de méthode optimale pour chaque situation. Il s’agit simplement d’une discussion sur quelques options, et elle est principalement destinée à une organisation simple de l’espace de noms dans une application de petite à moyenne taille – pas l’entreprise, et pas nécessairement celles qui ont des problèmes architecturaux complexes.
  • “Petit” et “Moyen” et “Grand” ici se rapportent uniquement au nombre de classes et d’entités, et n’ont rien à voir avec les lignes de code, le nombre d’utilisateurs du site ou quoi que ce soit d’autre.
  • Nous prendrons l’exemple d’un Command qui envoie un Receipt à un User.
  • Notre espace de noms global sera actuellement App juste pour la brièveté, mais vous pouvez remplacer cela par Vendor\Package.

A quoi servent les espaces de noms ?

Avant même de parler de la spécificité façons nous pouvons namespace, parlons-en pourquoi nous le faisons. Je suis redevable à Shawn McCool (comme toujours) pour m’avoir aidé à relier certaines de mes vagues pensées ici à des concepts informatiques réels.

Comme Shawn me l’a fait remarquer, le but de l’espacement des noms est cohésion: décrivant à quel point un code est connecté à un autre code. Il a souligné que dans d’autres langages, les espaces de noms sont appelés “Packages” ou “Modules” – et une fois que vous réalisez cela, vous comprenez que nous voyons les sous-espaces de noms comme de petits modules individuels qui devraient s’appuyer le moins possible sur d’autres modules. (encapsulation). Si la modularité est l’un des principaux objectifs finaux de notre espacement de noms, alors cela devient une (parmi plusieurs) métriques que nous pouvons utiliser pour juger d’un style particulier d’espacement de noms.

Bien sûr, même cette affirmation – que la modularité est un objectif principal de l’espacement des noms – fait l’objet d’un débat. Mais quand je l’entends, je l’aime.

Approches d’espacement de noms

OK, allons-y.

Espace de noms global

<?php namespace App;

class SendReceipt {}
src
    Receipt
    ReceiptRepository
    SendReceipt
    SendReceiptHandler
    User
    UserRepository
    CreateUser
    CreateUserHandler

Avantages

Je suppose qu’il est plus simple de ne pas avoir à gérer les sous-espaces de noms ? Sur un très petit app, cela pourrait être bien. Si vous avez cinq classes, qui dit que vous devez les sous-nommer du tout ? S’il s’agit d’un package unique avec un seul objectif, ou d’une application qui a un seul “module”, il se peut qu’il n’ait besoin de rien d’autre qu’un seul espace de noms global.

Les inconvénients

Au moment où vous obtenez une application d’une quelconque complexité, il va être difficile de trouver vos classes dans l’énorme bouillie de votre espace de noms global. Si vous avez une séparation d’identité ou d’objectif entre vos types de classes – par exemple, Utilisateurs contre Reçus – cet espacement de noms global les jette ensemble dans un grand pot. Pas du tout modulable.

Regrouper par motif

<?php namespace App\Commands;

class SendReceipt {}
src
    Commands
        SendReceipt
        CreateUser
    Entities
        Receipt
        User
    Handlers
        SendReceiptHandler
        CreateUserHandler
    Repositories
        ReceiptRepository
        UserRepository

Avantages

Lorsque vous voulez traquer une commande, vous savez exactement où elle se trouve. Si votre cerveau vous dit “Je dois modifier une de mes commandes. Laquelle ? Celle qui envoie les reçus”, c’est un bon choix. C’est un niveau plus organisé que d’ignorer les espaces de noms, mais pas si profond que vous serez ennuyé de l’utiliser dans un site de taille moyenne.

De plus, vos classes associées (par exemple, les commandes) peuvent vivre côte à côte ; vous pouvez voir tous les parallèles qu’il peut y avoir entre SendReceipt et SendReminderpar exemple, et voyez comment ils se connectent tous les uns aux autres.

Cette méthode vous permet également d’architecturer les relations entre les types de classe par programmation. Par exemple, un bus de commande peut savoir que le gestionnaire d’une commande (qui vit à App\Commands\{commandName}) vit toujours à App\Handlers\{commandName}Handler.

Les inconvénients

Le faire de cette façon vous laisse avec des classes dans le même contexte répartis sur de nombreux espaces de noms différents. Par exemple, vous pourriez avoir App\Commands\SendReceipt, App\Receipt ou App\Entities\Receipt, App\Providers\ReceiptServiceProvider, App\Handlers\Commands\SendReceiptHandler, App\Repositories\ReceiptRepository, et ainsi de suite. Toute votre logique de reçu, éparpillée partout.

Si l’on mise sur l’encapsulation et la modularité, ce regroupement n’est pas gagnant. Parce que nous avons réparti tout le code sur la facturation, par exemple, sur l’ensemble de notre paysage d’espace de noms, l’organisation de la classe ne se concentre pas sur la création d’un module de facturation. Les classes sont côte à côte simplement parce qu’elles suivent le même modèle architectural, pas parce qu’elles sont réellement liées.

Regrouper par contexte

<?php namespace App\Billing;

class SendReceipt {}
src
    Billing
        Receipt
        ReceiptRepository
        SendReceipt
        SendReceiptHandler
    User
        User
        UserRepository
        CreateUser
        CreateUserHandler

Avantages

Si vous travaillez uniquement dans la facturation en ce moment, vous savez que vous aurez tout liés à la facturation réunis en un seul endroit. Pour vos reçus, l’entité, la commande, le gestionnaire de commandes, le référentiel, etc., tous réunis dans un ensemble agréable et soigné, facile à traiter en tant que groupe unique.

C’est là que nous commençons à faire l’expérience de l’encapsulation et de la modularité. Toutes nos classes liées à la facturation, quel que soit leur modèle de conception, sont réunies au même endroit, ce qui nous aide à les regrouper mentalement, en commençant même à les considérer comme une unité pouvant vivre en dehors de cette application.

Les inconvénients

Vos commandes sont maintenant réparties sur la base de code. Vos dépôts aussi. Et vos entités. Et vos gestionnaires de commandes.

Regrouper par contexte et modèle

<?php namespace App\Billing\Commands;

class SendReceipt {}
src
    Billing
        Entities
            Receipt
        Repositories
            ReceiptRepository
        Commands
            SendReceipt
        Handlers
            SendReceiptHandler
    User
        Entities
            User
        Repositories
            UserRepository
        Commands
            CreateUser
        Handlers
            CreateUserHandler

Avantages

Le séparer de cette manière vous donne le plus haut niveau de séparation de l’espace de noms – cela seul est un pro pour certaines personnes. Ceci est particulièrement utile si vous avez une grande base de code avec beaucoup de classes – plus il y a de classes, plus vous apprécierez les options supplémentaires de séparation. Imaginez ajouter un UpdateUser commande, un DeleteUser commande, un Subscription entité et référentiel et gestionnaires associés…

Tout comme Group by pattern, vous pouvez associer des classes par programme.

Et bien que vos classes soient séparées par modèle, elles sont toujours regroupées par contexte, vous avez donc toujours tous vos Receipt coder ensemble en un seul endroit. Nous obtenons toujours l’avantage de la modularité que nous avons fait dans le groupe par contexte.

Les inconvénients

Plus la définition de l’espace de noms est longue pour votre classe, plus l’énergie mentale doit être dépensée pour comprendre l’ensemble de la pile d’espaces de noms. Il y a plus de possibilités de fautes de frappe et de confusion. Et avec une application de petite ou moyenne taille, cela peut sembler exagéré.

Étant donné que vous regroupez vos classes par modèle au niveau le plus bas, vous ne bénéficiez pas autant de l’avantage du regroupement par contexte que le style Regrouper par contexte.

Goûter

OK, donc c’est beaucoup de théorie abstraite à ce sujet. Mais qu’en est-il d’un exemple concret ? J’ai pris quelques classes de SaveMyProposals comme exemple. Voyons comment nous gérons les Talks, les Conférences, et comment nous proposons les Talks aux conférences :

Espace de noms global

app
    Conference
    ConferenceRepository
    CreateConference
    CreateConferenceHandler
    CreateTalk
    CreateTalkHandler
    DeleteConference
    DeleteConferenceHandler
    DeleteTalk
    DeleteTalkHandler
    ProposeTalkToConference
    ProposeTalkToConferenceHandler
    RetractTalkProposal
    RetractTalkProposalHandler
    Talk
    TalkRepository
    UpdateConference
    UpdateConferenceHandler
    UpdateTalk
    UpdateTalkHandler

Regrouper par motif

app
    Commands
        CreateConference
        CreateTalk
        DeleteConference
        DeleteProposal
        DeleteTalk
        ProposeTalkToConference
        RetractTalkProposal
        UpdateConference
        UpdateTalk
    Entities
        Conference
        Proposal
        Talk
    Handlers
        CreateConferenceHandler
        CreateTalkHandler
        CreateProposalHandler
        DeleteConferenceHandler
        DeleteProposalHandler
        DeleteTalkHandler
        ProposeTalkToConferenceHandler
        RetractTalkProposalHandler
        UpdateConferenceHandler
        UpdateTalkHandler
    Repositories
        ConferenceRepository
        TalkRepository

Regrouper par contexte

app
    Conferences
        Conference
        ConferenceRepository
        CreateConference
        CreateConferenceHandler
        DeleteConference
        DeleteConferenceHandler
        UpdateConference
        UpdateConferenceHandler
    Talks
        CreateTalk
        CreateTalkHandler
        DeleteTalk
        DeleteTalkHandler
        ProposeTalkToConference
        ProposeTalkToConferenceHandler
        Talk
        TalkRepository
        RetractTalkProposal
        RetractTalkProposalHandler
        UpdateTalk
        UpdateTalkHandler

Regrouper par contexte et modèle

app
    Conferences
        Commands
            CreateConference
            DeleteConference
            UpdateConference
        Entities
            Conference
        Handlers
            CreateConferenceHandler
            DeleteConferenceHandler
            UpdateConferenceHandler
        Repositories
            ConferenceRepository
    Talks
        Commands
            CreateTalk
            DeleteTalk
            ProposeTalkToConference
            RetractTalkProposal
            UpdateTalk
        Entities
            Talk
        Handlers
            CreateTalkHandler
            DeleteTalkHandler
            ProposeTalkToConferenceHandler
            RetractTalkProposalHandler
            UpdateTalkHandler
        Repositories
            TalkRepository

Conclusion

Alors, quelle est la réponse ?

Ça dépend.

C’est possible que les structures organisationnelles plus simples fonctionnent mieux pour les applications avec moins de classes et d’entités, tandis que les structures organisationnelles plus grandes correspondent mieux aux systèmes organisationnels plus robustes. Mais ce n’est pas une règle stricte. Je ne suis même pas sûr à 100% que ce soit une règle du tout.

Je pense que les idées de modularité et d’encapsulation donnent du temps à votre cerveau. Réfléchissez à la manière dont vous le concevriez si chaque sous-espace de noms devait être supprimé des autres.

Mais à la fin, je dirais qu’il suffit de les essayer tous. Déterminez ce que vous aimez. Déterminez ce qui vous dérange. Déterminez les avantages que vous tirez de chacun. Vous comprendrez cela.