Erstellen Sie eine Anwendung mit Clean Architecture, während Sie DI + mock in Python verwenden

Bei der Implementierung einer Webanwendung mit Python in Unternehmen haben wir uns für die Einführung von Clean Architecture entschieden, um diese zu entwickeln und gleichzeitig die Interessen für jede Ebene zu trennen.

Wir haben eine Praxis für die Entwicklung testbarer Anwendungen mit progressiver Typisierung und Abhängigkeitsinjektion zusammengestellt, um Wissen mit Teammitgliedern zu teilen.

Clean Architecture

Diesmal ist der Zweck ein Beispiel mit Python, daher ist die Erklärung der sauberen Architektur einfach.

CleanArchitecture (1).jpg (Zitiert aus The Clean Architecture)

Saubere Architektur ist von Interesse für Architektur von Robert C. Martin im Jahr 2012 vorgeschlagen Es ist eine Entwurfsmethode, um eine Trennung zu erreichen. Sechseckige Architektur (befürwortet 2005) und Zwiebelarchitektur (befürwortet 2008) verfolgen dasselbe Ziel.

Die Hauptvorteile sind die Klärung der Geschäftslogik (obwohl dies nicht nur für Clean Architecture gilt), die Unabhängigkeit von Benutzeroberfläche, Datenbank und Framework sowie die Verbesserung der Testbarkeit.

Wie im Originaltext geschrieben, ist es nicht erforderlich, die konzentrischen Kreise in die gleichen Ebenen wie das Beispiel zu schneiden, und Sie können die Ebenen nach Bedarf vergrößern oder verkleinern. Diesmal habe ich mich jedoch entschlossen, die Ebenen gemäß dem Lehrbuch zu schneiden.

Allmähliches Tippen mit Tippen

Die schrittweise Eingabe hat sich heutzutage in dynamisch typisierten Sprachen etabliert, aber Python kann auch während der Entwicklung von Typen profitieren, indem es "Typisierung" verwendet.

Im Gegensatz zu TypeScript werden Typfehler beim Erstellen nicht erkannt. Mit einer IDE wie PyCharm (IntelliJ IDEA) können Sie jedoch entwickeln, während Sie vom Typ profitieren, sodass Sie eine Entwicklungserfahrung erzielen, die mit der statischen Typisierung vergleichbar ist.

Clean Architecture enthält das Prinzip der Abhängigkeitsinversion, und die Verwendung von "Typisierung" ist für die Erzielung von Vorgängen über die Schnittstelle von entscheidender Bedeutung.

Abhängigkeitsinjektion unter Verwendung abstrakter Klassen

Python hat keine Schnittstelle, aber abstrakte Klassen. Abstrakte Klassen können Implementierungen haben, aber der Schlüssel zum Gesetz der Abhängigkeitsumkehr besteht darin, dass sie von Abstraktionen abhängen, sodass abstrakte Klassen diese Anforderung auch erfüllen können.

Kehren wir die Abhängigkeit der Repository-Schicht um, die die Usecase-Schicht tatsächlich über eine abstrakte Klasse aufruft.

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()

Derzeit ist das Repository, das die Usecase-Schicht kennt, eine abstrakte Klasse und kennt ihre Implementierung nicht. Es ist auch möglich, einer abstrakten Klasse eine Implementierung zuzuweisen, dies verstößt jedoch gegen das Gesetz der Abhängigkeitsumkehr. Wenn Sie also "@ abstractmethod" angeben, verfügt die konkrete Klasse über eine Implementierung.

Abhängigkeitsinjektion ist der Schlüssel zu Clean Architecture, aber Sie können Abhängigkeiten mit sogenanntem Vanilla DI injizieren, ohne einen DI-Container zu verwenden. (Natürlich ist DI-Container nicht unnötig)

Die Typprüfung durch Eingabe ist beim Einfügen von Abhängigkeiten leistungsstark. Mit der IDE-Unterstützung sollten Sie sich mit dynamisch typisierten Sprachen wenig unwohl fühlen.

Unit Test mit Mock

