Strukturieren Sie ein Python-Projekt in hexagonaler Architektur mit Lambda AWS - AWS Prescriptive Guidance

Die vorliegende Übersetzung wurde maschinell erstellt. Im Falle eines Konflikts oder eines Widerspruchs zwischen dieser übersetzten Fassung und der englischen Fassung (einschließlich infolge von Verzögerungen bei der Übersetzung) ist die englische Fassung maßgeblich.

Strukturieren Sie ein Python-Projekt in hexagonaler Architektur mit Lambda AWS

Erstellt von Furkan Oruc (AWS), Dominik Goby (), Darius Kunce (AWS) und Michal Ploski () AWS AWS

Umgebung: PoC oder Pilotprojekt

Technologien: Web- und mobile Apps; Container und Mikroservices; Serverlos; Modernisierung

AWSDienste: Amazon DynamoDB; AWS Lambda; Amazon Gateway API

Übersicht

Dieses Muster zeigt, wie ein Python-Projekt in hexagonaler Architektur mithilfe AWS von Lambda strukturiert wird. Das Muster verwendet das AWS Cloud Development Kit (AWSCDK) als Infrastructure as Code (IaC) -Tool, Amazon API Gateway als und Amazon DynamoDB als Persistenzschicht. REST API Die hexagonale Architektur folgt domänengesteuerten Entwurfsprinzipien. In der hexagonalen Architektur besteht Software aus drei Komponenten: Domäne, Ports und Adaptern. Ausführliche Informationen zu sechseckigen Architekturen und ihren Vorteilen finden Sie im Leitfaden Aufbau sechseckiger Architekturen auf. AWS

Voraussetzungen und Einschränkungen

Voraussetzungen

  • Ein aktives Konto AWS

  • Erfahrung in Python

  • Vertrautheit mit AWS Lambda AWSCDK, Amazon API Gateway und DynamoDB

  • Ein GitHub Konto (siehe Anweisungen zur Registrierung)

  • Git (siehe Installationsanleitung)

  • Ein Code-Editor, mit dem Sie Änderungen vornehmen und Ihren Code übertragen können GitHub (z. B. Visual Studio Code oder JetBrains PyCharm)

  • Docker ist installiert und der Docker-Daemon ist betriebsbereit

Produktversionen

  • Git Version 2.24.3 oder höher

  • Python-Version 3.7 oder höher

  • AWSCDKv2

  • Poesie-Version 1.1.13 oder höher

  • AWSLambda Powertools für Python Version 1.25.6 oder höher

  • pytest Version 7.1.1 oder höher

  • Moto-Version 3.1.9 oder höher

  • pydantic Version 1.9.0 oder höher

  • Boto3 Version 1.22.4 oder höher

  • mypy-boto3-dynamodb Version 1.24.0 oder höher

Architektur

Zieltechnologie-Stack

Der Zieltechnologie-Stack besteht aus einem Python-Dienst, der API Gateway, Lambda und DynamoDB verwendet. Der Dienst verwendet einen DynamoDB-Adapter, um Daten zu speichern. Es bietet eine Funktion, die Lambda als Einstiegspunkt verwendet. Der Service verwendet Amazon API Gateway, um a verfügbar zu machen RESTAPI. Der API verwendet AWS Identity and Access Management (IAM) für die Authentifizierung von Clients.

Zielarchitektur

Zur Veranschaulichung der Implementierung wird in diesem Muster eine serverlose Zielarchitektur bereitgestellt. Clients können Anfragen an einen API Gateway-Endpunkt senden. APIGateway leitet die Anfrage an die Lambda-Zielfunktion weiter, die das hexagonale Architekturmuster implementiert. Die Lambda-Funktion führt Erstellungs-, Lese-, Aktualisierungs- und Löschvorgänge (CRUD) in einer DynamoDB-Tabelle durch.

Wichtig: Dieses Muster wurde in einer PoC-Umgebung getestet. Bevor Sie eine Architektur in einer Produktionsumgebung einsetzen, müssen Sie eine Sicherheitsüberprüfung durchführen, um das Bedrohungsmodell zu identifizieren und eine sichere Codebasis zu erstellen.

Zielarchitektur für die Strukturierung eines Python-Projekts in hexagonaler Architektur

