Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Data Api plus Aurora Serverless v2 #6250

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 29 additions & 10 deletions python/cross_service/aurora_item_tracker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,15 @@ python -m pip install -r requirements.txt

### Aurora Serverless DB cluster and Secrets Manager secret

This example requires an Aurora Serverless DB cluster that contains a MySQL database. The
database must be configured to use credentials that are contained in a Secrets Manager
secret.
This example requires an Aurora DB cluster with the Data API feature enabled. As of March 2024,
the available choices are:
* Aurora PostgreSQL using Aurora Serverless v2 or provisioned instances in the cluster.
* Aurora MySQL or Aurora PostgreSQL using a Serverless v1 cluster.

For this example, we assume that the Aurora cluster uses a combination of Aurora PostgreSQL
and Aurora Serverless v2.

The database must be configured to use credentials that are contained in a Secrets Manager secret.

Follow the instructions in the
[README for the Aurora Serverless application](/resources/cdk/aurora_serverless_app/README.md)
Expand All @@ -71,16 +77,16 @@ CloudFormation setup script:
credentials, such as `arn:aws:secretsmanager:us-west-2:123456789012:secret:docexampleauroraappsecret8B-xI1R8EXAMPLE-hfDaaj`.
* **DATABASE** — Replace with the name of the database, such as `auroraappdb`.

*Tip:* The caret `^` is the line continuation character for a Windows command prompt.
If you run this command on another platform, replace the caret with the line continuation
*Tip:* The caret `\` is the line continuation character for a Linux or Mac command prompt.
If you run this command on another platform, replace the backslash with the line continuation
character for that platform.

```
aws rds-data execute-statement ^
--resource-arn "CLUSTER_ARN" ^
--database "DATABASE" ^
--secret-arn "SECRET_ARN" ^
--sql "create table work_items (iditem INT AUTO_INCREMENT PRIMARY KEY, description TEXT, guide VARCHAR(45), status TEXT, username VARCHAR(45), archived BOOL DEFAULT 0);"
aws rds-data execute-statement \
--resource-arn "CLUSTER_ARN" \
--database "DATABASE" \
--secret-arn "SECRET_ARN" \
--sql "create table work_items (iditem SERIAL PRIMARY KEY, description TEXT, guide VARCHAR(45), status TEXT, username VARCHAR(45), archived BOOL DEFAULT false);"
```

#### AWS Management Console
Expand All @@ -102,6 +108,19 @@ as `auroraappdb`.
This opens a SQL query console. You can run any SQL queries here that you want. Run the
following to create the `work_items` table:

For a PostgreSQL-compatible database:
```sql
create table work_items (
iditem SERIAL PRIMARY KEY,
description TEXT,
guide VARCHAR(45),
status TEXT,
username VARCHAR(45),
archived BOOL DEFAULT false
);