Einer der Vorteile von Clean Architecture ist die Testbarkeit. Teilweise Verspottung kann leicht durchgeführt werden, verbunden mit der Tatsache, dass die Abhängigkeitsinjektion durchgeführt wird. Nutzen Sie auch die leistungsstarken Funktionen des Python-Standardmoduls "unittest".

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 wird als Teilmodell bereitgestellt, und Sie können die Methode einfach ersetzen, indem Sie diese verwenden. Sie können auch überprüfen, ob es aufgerufen wurde.

Darüber hinaus wird "AsyncMock" auch als erwarteter Rückgabewert bereitgestellt, wodurch die Notwendigkeit entfällt, eine vorläufige "Async-Def" zu generieren, um ein natives Collout oder eine Variable vom Typ "Future" auf niedriger Ebene zurückzugeben. .. Bei der Kommunikation nach außen werden heutzutage in Python häufig native Collouts mit "async / await" verwendet, dies kann jedoch auch nur vom Standardmodul unterstützt werden.

Zum Zeitpunkt des Tests wird Dependency Injection verwendet, um ein Dummy-Repository zum Testen in die Usecase-Ebene einzufügen, um den Betrieb nur der zu testenden Ebene zu überprüfen.

Das Gleiche kann übrigens erreicht werden, indem eine tatsächlich verwendete Klasseninstanz injiziert wird, ohne eine Dummy-Klasse vorzubereiten, und diese dann durch einen Teil-Mock ersetzt wird. Darauf gibt es keine richtige Antwort. Wenn Sie sich nur auf die zu testende Ebene konzentrieren möchten, ist es besser, eine Dummy-Klasse einzufügen. Wenn Sie über mehrere Ebenen hinweg testen, benötigen Sie keinen Dummy.

Der Grund, warum es im ersteren Fall wünschenswert ist, ein Dummy zu sein, ist, dass, wenn Sie den Mock vergessen, der Test abhängig von der Ebene, von der er abhängt, bestanden wird und die Dummy-Klasse, die eine Ausnahme auslöst, wenn Sie den Mock nicht verwenden, zuverlässiger ist. Weil es erkannt werden kann.

Verwenden Sie die Datenklasse in der Entitätsebene

Clean Architecture fügt Geschäftslogik in die Entitätsebene ein. Entität erinnert mich an DDD, aber es ist nicht genau dasselbe wie Entität in DDD, noch ist es dasselbe wie Wertobjekt.

Gemäß dem Originaltext ist "Entität eine Reihe von Datenstrukturen und Funktionen, die Geschäftsregeln kapseln."

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.

(Zitiert aus The Clean Architecture)

Es gibt keine strenge Regel, welche Art von Eigenschaft die Entität haben soll, sondern persönlich aus Python 3.7 eingeführte Datenklasse ) Ich denke es passt gut dazu.

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

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

#True durch wertbasierte Identitätsprüfung(Gl. Standardmäßig=True ist gesetzt)
article1 == article2 

#Ein Fehler tritt auf, weil er eingefroren ist und ein unveränderliches Objekt ist.
article1.id = "2" 

Die Funktionen, die "Datenklasse" selbst hat, sind für sich genommen bequem, aber insbesondere Eigenschaften wie "eingefroren" für invariante Bedingungen und "Gleichung" für die Überprüfung der Identität nach Wert sind erwartete Verhaltensweisen für die Entitätsschicht. (Unveränderlichkeit und Wertevergleich sind natürlich nicht immer erforderlich)

Auch wenn es nicht auf die Entitätsschicht beschränkt ist, ist es schwierig, die Methode "eq" nacheinander zu definieren, wenn die Identität eines Objekts in einem Komponententest überprüft wird, und der Nutzen der Verwendung der "Datenklasse" ist groß.

Probe auf Github veröffentlicht

Da es überflüssig wäre, den gesamten Beispielcode in den Artikel aufzunehmen, habe ich die Quelle einer einfachen Webanwendung veröffentlicht, die tatsächlich auf Github funktioniert.

Ich verwende Flask als Webframework, aber nach der Idee von Clean Architecture hängt nur die Rest-Ebene von der Bibliothek ab, und der Rest ist fast Vanille-Python-Code.

Ein Unit-Test-Code ist ebenfalls enthalten. Probieren Sie ihn aus, wenn Sie interessiert sind.