Die API unterstützt fünf Operationen an einer Produktentität:

  • GET /productsgibt alle Produkte zurück.

  • POST /productserstellt ein neues Produkt.

  • GET /products/{id}gibt ein bestimmtes Produkt zurück.

  • PUT /products/{id}aktualisiert ein bestimmtes Produkt.

  • DELETE /products/{id}löscht ein bestimmtes Produkt.

Sie können die folgende Ordnerstruktur verwenden, um Ihr Projekt so zu organisieren, dass es dem hexagonalen Architekturmuster folgt:  

app/ # application code |--- adapters/ # implementation of the ports defined in the domain |--- tests/ # adapter unit tests |--- entrypoints/ # primary adapters, entry points |--- api/ # api entry point |--- model/ # api model |--- tests/ # end to end api tests |--- domain/ # domain to implement business logic using hexagonal architecture |--- command_handlers/ # handlers used to execute commands on the domain |--- commands/ # commands on the domain |--- events/ # events triggered via the domain |--- exceptions/ # exceptions defined on the domain |--- model/ # domain model |--- ports/ # abstractions used for external communication |--- tests/ # domain tests |--- libraries/ # List of 3rd party libraries used by the Lambda function infra/ # infrastructure code simple-crud-app.py # AWS CDK v2 app

Tools

AWSDienste

  • Amazon API Gateway ist ein vollständig verwalteter Service, der Entwicklern die Erstellung, Veröffentlichung, Wartung, Überwachung und Sicherung APIs in jeder Größenordnung erleichtert.

  • Amazon DynamoDB ist eine vollständig verwaltete, serverlose SQL Key-Value-No-Datenbank, die für die Ausführung von Hochleistungsanwendungen in jeder Größenordnung konzipiert ist.

  • AWSLambda ist ein serverloser, ereignisgesteuerter Rechendienst, mit dem Sie Code für praktisch jede Art von Anwendung oder Backend-Service ausführen können, ohne Server bereitstellen oder verwalten zu müssen. Sie können Lambda-Funktionen von über 200 AWS Diensten und SaaS-Anwendungen (Software as a Service) aus starten und zahlen nur für das, was Sie tatsächlich nutzen.

Tools

  • Git wird in diesem Muster als Versionskontrollsystem für die Codeentwicklung verwendet.

  • Python wird als Programmiersprache für dieses Muster verwendet. Python bietet Datenstrukturen auf hoher Ebene und einen Ansatz für objektorientierte Programmierung. AWSLambda bietet eine integrierte Python-Laufzeit, die den Betrieb von Python-Diensten vereinfacht.

  • Visual Studio Code wird IDE für die Entwicklung und das Testen dieses Musters verwendet. Sie können jedes verwendenIDE, das die Python-Entwicklung unterstützt (z. B. PyCharm).

  • AWSCloud Development Kit (AWSCDK) ist ein Open-Source-Framework für die Softwareentwicklung, mit dem Sie Ihre Cloud-Anwendungsressourcen mithilfe vertrauter Programmiersprachen definieren können. Dieses Muster verwendet dieCDK, um die Cloud-Infrastruktur als Code zu schreiben und bereitzustellen.

  • Poesie wird verwendet, um Abhängigkeiten im Muster zu verwalten.

  • Docker wird von der verwendet, AWS CDK um das Lambda-Paket und die Lambda-Layer zu erstellen.

Code

Der Code für dieses Muster ist im Beispiel-Repository für die hexagonale Architektur von GitHub Lambda verfügbar.

Bewährte Methoden

Um dieses Muster in einer Produktionsumgebung zu verwenden, folgen Sie diesen bewährten Methoden:

Dieses Muster verwendet AWSX-Ray, um Anfragen über den Einstiegspunkt, die Domäne und die Adapter der Anwendung zu verfolgen. AWSX-Ray hilft Entwicklern dabei, Engpässe zu erkennen und hohe Latenzen zu ermitteln, um die Anwendungsleistung zu verbessern.

Epen

AufgabeBeschreibungErforderliche Fähigkeiten

Erstellen Sie Ihr eigenes Repository.

  1. Loggen Sie sich ein bei GitHub.

  2. Erstellen Sie ein neues Repository. Anweisungen finden Sie in der GitHub Dokumentation.

  3. Klonen Sie das Beispiel-Repository für dieses Muster und übertragen Sie es in das neue Repository in Ihrem Konto.

