Créez une application avec une architecture propre tout en utilisant DI + mock en Python

Lors de la mise en œuvre d'une application web utilisant Python en entreprise, nous avons décidé d'introduire une architecture propre afin de développer en séparant les intérêts pour chaque couche.

Nous avons mis en place une pratique pour développer des applications testables en utilisant le typage progressif et l'injection de dépendances pour partager des connaissances avec les membres de l'équipe.

Clean Architecture

Cette fois, le but est un exemple utilisant Python, donc l'explication de Clean Architecture sera simple.

CleanArchitecture (1).jpg (Extrait de The Clean Architecture)

L'architecture propre est intéressante dans Architecture proposée par Robert C. Martin en 2012 C'est une méthode de conception pour réaliser la séparation. L'architecture hexagonale (préconisée en 2005) et l'architecture oignon (préconisée en 2008) ont le même objectif.

Les principaux avantages sont la clarification de la logique métier (bien qu'elle ne soit pas unique à Clean Architecture), l'indépendance de l'interface utilisateur, de la base de données et du framework, et l'amélioration de la testabilité.

Comme écrit dans le texte d'origine, il n'est pas nécessaire de couper les cercles concentriques dans les mêmes couches que l'échantillon, et vous pouvez augmenter ou diminuer les couches selon les besoins, mais cette fois j'ai décidé de couper les couches selon le manuel.

Saisie progressive à l'aide de la saisie

Le typage progressif s'est établi dans les langages à typage dynamique ces jours-ci, mais Python peut également bénéficier de la saisie pendant le développement en utilisant typing.

Contrairement à TypeScript, les erreurs de type ne sont pas détectées au moment de la construction, mais avec un IDE comme PyCharm (IntelliJ IDEA), vous pouvez développer tout en bénéficiant du type, de sorte que vous pouvez obtenir une expérience de développement comparable au typage statique.

L'architecture propre incorpore le principe d'inversion de dépendance, et l'utilisation du «typage» sera essentielle pour réaliser des opérations via l'interface.

Injection de dépendances utilisant des classes abstraites

Python n'a pas d'interface, mais il a des classes abstraites. Les classes abstraites peuvent avoir des implémentations, mais la clé de la loi de l'inversion des dépendances est qu'elles dépendent d'abstractions, de sorte que les classes abstraites peuvent également répondre à cette exigence.

Inversons la dépendance de la couche Repository que la couche Usecase appelle réellement via une classe abstraite.

class SampleRepository(metaclass=ABCMeta):
    @abstractmethod
    async def get(self, resource_id: str) -> dict:
        raise NotImplementedError


class SampleRepositoryImpl(SampleRepository):
    async def get(self, resource_id: str) -> dict:
        return {"id": id}


class SampleUsecase:
    sample_repository: SampleRepository

    def __init__(self, repository: SampleRepository):
        self.sample_repository = repository

   def get(self, resource_id: str) -> dict:
        return asyncio.run(self.sample_repository.get(resource_id))


SampleUsecase(repository=SampleRepositoryImpl()).get()

À ce stade, le référentiel que la couche Usecase connaît est une classe abstraite et ne connaît pas son implémentation. De plus, il est possible de donner une implémentation à une classe abstraite, mais cela viole la loi de l'inversion des dépendances, donc en donnant @ abstractmethod, la classe concrète a une implémentation.

L'injection de dépendances est la clé de l'architecture propre, mais vous pouvez injecter des dépendances avec ce que l'on appelle vanilla DI sans utiliser de conteneur DI. (Bien sûr, le conteneur DI n'est pas inutile)

La vérification de type par saisie est puissante lors de l'injection de dépendances, et avec le support IDE, vous ne devriez pas ressentir de gêne avec les langages à typage dynamique.

Test unitaire utilisant un simulacre