Schließlich

Python hat sich in den letzten Jahren zu einer der beliebtesten Sprachen entwickelt, da maschinelles Lernen immer beliebter wird, und es scheint, dass seine Verwendung in der Webdomäne entsprechend zunimmt.

Mit dem Aufkommen der schrittweisen Eingabe und Datenklasse wird der Teil, in dem die Entwicklungserfahrung ursprünglich in der dynamisch typisierten Sprache beeinträchtigt war, vollständig abgedeckt, und mit der herkömmlichen hohen Produktivität ist dies auch eine attraktive Option als Webanwendung. Es ist geworden.

Ich selbst schreibe Python seit einigen Monaten auf Produktionsebene, aber ich hoffe, dass es für diejenigen hilfreich ist, die Python in Zukunft ausprobieren möchten, da ich die Erfahrung nutzen kann, die ich in anderen Sprachen gesammelt habe.

Recommended Posts

Erstellen Sie eine Anwendung mit Clean Architecture, während Sie DI + mock in Python verwenden
Erstellen und testen Sie mit Docker in wenigen Minuten eine OpenCV- und Python-Umgebung
Erstellen Sie ein Bild mit Zeichen mit Python (Japanisch)
Lösen Sie simultane Gleichungen sofort mit Python
So senden Sie automatisch E-Mails mit Anhängen mithilfe der Google Mail-API in Python
Senden Sie eine E-Mail mit Excel in Python
Erstellen Sie mit Python eine interaktive Umgebung für maschinelles Lernen
Erstellen Sie mit Python eine Entwicklungsumgebung für maschinelles Lernen
Erstellen Sie mit tkinter eine Anwendung mit cx_freeze zu einer ausführbaren Datei
Erstellen Sie eine Umgebung für maschinelles Lernen mit Python unter MacOSX
Grundlegende Authentifizierung mit verschlüsseltem Passwort (.htpasswd) mit Flasche in Python
[Python] Erstellen Sie einen ereignisgesteuerten Webcrawler mithilfe der serverlosen Architektur von AWS
Vorsichtsmaßnahmen bei der Verwendung von Python mit AtCoder
Dinge, die Sie bei der Verwendung von CGI mit Python beachten sollten.
PDF in Python unter Windows drucken: Verwenden Sie eine externe Anwendung
Morphologische Analyse mit Igo + mecab-ipadic-neologd in Python (mit Ruby-Bonus)
Sortieren Sie schnell ein Array in Python 3
Schaben mit Selen in Python
[S3] CRUD mit S3 unter Verwendung von Python [Python]
Betreiben Sie LibreOffice mit Python
Verwenden von Quaternion mit Python ~ numpy-quaternion ~
Debuggen mit pdb in Python
Ein Ei mit Python erstellen
[Python] Verwenden von OpenCV mit Python (Basic)
Umgang mit Sounds in Python
Erstellen Sie eine Python3-Umgebung mit Ubuntu 16.04
Scraping mit Tor in Python
Erstellen Sie mit direnv eine Python-Umgebung
Tweet mit Bild in Python
Kombiniert mit Ordnungszahl in Python
Webanwendung mit Python + Flask ② ③
Lassen Sie uns Git-Cat mit Python bauen
Übersetzt mit Googletrans in Python
Verwenden des Python-Modus in der Verarbeitung
DI (Dependency Injection) in Python
Verwenden von OpenCV mit Python @Mac
Webanwendung mit Python + Flask ④
Senden Sie mit Python mit Google Mail
Wie man mit Python-Installationsfehlern in pyenv umgeht (BUILD FAILED)
Objektextraktion im Bild durch Mustervergleich mit OpenCV mit Python
Ich habe versucht, eine ToDo-App mit einer Flasche mit Python zu erstellen
Erstellen Sie eine Python-Ausführungsumgebung mithilfe der GPU mit der GCP Compute Engine
Implementieren Sie die Ranking-Verarbeitung mit Bindungen in Python mithilfe von Redis Sorted Set
So schreiben Sie, was zu tun ist, wenn die Anwendung zum ersten Mal in Qt for Python mit Designer angezeigt wird