App-Developer

Installieren Sie die Abhängigkeiten.

  1. Installieren Sie Poetry.

    pip install poetry
  2. Installiere Pakete aus dem Stammverzeichnis. Der folgende Befehl installiert die Anwendung und die AWS CDK Pakete. Außerdem werden Entwicklungspakete installiert, die für die Ausführung von Komponententests erforderlich sind. Alle installierten Pakete werden in einer neuen virtuellen Umgebung platziert.

    poetry install
  3. Um eine grafische Darstellung der installierten Pakete zu sehen, führen Sie den folgenden Befehl aus.

    poetry show --tree
  4. Aktualisieren Sie alle Abhängigkeiten.

    poetry update
  5. Öffnen Sie eine neue Shell in der neu erstellten virtuellen Umgebung. Sie enthält alle installierten Abhängigkeiten.

    poetry shell
App-Developer

Konfiguriere deineIDE.

Wir empfehlen Visual Studio Code, aber Sie können jeden beliebigen Code IDE Ihrer Wahl verwenden, der Python unterstützt. Die folgenden Schritte beziehen sich auf Visual Studio Code.

  1. Aktualisieren Sie die .vscode/settings Datei.

    { "python.testing.pytestArgs": [ "app/adapters/tests", "app/entrypoints/api/tests", "app/domain/tests" ], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, "python.envFile": "${workspaceFolder}/.env", }
  2. Erstellen Sie eine .env Datei im Stammverzeichnis des Projekts. Dadurch wird sichergestellt, dass das Stammverzeichnis des Projekts im Verzeichnis enthalten ist, PYTHONPATH sodass Sie es pytest finden und alle Pakete richtig erkennen können.

    PYTHONPATH=.
App-Developer

Führen Sie Komponententests aus, Option 1: Verwenden Sie Visual Studio Code.

  1. Wählen Sie den Python-Interpreter der virtuellen Umgebung, die von Poetry verwaltet wird.

  2. Führen Sie Tests im Test Explorer aus.

App-Developer

Unit-Tests ausführen, Option 2: Verwenden Sie Shell-Befehle.

  1. Starten Sie eine neue Shell in der virtuellen Umgebung.

    poetry shell
  2. Führen Sie den pytest Befehl im Stammverzeichnis aus.

    python -m pytest

    Alternativ können Sie den Befehl direkt von Poetry aus ausführen.

    poetry run python -m pytest
App-Developer
AufgabeBeschreibungErforderliche Fähigkeiten

Fordern Sie temporäre Anmeldeinformationen an.

Um bei der Ausführung über AWS Anmeldeinformationen auf der Shell zu verfügencdk deploy, erstellen Sie mithilfe von AWS IAM Identity Center (Nachfolger von AWS Single Sign-On) temporäre Anmeldeinformationen. Anweisungen finden Sie im Blogbeitrag So rufen Sie kurzfristige Anmeldeinformationen für die CLI Verwendung mit AWS IAM Identity Center ab.

App-Entwickler, AWS DevOps

Stellen Sie die Anwendung bereit.

  1. Installieren Sie die AWS CDK Version 2.

    npm install -g aws-cdk

    Weitere Informationen finden Sie in der AWSCDKDokumentation.

  2. Booten Sie das AWS CDK in Ihr Konto und Ihre Region.

    cdk bootstrap aws://12345678900/us-east-1 --profile aws-profile-name
  3. Stellen Sie die Anwendung mithilfe eines AWS Profils als AWS CloudFormation Stack bereit.

    cdk deploy --profile aws-profile-name
App-Entwickler, AWS DevOps

Testen Sie dasAPI, Option 1: Verwenden Sie die Konsole.

Verwenden Sie die APIGateway-Konsole, um das zu testenAPI. Weitere Informationen zu API Vorgängen und Anforderungs-/Antwortnachrichten finden Sie im Abschnitt API Verwendung der Readme-Datei im Repository. GitHub

App-Entwickler, AWS DevOps

Testen Sie dasAPI, Option 2: Verwenden Sie Postman.

