Strukturieren Sie ein Python-Projekt in hexagonaler Architektur mit AWS Lambda - 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 AWS Lambda

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

Umgebung: PoC oder Pilotprojekt

Technologien: DevelopmentAndTesting; CloudNative; Container und Mikroservices; Serverlos; Modernisierung

AWS-Dienste: Amazon DynamoDB; AWS Lambda; Amazon API Gateway

Übersicht

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

Voraussetzungen und Einschränkungen

Voraussetzungen

Produktversionen

  • Git Version 2.24.3 oder höher

  • Python-Version 3.7 oder höher

  • AWS CDK v2

  • Poesie-Version 1.1.13 oder höher

  • AWS Lambda 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 eine REST-API verfügbar zu machen. Die 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. API Gateway 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

AWS-Services

  • Amazon API Gateway ist ein vollständig verwalteter Service, der es Entwicklern erleichtert, APIs in jeder Größenordnung zu erstellen, zu veröffentlichen, zu warten, zu überwachen und zu sichern.

  • Amazon DynamoDB ist eine vollständig verwaltete, serverlose NoSQL-Datenbank mit Schlüsselwerten, die für die Ausführung von Hochleistungsanwendungen in jeder Größenordnung konzipiert ist.

  • AWS Lambda ist ein serverloser, ereignisgesteuerter Rechenservice, 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-Services 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. AWS Lambda bietet eine integrierte Python-Laufzeit, die den Betrieb von Python-Services vereinfacht.

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

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

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

  • Docker wird vom AWS CDK verwendet, 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 AWS X-Ray, um Anfragen über den Einstiegspunkt, die Domäne und die Adapter der Anwendung zu verfolgen. AWS X-Ray hilft Entwicklern dabei, Engpässe zu identifizieren 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. Mit dem folgenden Befehl werden die Anwendung und die AWS-CDK-Pakete installiert. 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

Konfigurieren Sie Ihre IDE.

Wir empfehlen Visual Studio Code, aber Sie können jede IDE Ihrer Wahl verwenden, die 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 Zugangsdaten an.

Um bei der Ausführung AWS-Anmeldeinformationen auf der Shell zu habencdk 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 das AWS CDK v2.

    npm install -g aws-cdk

    Weitere Informationen finden Sie in der AWS-CDK-Dokumentation.

  2. Bootstrap 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 CloudFormation AWS-Stack bereit.

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

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

Verwenden Sie die API Gateway Gateway-Konsole, um die API zu testen. Weitere Informationen zu API-Vorgängen und Anforderungs-/Antwortnachrichten finden Sie im Abschnitt API-Nutzung der Readme-Datei im Repository. GitHub

App-Entwickler, AWS DevOps

Testen Sie die API, 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 die 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 Gateway-REST-APIs.

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

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-Services 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 API-Aufruf.

    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 es immer noch keine Logik gibt.

    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-Bibliothek Lambda Powertools for Python. Einzelheiten finden Sie auf der Website AWS Lambda Powertools for Python.

  2. Implementieren Sie die API-Logik.

    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

APG-Leitfaden

AWS-Referenzen

Tools

IDEs