Skip to content

Commit 1603800

Browse files
authored
feature: add DAL (#586)
1 parent e43c2c6 commit 1603800

File tree

12 files changed

+248
-253
lines changed

12 files changed

+248
-253
lines changed

cdk/my_service/service_stack.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22
from pathlib import Path
33

4-
from aws_cdk import Stack
4+
from aws_cdk import Stack, Tags
55
from constructs import Construct
66
from git import Repo
77
from my_service.api_construct import ApiConstruct # type: ignore
@@ -27,6 +27,7 @@ class ServiceStack(Stack):
2727

2828
def __init__(self, scope: Construct, id: str, **kwargs) -> None:
2929
super().__init__(scope, id, **kwargs)
30+
Tags.of(self).add('service_name', 'Order')
3031

3132
# This construct should be deployed in a different repo and have its own pipeline so updates can be decoupled
3233
# from running the service pipeline and without redeploying the service lambdas. For the sake of this template

poetry.lock

+173-204
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

service/dal/__init__.py

Whitespace-only changes.

service/dal/db_handler.py

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import uuid
2+
3+
import boto3
4+
from botocore.exceptions import ClientError
5+
from cachetools import TTLCache, cached
6+
from mypy_boto3_dynamodb import DynamoDBServiceResource
7+
from mypy_boto3_dynamodb.service_resource import Table
8+
from pydantic import ValidationError
9+
10+
from service.dal.schemas.db import OrderEntry
11+
from service.handlers.utils.observability import logger, tracer
12+
from service.schemas.exceptions import InternalServerException
13+
14+
15+
# cache dynamodb connection data for no longer than 5 minutes
16+
@cached(cache=TTLCache(maxsize=1, ttl=300))
17+
def _get_db_handler(table_name: str) -> Table:
18+
dynamodb: DynamoDBServiceResource = boto3.resource('dynamodb')
19+
return dynamodb.Table(table_name)
20+
21+
22+
@tracer.capture_method(capture_response=False)
23+
def create_order_in_db(table_name: str, customer_name: str, order_item_count: int) -> OrderEntry:
24+
order_id = str(uuid.uuid4())
25+
logger.info('trying to save order', extra={'order_id': order_id})
26+
try:
27+
entry = OrderEntry(order_id=order_id, customer_name=customer_name, order_item_count=order_item_count)
28+
logger.info('opening connection to dynamodb table', extra={'table_name': table_name})
29+
table: Table = _get_db_handler(table_name)
30+
table.put_item(Item=entry.dict())
31+
except (ClientError, ValidationError) as exc:
32+
error_msg = 'failed to create order'
33+
logger.exception(error_msg, extra={'exception': str(exc), 'customer_name': customer_name})
34+
raise InternalServerException(error_msg) from exc
35+
36+
logger.info('finished create order', extra={'order_id': order_id, 'order_item_count': order_item_count, 'customer_name': customer_name})
37+
return entry

service/dal/schemas/__init__.py

Whitespace-only changes.

service/dal/schemas/db.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from typing import Annotated
2+
from uuid import UUID
3+
4+
from pydantic import BaseModel, Field, PositiveInt, validator
5+
6+
7+
class OrderEntry(BaseModel):
8+
order_item_count: PositiveInt
9+
customer_name: Annotated[str, Field(min_length=1, max_length=20)]
10+
order_id: str # primary key
11+
12+
@validator('order_id')
13+
def valid_uuid(cls, v):
14+
UUID(v, version=4)
15+
return v

service/handlers/create_order.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from service.handlers.utils.observability import logger, metrics, tracer
1717
from service.logic.handle_create_request import handle_create_request
1818
from service.schemas.exceptions import InternalServerException
19-
from service.schemas.output import Output
19+
from service.schemas.output import CreateOrderOutput
2020

2121

2222
@init_environment_variables(model=MyHandlerEnvVars)
@@ -45,7 +45,7 @@ def create_order(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Any
4545

4646
metrics.add_metric(name='ValidEvents', unit=MetricUnit.Count, value=1)
4747
try:
48-
response: Output = handle_create_request(
48+
response: CreateOrderOutput = handle_create_request(
4949
customer_name=input.customer_name,
5050
order_item_count=input.order_item_count,
5151
table_name=env_vars.TABLE_NAME,
+7-34
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,13 @@
1-
import uuid
2-
3-
import boto3
4-
from botocore.exceptions import ClientError
5-
from cachetools import TTLCache, cached
6-
from mypy_boto3_dynamodb import DynamoDBServiceResource
7-
from mypy_boto3_dynamodb.service_resource import Table
8-
1+
from service.dal.db_handler import create_order_in_db
2+
from service.dal.schemas.db import OrderEntry
93
from service.handlers.schemas.dynamic_configuration import FeatureFlagsNames
104
from service.handlers.utils.dynamic_configuration import get_dynamic_configuration_store
115
from service.handlers.utils.observability import logger, tracer
12-
from service.schemas.exceptions import InternalServerException
13-
from service.schemas.output import Output
6+
from service.schemas.output import CreateOrderOutput
147

158

169
@tracer.capture_method(capture_response=False)
17-
def handle_create_request(customer_name: str, order_item_count: int, table_name: str) -> Output:
10+
def handle_create_request(customer_name: str, order_item_count: int, table_name: str) -> CreateOrderOutput:
1811
logger.info('starting to handle create request', extra={'order_item_count': order_item_count, 'customer_name': customer_name})
1912

2013
# feature flags example
@@ -32,26 +25,6 @@ def handle_create_request(customer_name: str, order_item_count: int, table_name:
3225
)
3326
logger.debug('premium feature flag value', extra={'premium': premium})
3427

35-
return _create_order_in_db(table_name, customer_name, order_item_count)
36-
37-
38-
# cache dynamodb connection data for no longer than 5 minutes
39-
@cached(cache=TTLCache(maxsize=1, ttl=300))
40-
def _get_db_handler(table_name: str) -> Table:
41-
dynamodb: DynamoDBServiceResource = boto3.resource('dynamodb')
42-
logger.info('opening connection to dynamodb table', extra={'table_name': table_name})
43-
return dynamodb.Table(table_name)
44-
45-
46-
def _create_order_in_db(table_name: str, customer_name: str, order_item_count: int):
47-
order_id = str(uuid.uuid4())
48-
try:
49-
table: Table = _get_db_handler(table_name)
50-
table.put_item(Item={'order_id': order_id, 'customer_name': customer_name, 'count': order_item_count})
51-
except ClientError as exc:
52-
error_msg = 'failed to create order'
53-
logger.exception(error_msg, extra={'exception': str(exc), 'customer_name': customer_name})
54-
raise InternalServerException(error_msg) from exc
55-
56-
logger.info('finished create order', extra={'order_id': order_id, 'order_item_count': order_item_count, 'customer_name': customer_name})
57-
return Output(customer_name=customer_name, order_item_count=order_item_count, order_id=order_id)
28+
order: OrderEntry = create_order_in_db(table_name, customer_name, order_item_count)
29+
# convert from db entry to output, they won't always be the same
30+
return CreateOrderOutput(customer_name=order.customer_name, order_item_count=order.order_item_count, order_id=order.order_id)

service/schemas/output.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from pydantic import BaseModel, Field, PositiveInt, validator
55

66

7-
class Output(BaseModel):
7+
class CreateOrderOutput(BaseModel):
88
order_item_count: PositiveInt
99
customer_name: Annotated[str, Field(min_length=1, max_length=20)]
1010
order_id: str

tests/integration/test_create_order.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,15 @@ def test_handler_200_ok(mocker, table_name: str):
6161
response = dynamodb_table.get_item(Key={'order_id': body_dict['order_id']})
6262
assert 'Item' in response # order was found
6363
assert response['Item']['customer_name'] == customer_name
64-
assert response['Item']['count'] == order_item_count
64+
assert response['Item']['order_item_count'] == order_item_count
6565

6666

6767
def test_internal_server_error(mocker):
6868

6969
def db_mock_function(table_name: str):
7070
raise ClientError(error_response={}, operation_name='put_item')
7171

72-
db_mock = mocker.patch('service.logic.handle_create_request._get_db_handler')
72+
db_mock = mocker.patch('service.dal.db_handler._get_db_handler')
7373
db_mock.side_effect = db_mock_function
7474
body = Input(customer_name='RanTheBuilder', order_item_count=5)
7575
response = create_order(generate_api_gw_event(body.dict()), generate_context())

tests/unit/test_create_order_output.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,30 @@
33
import pytest
44
from aws_lambda_powertools.utilities.parser import ValidationError
55

6-
from service.schemas.output import Output
6+
from service.schemas.output import CreateOrderOutput
77

88
order_id = str(uuid.uuid4())
99

1010

1111
def test_invalid_items_type():
1212
with pytest.raises(ValidationError):
13-
Output(order_id=order_id, customer_name='3333', order_item_count='a')
13+
CreateOrderOutput(order_id=order_id, customer_name='3333', order_item_count='a')
1414

1515

1616
def test_invalid_items_negative():
1717
with pytest.raises(ValidationError):
18-
Output(order_id=order_id, customer_name='3333', order_item_count=-1)
18+
CreateOrderOutput(order_id=order_id, customer_name='3333', order_item_count=-1)
1919

2020

2121
def test_invalid_items_zero():
2222
with pytest.raises(ValidationError):
23-
Output(order_id=order_id, customer_name='3333', order_item_count=0)
23+
CreateOrderOutput(order_id=order_id, customer_name='3333', order_item_count=0)
2424

2525

2626
def test_invalid_order_id():
2727
with pytest.raises(ValidationError):
28-
Output(order_id='2', customer_name='3333', order_item_count=2)
28+
CreateOrderOutput(order_id='2', customer_name='3333', order_item_count=2)
2929

3030

3131
def test_valid_output():
32-
Output(customer_name='222', order_item_count=4, order_id=order_id)
32+
CreateOrderOutput(customer_name='222', order_item_count=4, order_id=order_id)

tests/unit/test_db_handler.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22
from botocore.exceptions import ClientError
33

4-
from service.logic.handle_create_request import _create_order_in_db
4+
from service.dal.db_handler import create_order_in_db
55
from service.schemas.exceptions import InternalServerException
66

77

@@ -10,8 +10,8 @@ def test_raise_exception(mocker):
1010
def db_mock_function(table_name: str):
1111
raise ClientError(error_response={}, operation_name='put_item')
1212

13-
db_mock = mocker.patch('service.logic.handle_create_request._get_db_handler')
13+
db_mock = mocker.patch('service.dal.db_handler._get_db_handler')
1414
db_mock.side_effect = db_mock_function
1515
with pytest.raises(InternalServerException):
16-
_create_order_in_db(table_name='table', customer_name='customer', order_item_count=5)
16+
create_order_in_db(table_name='table', customer_name='customer', order_item_count=5)
1717
db_mock.assert_called

0 commit comments

Comments
 (0)