L'un des avantages de l'architecture propre est la testabilité. Une moquerie partielle peut être faite facilement, associée au fait que l'injection de dépendance est effectuée. Profitez également des fonctionnalités puissantes du module standard de Python ʻunit test`.

test_sample.py


class SampleRepositoryMock(SampleRepository):
    async def get(self, resource_id: str) -> dict:
        raise NotImplementedError


class TestSampleUsecase(TestCase):
    def test_get(self):
        get_mock = AsyncMock(return_value={"id": "0002"})

        repository = SampleRepositoryMock()
        repository.get = get_mock
        usecase = SampleUsecase(repository=repository)

        self.assertEqual(usecase.get("0002"), {"id": "0002"})
        get_mock.assert_called_with("0002")

MagicMock est fourni comme une maquette partielle, et vous pouvez facilement remplacer la méthode en l'utilisant. Vous pouvez également vérifier qu'il a été appelé.

De plus, ʻAsyncMock est également fourni comme valeur de retour attendue, ce qui élimine le besoin de générer une ʻasync def provisoire pour renvoyer un collout natif ou une variable de type Future de bas niveau. .. Lors de la communication avec l'extérieur, les collouts natifs utilisant ʻasync / await` sont souvent utilisés en Python de nos jours, mais cela ne peut également être pris en charge que par le module standard.

Au moment des tests, l'injection de dépendances est utilisée pour insérer un référentiel factice à tester dans la couche Usecase afin de vérifier le fonctionnement de la couche à tester uniquement.

En passant, la même chose peut être obtenue en injectant une instance de classe qui est réellement utilisée sans préparer une classe factice, puis en la remplaçant à l'aide d'une simulation partielle. Il n'y a pas de bonne réponse à cela, et si vous voulez vous concentrer uniquement sur la couche à tester, il est préférable d'injecter une classe factice, et si vous testez sur plusieurs couches, vous n'avez pas besoin d'un mannequin.

La raison pour laquelle il est souhaitable d'être un mannequin dans le premier cas est que si vous oubliez le simulacre, le test réussit dépend de la couche dont il dépend, et la classe factice qui déclenche une exception si vous n'utilisez pas le simulacre est plus fiable. Parce qu'il peut être détecté.

Utiliser la classe de données dans la couche d'entité

Clean Architecture place la logique métier dans la couche Entity. Entity me rappelle DDD, mais ce n'est pas exactement la même chose que Entity dans DDD, ni la même chose que Value Object.

Selon le texte original, «l'entité est un ensemble de structures de données et de fonctions qui encapsule des règles métier».

Entities encapsulate Enterprise wide business rules. An entity can be an object with methods, or it can be a set of data structures and functions. It doesn’t matter so long as the entities could be used by many different applications in the enterprise.

(Extrait de The Clean Architecture)

Il n'y a pas de réglementation stricte sur le type de propriété que l'entité devrait avoir, mais personnellement, dataclass introduit à partir de Python 3.7 ) Je pense que cela va bien avec.

@dataclass
class Article(frozen=True, eq=True)
  id: str
  body: str

article1 = Article(id="1", body="test")
article2 = Article(id="1", body="test")

#Vrai par la vérification d'identité basée sur la valeur(Eq par défaut=Vrai est défini)
article1 == article2 

#Une erreur se produit car il est gelé et est un objet immuable
article1.id = "2" 

Les fonctions que possède «dataclass» elle-même sont pratiques en elles-mêmes, mais en particulier, des propriétés telles que «gelé» pour avoir des conditions invariantes et «eq» pour la vérification d'identité par valeur sont des comportements attendus pour la couche Entité. (Bien sûr, l'immuabilité et la comparaison de valeurs ne sont pas toujours nécessaires)

De plus, non seulement dans la couche Entité, il est difficile de définir la méthode __eq__ de manière séquentielle lors de la vérification de l'identité d'un objet dans un test unitaire, et le mérite d'utiliser la classe de données sera excellent.

