re:Invent 2023 - The Pragmatic Serverless Python Developer

HeitorLessa1 30 views 102 slides Jul 22, 2024
Slide 1
Slide 1 of 102
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32
Slide 33
33
Slide 34
34
Slide 35
35
Slide 36
36
Slide 37
37
Slide 38
38
Slide 39
39
Slide 40
40
Slide 41
41
Slide 42
42
Slide 43
43
Slide 44
44
Slide 45
45
Slide 46
46
Slide 47
47
Slide 48
48
Slide 49
49
Slide 50
50
Slide 51
51
Slide 52
52
Slide 53
53
Slide 54
54
Slide 55
55
Slide 56
56
Slide 57
57
Slide 58
58
Slide 59
59
Slide 60
60
Slide 61
61
Slide 62
62
Slide 63
63
Slide 64
64
Slide 65
65
Slide 66
66
Slide 67
67
Slide 68
68
Slide 69
69
Slide 70
70
Slide 71
71
Slide 72
72
Slide 73
73
Slide 74
74
Slide 75
75
Slide 76
76
Slide 77
77
Slide 78
78
Slide 79
79
Slide 80
80
Slide 81
81
Slide 82
82
Slide 83
83
Slide 84
84
Slide 85
85
Slide 86
86
Slide 87
87
Slide 88
88
Slide 89
89
Slide 90
90
Slide 91
91
Slide 92
92
Slide 93
93
Slide 94
94
Slide 95
95
Slide 96
96
Slide 97
97
Slide 98
98
Slide 99
99
Slide 100
100
Slide 101
101
Slide 102
102

About This Presentation

Are you developing AWS Lambda functions with Python? Always looking for tools to make you more productive? What if you could hear directly from practitioners? This session covers an opinionated approach to Python project setup, testing, profiling, deployments, and operations. Learn about many open s...


Slide Content

The pragmatic serverless Python developer Heitor Lessa OPN305-R1 (he/him) Chief Architect, Powertools for AWS AWS Ran Isenberg (he/him) Principal Software Architect CyberArk

Pragmatism Use case Product API Stream processor Wrap - up Readiness

Pragmatism: Analysis paralysis Infra as code framework decisions & trade-offs situational awareness is key Project structure Domain-driven design? Micro or mono functions? Open source tools? Sync or async? Asynchronous testing? App integrations Observability? Leading practices? Metrics? Alarms? Documentation?

