re:Invent 2023 - The Pragmatic Serverless Python Developer
HeitorLessa1
30 views
102 slides
Jul 22, 2024
Slide 1 of 102
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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...
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 source tools, including Powertools for AWS Lambda—a toolkit that can help you implement serverless best practices and increase developer velocity. Join to discover tools and patterns for effective serverless development with Python. To maximize your learning experience, the session includes a sample application that implements what’s described.
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
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
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
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
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