Skip to content

Commit 5dd6150

Browse files
authored
Add simple skeleton for sample app (#2)
1 parent f453a52 commit 5dd6150

File tree

6 files changed

+219
-1
lines changed

6 files changed

+219
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
.idea/
22
.venv/
33
*.pyc
4+
.terraform*
5+
build/
6+
terraform.tfstate*

Makefile

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,23 @@ $(VENV_ACTIVATE):
1212
test -d .venv || $(VENV_BIN) .venv
1313

1414
clean:
15-
rm -rf .venv/
15+
rm -rf .venv/ build/
1616

1717
install: venv ## Install dependencies
1818
$(VENV_RUN); pip install --upgrade localstack pytest requests ruff typedb-driver
1919

20+
tf-deploy: ## Deploy the app locally via Terraform
21+
mkdir -p build/
22+
cp -r app/lambda build/
23+
docker run -it --rm --entrypoint= -v ./build/lambda:/tmp/lambda public.ecr.aws/lambda/python:3.11 pip install --target /tmp/lambda -r /tmp/lambda/requirements.txt
24+
$(VENV_RUN); tflocal init; tflocal apply -auto-approve
25+
26+
requests: ## Send a couple of test requests to create entries in the database
27+
endpoint=http://users-api.execute-api.localhost.localstack.cloud:4566/test/users; \
28+
curl -H 'content-type: application/json' -d '{"name":"Alice","age":42}' $$endpoint; \
29+
curl -H 'content-type: application/json' -d '{"name":"Bob","age":31}' $$endpoint; \
30+
curl $$endpoint
31+
2032
format: ## Run ruff to format the whole codebase
2133
$(VENV_RUN); python -m ruff format .; python -m ruff check --output-format=full --fix .
2234

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,26 @@ Sample app that demonstrates how to use TypeDB + LocalStack, to develop and test
77
* Docker
88
* LocalStack Pro (free trial available [here](https://app.localstack.cloud))
99
* `localstack` CLI
10+
* `terraform` CLI
11+
12+
## Enable the TypeDB Extension
13+
14+
To enable the TypeDB extension in LocalStack, use this command:
15+
```
16+
$ localstack extensions install "git+https://github.com/whummer/localstack-utils.git#egg=localstack-typedb&subdirectory=localstack-typedb"
17+
```
18+
19+
## Deploy and Run the App
20+
21+
To deploy the sample app to LocalStack, run the following `make` target:
22+
```
23+
$ make tf-deploy
24+
```
25+
26+
Once the app is deployed, we can run some HTTP requests against the local API Gateway, which spawns a local Lambda function, and interacts with local TypeDB:
27+
```
28+
$ make requests
29+
```
1030

1131
## License
1232

app/lambda/handler.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import json
2+
3+
from typedb.driver import TypeDB, Credentials, DriverOptions, TransactionType
4+
5+
db_name = "test-db"
6+
server_host = "typedb.localhost.localstack.cloud:4566"
7+
8+
9+
def handler(event, context):
10+
_create_database_and_schema()
11+
method = event["httpMethod"]
12+
result = {}
13+
if method == "GET":
14+
result = list_users()
15+
elif method == "POST":
16+
payload = json.loads(event["body"])
17+
result = create_user(payload)
18+
return {"statusCode": 200, "body": json.dumps(result)}
19+
20+
21+
def create_user(payload: dict):
22+
user_name = payload["name"]
23+
user_age = payload["age"]
24+
with _driver() as driver:
25+
with driver.transaction(db_name, TransactionType.WRITE) as tx:
26+
query = f"insert $p isa person, has name '{user_name}', has age {user_age};"
27+
tx.query(query).resolve()
28+
tx.commit()
29+
30+
31+
def list_users():
32+
with _driver() as driver:
33+
with driver.transaction(db_name, TransactionType.READ) as tx:
34+
result = tx.query(
35+
'match $p isa person; fetch {"name": $p.name, "age": $p.age};'
36+
).resolve()
37+
result = list(result)
38+
return result
39+
40+
41+
def _create_database_and_schema():
42+
with _driver() as driver:
43+
driver.databases.create(db_name)
44+
with driver.transaction(db_name, TransactionType.SCHEMA) as tx:
45+
tx.query("define entity person;").resolve()
46+
tx.query("define attribute name, value string; person owns name;").resolve()
47+
tx.query("define attribute age, value integer; person owns age;").resolve()
48+
tx.commit()
49+
50+
51+
def _driver():
52+
return TypeDB.driver(
53+
server_host,
54+
# TODO: make configurable
55+
Credentials("admin", "password"),
56+
DriverOptions(is_tls_enabled=False),
57+
)

app/lambda/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
typedb-driver

main.tf

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
2+
# Create deployment package for Lambda
3+
data "archive_file" "lambda_zip" {
4+
type = "zip"
5+
source_dir = "build/lambda"
6+
output_path = "build/lambda.zip"
7+
}
8+
9+
# IAM role for Lambda function
10+
resource "aws_iam_role" "lambda_role" {
11+
name = "snowflake-counter-lambda-role"
12+
13+
assume_role_policy = jsonencode({
14+
Version = "2012-10-17"
15+
Statement = [
16+
{
17+
Action = "sts:AssumeRole"
18+
Effect = "Allow"
19+
Principal = {
20+
Service = "lambda.amazonaws.com"
21+
}
22+
}
23+
]
24+
})
25+
}
26+
27+
# Lambda function
28+
resource "aws_lambda_function" "user_lambda" {
29+
filename = data.archive_file.lambda_zip.output_path
30+
function_name = "user-service"
31+
role = aws_iam_role.lambda_role.arn
32+
handler = "handler.handler"
33+
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
34+
runtime = "python3.11"
35+
timeout = 30
36+
}
37+
38+
# API Gateway
39+
resource "aws_api_gateway_rest_api" "user_api" {
40+
name = "user-service-api"
41+
description = "User Service API"
42+
tags = {
43+
_custom_id_ = "users-api"
44+
}
45+
}
46+
47+
# API Gateway resource 'create'
48+
resource "aws_api_gateway_resource" "user_resource" {
49+
rest_api_id = aws_api_gateway_rest_api.user_api.id
50+
parent_id = aws_api_gateway_rest_api.user_api.root_resource_id
51+
path_part = "users"
52+
}
53+
54+
# API Gateway method 'create'
55+
resource "aws_api_gateway_method" "create_user_method" {
56+
rest_api_id = aws_api_gateway_rest_api.user_api.id
57+
resource_id = aws_api_gateway_resource.user_resource.id
58+
http_method = "POST"
59+
authorization = "NONE"
60+
}
61+
62+
# API Gateway integration 'create'
63+
resource "aws_api_gateway_integration" "lambda_integration" {
64+
rest_api_id = aws_api_gateway_rest_api.user_api.id
65+
resource_id = aws_api_gateway_resource.user_resource.id
66+
http_method = aws_api_gateway_method.create_user_method.http_method
67+
68+
integration_http_method = "POST"
69+
type = "AWS_PROXY"
70+
uri = aws_lambda_function.user_lambda.invoke_arn
71+
}
72+
73+
# API Gateway method 'list'
74+
resource "aws_api_gateway_method" "list_users_method" {
75+
rest_api_id = aws_api_gateway_rest_api.user_api.id
76+
resource_id = aws_api_gateway_resource.user_resource.id
77+
http_method = "GET"
78+
authorization = "NONE"
79+
}
80+
81+
# API Gateway integration 'list'
82+
resource "aws_api_gateway_integration" "lambda_list_integration" {
83+
rest_api_id = aws_api_gateway_rest_api.user_api.id
84+
resource_id = aws_api_gateway_resource.user_resource.id
85+
http_method = aws_api_gateway_method.list_users_method.http_method
86+
87+
integration_http_method = "POST"
88+
type = "AWS_PROXY"
89+
uri = aws_lambda_function.user_lambda.invoke_arn
90+
}
91+
92+
# Lambda permission for API Gateway
93+
resource "aws_lambda_permission" "api_gateway_lambda" {
94+
statement_id = "AllowExecutionFromAPIGateway"
95+
action = "lambda:InvokeFunction"
96+
function_name = aws_lambda_function.user_lambda.function_name
97+
principal = "apigateway.amazonaws.com"
98+
source_arn = "${aws_api_gateway_rest_api.user_api.execution_arn}/*/*"
99+
}
100+
101+
# API Gateway deployment
102+
resource "aws_api_gateway_deployment" "user_api_deployment" {
103+
depends_on = [
104+
aws_api_gateway_integration.lambda_integration,
105+
]
106+
107+
rest_api_id = aws_api_gateway_rest_api.user_api.id
108+
109+
lifecycle {
110+
create_before_destroy = true
111+
}
112+
}
113+
114+
# Stage
115+
resource "aws_api_gateway_stage" "stage" {
116+
deployment_id = aws_api_gateway_deployment.user_api_deployment.id
117+
rest_api_id = aws_api_gateway_rest_api.user_api.id
118+
stage_name = "test"
119+
}
120+
121+
# Output the API Gateway ID
122+
output "api_endpoint" {
123+
value = "http://${aws_api_gateway_rest_api.user_api.id}.execute-api.localhost.localstack.cloud:4566/test/users"
124+
description = "API Gateway ID"
125+
}

0 commit comments

Comments
 (0)