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