```
For a MySQL-compatible database:
```sql
create table work_items (
iditem INT AUTO_INCREMENT PRIMARY KEY,
Expand Down
19 changes: 17 additions & 2 deletions python/cross_service/aurora_item_tracker/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,26 @@ def add_work_item(self, work_item):
"""
Adds a work item to the database.

Note: Wrapping the INSERT statement in a WITH clause and querying the auto-generated
ID value is required by a change in the Data API for Aurora Serverless v2.
The "generatedFields" fields in the return value for DML statements are all blank.
That's why the RETURNING clause is needed to specify the columns to return, and
the entire statement is structured as a query so that the returned value can
be retrieved from the "records" result set.

This limitation might not be permanent; the DML statement might be simplified
in future.

:param work_item: The work item to add to the database. Because the ID
and archive fields are auto-generated,
you don't need to specify them when creating a new item.
:return: The generated ID of the new work item.
"""
sql = (
f"WITH t1 AS ( "
f"INSERT INTO {self._table_name} (description, guide, status, username) "
f" VALUES (:description, :guide, :status, :username)"
f" VALUES (:description, :guide, :status, :username) RETURNING iditem "
f") SELECT iditem FROM t1"
)
sql_params = [
{"name": "description", "value": {"stringValue": work_item["description"]}},
Expand All @@ -128,7 +140,10 @@ def add_work_item(self, work_item):
{"name": "username", "value": {"stringValue": work_item["username"]}},
]
results = self._run_statement(sql, sql_params=sql_params)
work_item_id = results["generatedFields"][0]["longValue"]
# Old style, for Serverless v1:
# work_item_id = results["generatedFields"][0]["longValue"]
# New style, for Serverless v2:
work_item_id = results["records"][0][0]["longValue"]
return work_item_id

def archive_work_item(self, iditem):
Expand Down
26 changes: 6 additions & 20 deletions python/cross_service/aurora_rest_lending_library/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Service (Amazon RDS) API and AWS Chalice to create a REST API backed by an
Amazon Aurora database. The web service is fully serverless and represents
a simple lending library where patrons can borrow and return books. Learn how to:

* Create and manage a serverless Amazon Aurora database cluster.
* Create and manage a serverless Amazon Aurora database. This example uses Aurora Serverless v2.
* Use AWS Secrets Manager to manage database credentials.
* Implement a data storage layer that uses Amazon RDS Data Service to move data into
and out of the database.
Expand Down Expand Up @@ -47,29 +47,15 @@ all must be run separately.

---

### 1. Database deployment

This database setup includes:
* an Amazon Aurora serverless data cluster
* an AWS Secrets Manager secret to hold the database user credentials.

This infrastructure is defined in a [setup.ts](resources/cdk/aurora_serverless_app/setup.ts) (see [README.md](resources/cdk/aurora_serverless_app/README.md)).

To execute this CDK stack, run:
```
cdk deploy
```
The output will show `ClusterArn`, `DbName`, and `SecretArn`. Make sure these values are reflected in the [config for this project](config.yml).

---

### 2. Populate database
Creates an Amazon Aurora cluster and associated Aurora Serverless v2 database instance.
Also creates an AWS Secrets Manager secret to hold the database user credentials.
It fills the database with example data pulled from the [Internet Archive's Open Library](https://openlibrary.org).

Fill the database with example data pulled from the [Internet Archive's Open Library](https://openlibrary.org).

```
python library_demo.py populate_data
```
python library_demo.py deploy_database
```

The database is now ready and can be accessed through the
[AWS Console Query Editor](https://console.aws.amazon.com/rds/home?#query-editor:)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def index():
"""Briefly describes the REST API."""
return {
"description": "A simple lending library REST API that runs entirely on "
"serverless components."
"serverless components: Aurora Serverless v2, API Gateway"
}


Expand Down Expand Up @@ -131,8 +131,15 @@ def add_patron():

:return: The ID of the added patron.
"""
patron_id = get_storage().add_patron(app.current_request.json_body)
return {"Patrons.PatronID": patron_id}
try:
patron_id = get_storage().add_patron(app.current_request.json_body)
return {"Patrons.PatronID": patron_id}
except Exception as err:
logger.exception(
f"Got exception in add_patron() inside library_api/app.py: {str(err)}"
)
logger.exception(f"Returning None instead of patron_id.")
return None


@app.route("/patrons/{patron_id}", methods=["DELETE"])
Expand All @@ -155,7 +162,15 @@ def list_borrowed_books():

:return: The list of currently borrowed books.
"""
return {"books": get_storage().get_borrowed_books()}
try:
json_doc = {"books": get_storage().get_borrowed_books()}
return json_doc
except Exception as err:
logger.exception(
f"Exception while calling get_storage().get_borrowed_books(): {str(err)}"
)
logger.exception(f"Continuing with blank list of borrowed books...")
return {"books": []}


@app.route("/lending/{book_id}/{patron_id}", methods=["PUT", "DELETE"])
Expand Down
Loading
Loading