Échantillon publié sur Github

Comme il serait redondant d'inclure tout l'exemple de code dans l'article, j'ai publié la source d'une application Web simple qui fonctionne réellement sur Github.

J'utilise Flask comme framework Web, mais selon l'idée de Clean Architecture, seule la couche Rest dépend de la bibliothèque, et le reste est presque du code Python vanille.

Un code de test unitaire est également inclus, alors vérifiez-le si vous êtes intéressé.

finalement

Python est devenu l'un des langages les plus populaires ces dernières années, car l'apprentissage automatique a gagné en popularité et il semble que son utilisation dans le domaine Web augmente en conséquence.

Avec l'avènement du typage progressif et de la classe de données, la partie où l'expérience de développement était à l'origine altérée dans le langage à typage dynamique est entièrement couverte, et avec l'ajout de la productivité élevée conventionnelle, c'est également une option attrayante en tant qu'application Web. Il est devenu.

J'écris moi-même Python au niveau de la production depuis quelques mois, mais j'espère que cela sera utile pour ceux qui voudront essayer Python dans le futur car je peux utiliser l'expérience que j'ai cultivée dans d'autres langues.

Recommended Posts

Créez une application avec une architecture propre tout en utilisant DI + mock en Python
Créez et essayez un environnement OpenCV et Python en quelques minutes à l'aide de Docker
Créer une image avec des caractères avec python (japonais)
Résolvez des équations simultanées en un instant en utilisant Python
Pour envoyer automatiquement des e-mails avec des pièces jointes à l'aide de l'API Gmail en Python
Envoyer un e-mail avec Excel en pièce jointe en Python
Créez un environnement interactif pour l'apprentissage automatique avec Python
Créer un environnement de développement d'applications d'apprentissage automatique avec Python
Faire une application utilisant tkinter un fichier exécutable avec cx_freeze
Créer un environnement d'apprentissage automatique à l'aide de Python sur MacOSX
Authentification de base avec mot de passe crypté (.htpasswd) avec bouteille en python
[Python] Créez un robot d'exploration Web basé sur les événements à l'aide de l'architecture sans serveur d'AWS
Précautions lors de l'utilisation de Python avec AtCoder
Choses à garder à l'esprit lors de l'utilisation de cgi avec python.
Imprimer un PDF en Python sous Windows: utiliser une application externe
Analyse morphologique avec Igo + mecab-ipadic-neologd en Python (avec bonus Ruby)
Tri rapide d'un tableau en Python 3
Grattage au sélénium en Python
[S3] CRUD avec S3 utilisant Python [Python]
Exploitez LibreOffice avec Python
Utilisation de Quaternion avec Python ~ numpy-quaternion ~
Débogage avec pdb en Python
Créer un œuf avec python
[Python] Utilisation d'OpenCV avec Python (basique)
Gérer les sons en Python
Créer un environnement python3 avec ubuntu 16.04
Grattage avec Tor en Python
Construire un environnement python avec direnv
Tweet avec image en Python
Combiné avec ordinal en Python
Application Web avec Python + Flask ② ③
Construisons git-cat avec Python
Traduit à l'aide de googletrans en Python
Utilisation du mode Python dans le traitement
DI (injection de dépendances) en Python
Utiliser OpenCV avec Python @Mac
Application Web avec Python + Flask ④
Envoyer en utilisant Python avec Gmail
Comment gérer l'erreur d'installation de python dans pyenv (BUILD FAILED)
Extraction d'objets dans l'image par correspondance de modèles en utilisant OpenCV avec Python
J'ai essayé de créer une application todo en utilisant une bouteille avec python
Créez un environnement d'exécution Python à l'aide de GPU avec GCP Compute Engine
Implémenter le traitement du classement avec des liens en Python à l'aide de Redis Sorted Set
Comment écrire quoi faire lorsque l'application est affichée pour la première fois dans Qt pour Python avec Designer