Wenn Sie ein Tool wie Postman verwenden möchten:

  1. Installieren Sie Postman als eigenständige Anwendung oder Browsererweiterung.

  2. Kopieren Sie den Endpunkt URL für das API Gateway. Es wird das folgende Format haben.

    https://{api-id}.execute-api.{region}.amazonaws.com/{stage}/{path}
  3. Konfigurieren Sie die AWS Signatur auf der Registerkarte Autorisierung. Anweisungen finden Sie im AWS re:POST-Artikel zur Aktivierung der IAM Authentifizierung für API Gateway. REST APIs

  4. Verwenden Sie Postman, um Anfragen an Ihren Endpunkt zu senden. API

App-Entwickler, AWS DevOps
AufgabeBeschreibungErforderliche Fähigkeiten

Schreiben Sie Komponententests für den Geschäftsbereich.

  1. Erstellen Sie eine Python-Datei in dem app/domain/tests Ordner, indem Sie das test_ Dateinamenpräfix verwenden.

  2. Erstellen Sie eine neue Testmethode, um die neue Geschäftslogik anhand des folgenden Beispiels zu testen.

    def test_create_product_should_store_in_repository(): # Arrange command = create_product_command.CreateProductCommand( name="Test Product", description="Test Description", ) # Act create_product_command_handler.handle_create_product_command( command=command, unit_of_work=mock_unit_of_work ) # Assert
  3. Erstellen Sie eine Befehlsklasse im app/domain/commands Ordner. 

  4. Wenn die Funktionalität neu ist, erstellen Sie im app/domain/command_handlers Ordner einen Stub für den Befehlshandler.

  5. Führen Sie den Komponententest aus, um festzustellen, ob er fehlschlägt, da es immer noch keine Geschäftslogik gibt.

    python -m pytest
App-Developer

Implementieren Sie Befehle und Befehlshandler.

  1. Implementieren Sie Geschäftslogik in der neu erstellten Befehlshandler-Datei. 

  2. Deklarieren Sie für jede Abhängigkeit, die mit externen Systemen interagiert, eine abstrakte Klasse im app/domain/ports Ordner.

    class ProductsRepository(ABC): @abstractmethod def add(self, product: product.Product) -> None: ... class UnitOfWork(ABC): products: ProductsRepository @abstractmethod def commit(self) -> None: ... @abstractmethod def __enter__(self) -> typing.Any: ... @abstractmethod def __exit__(self, *args) -> None: ...
  3. Aktualisieren Sie die Befehlshandler-Signatur, sodass sie die neu deklarierten Abhängigkeiten akzeptiert, indem Sie die abstrakte Port-Klasse als Typanmerkung verwenden.

    def handle_create_product_command( command: create_product_command.CreateProductCommand, unit_of_work: unit_of_work.UnitOfWork, ) -> str: ...
  4. Aktualisieren Sie den Komponententest, um das Verhalten aller deklarierten Abhängigkeiten für den Befehlshandler zu simulieren.

    # Arrange mock_unit_of_work = unittest.mock.create_autospec( spec=unit_of_work.UnitOfWork, instance=True ) mock_unit_of_work.products = unittest.mock.create_autospec( spec=unit_of_work.ProductsRepository, instance=True )
  5. Aktualisieren Sie die Assertionslogik im Test, um nach den erwarteten Abhängigkeitsaufrufen zu suchen.

    # Assert mock_unit_of_work.commit.assert_called_once() product = mock_unit_of_work.products.add.call_args.args[0] assertpy.assert_that(product.name).is_equal_to("Test Product") assertpy.assert_that(product.description).is_equal_to("Test Description")
  6. Führen Sie den Komponententest aus, um zu sehen, ob er erfolgreich ist.

    python -m pytest
App-Developer