Pragmatism: Open source tools mkdocs-material pytest Pydantic Powertools for AWS Lambda p re-commit YAPF Tuna Radon Xenon AWS CDK mkdocstrings isort flake8 poetry cdk-monitoring-constructs codecov actionlint gitleaks GitPython typing-extensions ` mypy-boto3 cdk-nag cachetools aws-lambda-env-modeler mypy pytest-socket markdownlint dependabot Semantic PR

Pragmatism: Leading practices (CI) Project setup pre-commit check Merge conflicts Valid Python code JSON/YAML/TOML Secrets linting Code linting pre-pull request +pre-commit checks Test (unit/contract) AppSec baseline Complexity baseline Docs linting Pull request checks Governance checks Test (integ/E2E) SAST +pre-PR checks Dependency CVE code checkout virtual env dependencies pre-commit hooks

Pragmatism Use case Product API Stream processor Wrap - up Readiness

Use case: Simple yet practical Product API Product database Product events

Use case: Product API Product database Product events Route handlers CREATE Product LIST Products GET Product DELETE Product Amazon API Gateway

Use case: Product database Route handlers CREATE Product LIST Products GET Product DELETE Product Amazon DynamoDB Amazon API Gateway Product events

Use case: Product events AWS Lambda stream poller AWS Lambda Stream processor Amazon EventBridge Route handlers CREATE Product LIST Products GET Product DELETE Product Amazon DynamoDB Amazon API Gateway

Use case: Synchronous path AWS Lambda stream poller AWS Lambda Stream processor Amazon EventBridge Route handlers CREATE Product LIST Products GET Product DELETE Product Amazon DynamoDB Amazon API Gateway Synchronous

Use case: Asynchronous path AWS Lambda stream poller AWS Lambda Stream processor Amazon EventBridge Route handlers CREATE Product LIST Products GET Product DELETE Product Amazon DynamoDB Amazon API Gateway Synchronous Asynchronous

Pragmatism Use case Product API Stream processor Wrap - up Readiness

Introduction Principal Software Architect AWS Serverless Hero Owner & Blogger @RanTheBuilder.Cloud

Product API: Practices Practices Project structure, infra as code Lambda handler practices Business domain Integration logic, AWS services, and more Unit, integration, end-to-end testing Amazon DynamoDB Route handlers GET /products GET /{product} PUT /{product} DELETE /{product} Amazon API Gateway

Synergy of elements DevOps culture DEV -> Production Faster development Project structure Project: Overview

Project repository structure reflects elements: Business domain: Lambda handlers code, business logic, and integrations code Tests: unit, integration, end to end Production readiness: spread across all folders pyproject.toml: one file to rule them all IaC: CDK, AWS SAM, Terraform, and more Project: Folder structure

We chose AWS CDK as IaC framework One CDK app and one stack Domain-driven approach: One construct for product REST API One constructs builds: API GW Lambda functions DynamoDB table All roles Project: Infrastructure as code

Start with one Lambda function All business domain code and logic What is the ”best” way to write a Lambda function? Amazon DynamoDB Route handlers PUT /{product} Amazon API Gateway Handler: Overview

Start with a handler that does it all Single file Gets input Handler: Event source parsing

Start with a handler that does it all Single file Gets input Domain logic/DB access Handler: Integration

Start with a handler that does it all Single file Gets input Domain logic/DB access Returns response Handler: Response

Start with a handler that does it all Single file Gets input Domain logic/DB access Returns response Context matters Cron job handler vs. REST API with logic Handler: One handler to rule them all?

As the service expands: Code duplication Testing becomes harder Code readability issues – 300+ lines This does not scale! We need a better way to develop complex services Amazon DynamoDB Route handlers GET /products GET /{product} PUT /{product} DELETE /{product} Amazon API Gateway Handler: Will my service evolve?

Business logic only May be shared by N handlers Isolated tests Handler: Architectural layers Handler No domain code Concise responsibilities Domain

Handler: Responsibilities Configuration Env. vars Validation Serialize output Call domain

access Amazon DynamoDB Amazon API Gateway Handler Domain invoke handle output serialize Handler: High-level event flow

Zero domain code Short and concise Clear responsibilities Handler: Event source parsing (after)

Zero domain code Short and concise Clear responsibilities Handler: Integration (after)

Zero domain code Short and concise Clear responsibilities Handler: Response (after)

Zero domain code Short and concise Clear responsibilities Handler: Refactored

Functional or OOP Interface: Create product Get product List products Delete product Domain: Product

Functional or OOP Interface: Create product Get product List products Delete product Access DynamoDB Return output Domain: Integration

Integration practice relates to: Code that accesses DynamoDB And in general: Code that accesses API Integration with AWS resources Integration with external APIs Amazon DynamoDB Route handlers GET /products GET /{product} PUT /{product} DELETE /{product} Amazon API Gateway Integration: Overview

Domain code contains integration code When can it backfire? Integration: Should domain integrate directly?

DB requirements change and evolve New queries are too slow What if we replace DynamoDB? Harder refactors No isolated testing Can we do better? Route handlers GET /products GET /{product} PUT /{product} DELETE /{product} Amazon API Gateway Integration: Testing and refactoring challenges Amazon Aurora Amazon DynamoDB

Adapter pattern Contains API/DB code Business logic only May be shared by N handlers Isolated tests Integration: A new architecture layer Handler No domain code Concise responsibilities Domain Integration

invoke handle serialize output Amazon DynamoDB Amazon API Gateway Handler Domain Integration: High-level overview Integration call access output

Integration: Agreeing on an interface Abstract interface Interface includes: Create product List products Get product Delete product

Integration: Implementation example Implements adapter pattern The only class that has DynamoDB code Isolated tests Aurora handler inherits interface

Integration: Refactoring domain Getter for concrete implementation Domain is not aware of the underlying DB Easy to switch to a different database Output conversion

DevX Never leave IDE Real cloud resources Local debug Amazon DynamoDB Route handlers GET /products GET /{product} PUT /{product} DELETE /{product} Amazon API Gateway Testing: Overview

No deployment Run Locally Testing: Pyramid

call Test runner (pytest) Isolated unit Assertion Test data verify prepare Testing: Unit test overview

Test schema validations (Pydantic) Test isolated integration classes No deployment required Fast & repeatable Testing: Model validation

Require s deployment No deployment Run l ocally & on AWS Run l ocally Testing: Integration tests

call Test runner (pytest) Lambda handler Assertion Test data verify prepare resources Testing: Integration test overview

Build repeatable and consistent tests Event details: Print real events in CloudWatch Logs Powertools for AWS Lambda test JSON events AWS service documentation Testing: Event generation

Test-driven development (TDD) Breakpoints in IDE Work against cloud resources Testing: Local code, cloud resources

Test-driven development (TDD) Breakpoints in IDE Work against cloud resources Assert side effects and response Testing: Asserting responses

Test-driven development (TDD) Breakpoints in IDE Work against cloud resources Assert side effects and response Testing: Asserting side effects

Test-driven development (TDD) Breakpoints in IDE Work against cloud resources Assert side effects and response Testing: Integration test complete

Simulate failures Mock DynamoDB exceptions Assert handler response Why not write as a unit test ? Testing: Injecting SDK failures

Require deployment Require deployment No deployment Run on AWS Run l ocally & on AWS Run l ocally Testing: End-to-end (E2E) tests

call Test runner (pytest) Product API Assertion Test data verify prepare Endpoint Lambda function invoke Testing: E2E test overview

Simulate real user usage Runs 100% on AWS Testing: Black box testing

Simulate real user usage Runs 100% on AWS Assert response Testing: Asserting product creation

Simulate bad input API security tests: Invalid authorization Invalid authentication Testing: Unhappy paths

Pragmatism Use case Product API Stream processor Wrap - up Readiness

Amazon DynamoDB Stream processor: Intro Amazon EventBridge AWS Lambda Stream processor Practices Project scaffolding, infra as code Lambda handler practices Business domain Integration logic, AWS services, and more Unit, integration, end-to-end testing

Practices Same structure as CRUD API Project: Folder structure

Amazon DynamoDB Amazon EventBridge AWS Lambda Stream processor Handler: Focus area

Practices Extend your handler with defaults Handler: Extend the signature

Practices Extend your handler with defaults Build domain input from event Handler: Prepare and validate domain input

Practices Extend your handler with defaults Build domain input from event Inject integrations Handler: Create and inject event emitter

Practices Dependency injection to the rescue Domain: Use dependency and issue receipt

Amazon EventBridge Integration: High-level overview Integration layer Domain

Amazon EventBridge Integration: High-level overview Event Handler Domain

Amazon EventBridge Integration: High-level overview Event Handler Domain Model to Event Standardize Event Inject metadata

Amazon EventBridge Integration: High-level overview Event Handler Domain Model to Event Event Provider Standardize Event Inject metadata send

Amazon EventBridge Integration: High-level overview Event Handler Domain Model to Event Event Provider Standardize Event Inject metadata send PutEvents

Amazon EventBridge Integration: High-level overview Event Handler Domain Model to Event Event Provider Standardize Event Inject metadata Build request Error Handling Issue Receipt send PutEvents

Integration: Model to standard Event

Integration: Model to standard Event Model  Event

Integration: Model to provider request Model  Event  EventBridge

Integration Domain Handler AWS Lambda Amazon EventBridge Testing: Isolated tests, thanks to layering Amazon DynamoDB

Testing: Quick recap on our handler

Integration Domain Amazon EventBridge Amazon DynamoDB Testing: Unit testing Lambda handler AWS Lambda Handler Event source Domain input EventHandler

Testing: Preventing side effects Practices Fail tests for unexpected connections

Testing: FakeEventHandler Practices Fail tests for unexpected connections Use in-memory Fakes

Testing: Unit testing benefits Practices Fail tests for unexpected connections Use in-memory Fakes Test in isolation, and as a whole unit

Amazon DynamoDB Integration Amazon EventBridge Domain Testing: Unit testing domain AWS Lambda Handler Event source Domain input EventHandler Business rule Use integ.

Testing: Unit testing domain logic Practices Fail tests for unexpected connections Use in-memory Fakes Test in isolation , and as a whole unit

Amazon DynamoDB Amazon EventBridge Integration Domain Testing: Unit testing integration AWS Lambda Handler Event source Domain input EventHandler Business rule Use integ.

Testing: Contract testing Practices Fail tests for unexpected connections Use in-memory Fakes Test model -> event -> provider request

Practices Fail tests for unexpected connections Use in-memory Fakes Test model  event  provider request Testing: Contract testing (event)

Testing: Contract testing (provider) Practices Fail tests for unexpected connections Use in-memory Fakes Test model  event  provider request

Testing: Asserting contract Practices Fail tests for unexpected connections Use in-memory Fakes Test model  event  provider request AWS SDK stubber for deeper validation

Amazon DynamoDB Amazon EventBridge Integration Domain Testing: Asserting asynchronous behavior AWS Lambda Handler Event source Domain input EventHandler Business rule Use integ. Init provider Build event Send event

Amazon EventBridge Testing: Listening to events AWS Lambda Amazon DynamoDB

Amazon EventBridge Testing: Creating new side effects AWS Lambda AWS Step Functions Amazon DynamoDB Amazon DynamoDB Intercept Event Testing Construct

Amazon EventBridge Testing: Querying intercepted events AWS Lambda AWS Step Functions Amazon DynamoDB Amazon DynamoDB Intercept Event Testing Construct pk sk data metadata receipt_id test_eventbridge_provider_send b30a7 ..# SAMPLE_NOTIFICATION #2023-10-08T20:17:02… {…} {…} b30a7 …

Testing: Using test name as event source Practices Fail tests for unexpected connections Use in-memory Fakes Test model  event  provider request Validate input with AWS SDK stubber Make it easier to trace test events

Testing: Asserting intercepted events Practices Fail tests for unexpected connections Use in-memory Fakes Test model  event  provider request Validate input with AWS SDK stubber Make it easier to trace test events Consider eventual consistency

Pragmatism Use case Product API Stream processor Wrap - up Readiness

Powertools for AWS Lambda: T oolkit Batch processing REST/GraphQL API Input/output validation Config management Secrets handling Idempotency Observability BYO middleware Self-documented schemas Feature flags Data extraction Caching best practices, for everyone Streaming *feature set may vary across languages Python | TypeScript | Java | .NET

Tuna: Visualizing import time (cold start)

Py-spy: Visualizing most freq. code path

Pyinstrument: Visualizing select code areas

Pragmatism Use case Product API Stream processor Wrap - up Readiness

Recap: Open source repository o pinionated reference and fully documented

Summary: Takeaways PROJECT HANDLER DOMAIN INTEGRATION TESTING Resources and template
Tags