Schreiben Sie Integrationstests für sekundäre Adapter.

  1. Erstellen Sie eine Testdatei in dem app/adapters/tests Ordner, indem Sie test_ sie als Dateinamenpräfix verwenden.

  2. Verwenden Sie die Moto-Bibliothek, um AWS Dienste nachzuahmen.

    @pytest.fixture def mock_dynamodb(): with moto.mock_dynamodb(): yield boto3.resource("dynamodb", region_name="eu-central-1")
  3. Erstellen Sie eine neue Testmethode für einen Integrationstest des Adapters.

    def test_add_and_commit_should_store_product(mock_dynamodb): # Arrange unit_of_work = dynamodb_unit_of_work.DynamoDBUnitOfWork( table_name=TEST_TABLE_NAME, dynamodb_client=mock_dynamodb.meta.client ) current_time = datetime.datetime.now(datetime.timezone.utc).isoformat() new_product_id = str(uuid.uuid4()) new_product = product.Product( id=new_product_id, name="test-name", description="test-description", createDate=current_time, lastUpdateDate=current_time, ) # Act with unit_of_work: unit_of_work.products.add(new_product) unit_of_work.commit() # Assert
  4. Erstellen Sie eine Adapterklasse im app/adapters Ordner. Verwenden Sie die abstrakte Klasse aus dem Ports-Ordner als Basisklasse.

  5. Führen Sie den Komponententest aus, um festzustellen, ob er fehlschlägt, da immer noch keine Logik vorhanden ist.

    python -m pytest
App-Developer

Implementieren Sie sekundäre Adapter.

  1. Implementieren Sie Logik in der neu erstellten Adapterdatei.

  2. Aktualisieren Sie die Test-Assertionen.

    # Assert with unit_of_work_readonly: product_from_db = unit_of_work_readonly.products.get(new_product_id) assertpy.assert_that(product_from_db).is_not_none() assertpy.assert_that(product_from_db.dict()).is_equal_to( { "id": new_product_id, "name": "test-name", "description": "test-description", "createDate": current_time, "lastUpdateDate": current_time, } )
  3. Führen Sie den Komponententest aus, um zu sehen, ob er erfolgreich ist.

    python -m pytest
App-Developer

Schreiben Sie end-to-end Tests.

  1. Erstellen Sie eine Testdatei in dem app/entrypoints/api/tests Ordner, indem Sie test_ sie als Dateinamenpräfix verwenden. 

  2. Erstellen Sie ein Lambda-Kontext-Fixture, das vom Test zum Aufrufen von Lambda verwendet wird.

    @pytest.fixture def lambda_context(): @dataclass class LambdaContext: function_name: str = "test" memory_limit_in_mb: int = 128 invoked_function_arn: str = "arn:aws:lambda:eu-west-1:809313241:function:test" aws_request_id: str = "52fdfc07-2182-154f-163f-5f0f9a621d72" return LambdaContext()
  3. Erstellen Sie eine Testmethode für den Aufruf. API

    def test_create_product(lambda_context): # Arrange name = "TestName" description = "Test description" request = api_model.CreateProductRequest(name=name, description=description) minimal_event = api_gateway_proxy_event.APIGatewayProxyEvent( { "path": "/products", "httpMethod": "POST", "requestContext": { # correlation ID "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef" }, "body": json.dumps(request.dict()), } ) create_product_func_mock = unittest.mock.create_autospec( spec=create_product_command_handler.handle_create_product_command ) handler.create_product_command_handler.handle_create_product_command = ( create_product_func_mock ) # Act handler.handler(minimal_event, lambda_context)
  4. Führen Sie den Komponententest aus, um festzustellen, ob er fehlschlägt, da immer noch keine Logik vorhanden ist.

    python -m pytest
App-Developer

Implementieren Sie Primäradapter.

  1. Erstellen Sie eine Funktion für die API Geschäftslogik und deklarieren Sie sie als API Ressource.

    @tracer.capture_method @app.post("/products") @utils.parse_event(model=api_model.CreateProductRequest, app_context=app) def create_product( request: api_model.CreateProductRequest, ) -> api_model.CreateProductResponse: """Creates a product.""" ...

    Hinweis: Alle Decorators, die Sie sehen, sind Funktionen der AWS Lambda Powertools for Python-Bibliothek. Einzelheiten finden Sie auf der Website AWSLambda Powertools for Python.

  2. Implementieren Sie die LogikAPI.

    id=create_product_command_handler.handle_create_product_command( command=create_product_command.CreateProductCommand( name=request.name, description=request.description, ), unit_of_work=unit_of_work, ) response = api_model.CreateProductResponse(id=id) return response.dict()
  3. Führen Sie den Komponententest aus, um zu sehen, ob er erfolgreich ist.

    python -m pytest
App-Developer

Zugehörige Ressourcen

APGLeitfaden

AWSVerweise

Tools

IDEs