Skip to content

Commit f8e35eb

Browse files
authored
Merge pull request #1 from oracle-devrel/api-gateway-function-nosql-table
Added sample for apigateway function async processing with a queue an…
2 parents 9758476 + f22091d commit f8e35eb

26 files changed

Lines changed: 1127 additions & 1 deletion

File tree

.gitignore

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,27 @@ Temporary Items
3030
.key
3131
.crt
3232
.csr
33-
.pem
33+
.pem
34+
35+
# Go build
36+
**/bin/
37+
**/vendor/
38+
39+
# Editor
40+
*.swp
41+
*.swo
42+
.idea/
43+
.vscode/
44+
.DS_Store
45+
46+
# Debug
47+
*.log
48+
49+
# Local Terraform variable overrides
50+
**/.terraform.lock.hcl
51+
**/terraform_local.tfvars
52+
**/.terraform*
53+
54+
*.tfstate
55+
*.tfstate.backup
56+
**/__pycache__/
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# API Gateway Function Queue Async
2+
3+
This project demonstrates an asynchronous messaging pattern using Oracle Cloud Infrastructure (OCI) services:
4+
5+
- **API Gateway**: Receives HTTP requests and routes them to functions
6+
- **OCI Functions**: Serverless functions for processing requests
7+
- **OCI Queue**: Asynchronous message queue for decoupling services
8+
9+
## Architecture
10+
11+
The application follows an asynchronous messaging pattern:
12+
13+
1. HTTP request arrives at API Gateway → `/order` endpoint
14+
2. API Gateway routes to **place-order OCI Function**
15+
3. Function validates and enqueues order to **OCI Queue**
16+
4. Function returns immediately (async response)
17+
5. **process-order Container Instance** continuously polls the queue
18+
6. Container Instance processes messages and inserts into **NoSQL Database**
19+
20+
```
21+
HTTP Request → API Gateway → place-order Function → Queue → process-order Container Instance → NoSQL Table
22+
23+
(Async Processing)
24+
```
25+
26+
## Components
27+
28+
### API Gateway
29+
- Receives HTTP requests at `/order` endpoint
30+
- Routes requests to the `place-order` function
31+
32+
### Functions
33+
- **place-order**: OCI Function that receives order requests via API Gateway and enqueues them to the queue
34+
- **process-order**: Container Instance (not an OCI Function) that continuously polls the OCI Queue and processes orders by inserting them into the NoSQL table
35+
36+
### Queue
37+
- **OrderQueue**: Asynchronous message queue for order processing
38+
- Provides reliable message delivery and processing
39+
40+
## Directory Structure
41+
42+
```
43+
api-gateway-function-queue-async/
44+
├── README.md # Project documentation
45+
├── functions/ # Application components
46+
│ ├── place-order/ # OCI Function for receiving orders
47+
│ └── process-order/ # Container Instance for queue polling
48+
└── terraform/ # Infrastructure as Code
49+
└── modules/ # Reusable Terraform modules
50+
├── apigateway/ # API Gateway module
51+
├── container_repository/ # OCI Container Registry module
52+
├── functions/ # OCI Functions module
53+
├── queue/ # OCI Queue module
54+
└── vcn/ # Virtual Cloud Network module
55+
```
56+
57+
## Prerequisites
58+
59+
Before you begin, ensure you have the following:
60+
61+
- **OCI Account**: Active Oracle Cloud Infrastructure account with appropriate compartment access
62+
- **Terraform**: Version 1.10.0 or higher installed on your local machine
63+
- **Docker**: Docker Desktop or equivalent for building and pushing container images
64+
- **OCI CLI**: Configured with your OCI credentials for authentication
65+
- **Network**: A public subnet in your VPC for the API Gateway and Container Instance
66+
- **IAM Permissions**: Your OCI user must have permissions to:
67+
- Create API Gateways
68+
- Create and manage OCI Functions
69+
- Create and manage OCI Queues
70+
- Create NoSQL tables
71+
- Create Container Instances
72+
- Create Container Registries
73+
- Manage IAM policies and dynamic groups
74+
- **Authentication**: OCI auth token for Docker Container Registry access
75+
76+
## Getting Started
77+
78+
1. **Configure OCI Variables**:
79+
80+
Update `terraform/terraform.tfvars` with your OCI configuration:
81+
```hcl
82+
region = "us-ashburn-1" # Your OCI region
83+
compartment_ocid = "ocid1.compartment.oc1..." # Your compartment OCID
84+
subnet_ocid = "ocid1.subnet.oc1..." # Your public subnet OCID
85+
tenancy_ocid = "ocid1.tenancy.oc1..." # Your tenancy OCID
86+
queue_name = "OrderQueue"
87+
post_order_container_repository_name = "queue_async_repo"
88+
process_order_container_repository_name = "queue_async_process_repo"
89+
application_display_name = "queue_async_app"
90+
nosql_table_name = "orders"
91+
```
92+
93+
2. **Deploy Infrastructure**:
94+
```bash
95+
cd terraform
96+
terraform init
97+
terraform apply -var-file=terraform.tfvars
98+
```
99+
100+
**Note**: After successful deployment, note the OCIR repository addresses from the Terraform outputs:
101+
- `post_order_repository_path`: Container registry path for place-order function
102+
- `process_order_repository_path`: Container registry path for process-order function
103+
104+
These will be used in the next step for building and pushing Docker images.
105+
106+
3. **Build and Deploy Place-Order Image**:
107+
```bash
108+
cd functions/place-order
109+
docker build -t place-order:1 .
110+
docker tag place-order:1 <post_order_repository_path>:1
111+
docker push <post_order_repository_path>:1
112+
```
113+
114+
4. **Build and Deploy Process-Order Image**:
115+
```bash
116+
cd ../process-order
117+
docker build -t process-order:1 .
118+
docker tag process-order:1 <process_order_repository_path>:1
119+
docker push <process_order_repository_path>:1
120+
```
121+
122+
5. **Update Terraform Variables with Image Tags**:
123+
124+
Update `terraform/terraform.tfvars` to add the function image URIs you just pushed:
125+
```hcl
126+
# Add these to your existing terraform.tfvars
127+
functions = {
128+
"place-order" = {
129+
source_image = "<post_order_repository_path>:1"
130+
path = "place-order"
131+
config = {}
132+
}
133+
}
134+
queue_poller_image = "<process_order_repository_path>:1"
135+
```
136+
137+
6. **Re-apply Terraform Configuration**:
138+
139+
Apply the updated configuration with the new image tags:
140+
```bash
141+
cd terraform
142+
terraform apply -var-file=terraform.tfvars
143+
```
144+
145+
This will deploy the OCI Function and Container Instance with the container images you just built and pushed.
146+
147+
7. **Get API Gateway Endpoint**:
148+
149+
After the Terraform apply completes, retrieve the API Gateway endpoint URL:
150+
```bash
151+
terraform output api_gateway_endpoint
152+
```
153+
154+
Save this URL for testing the API in the next step.
155+
156+
8. **Test the API**:
157+
```bash
158+
curl -X POST https://<api_gateway_endpoint>/order \
159+
-H "Content-Type: application/json" \
160+
-d '{
161+
"data": {
162+
"order_id": "ORD-001",
163+
"customer_id": "CUST123",
164+
"amount": 99.99
165+
}
166+
}'
167+
```
168+
169+
9. **Verify Records in NoSQL Table**:
170+
171+
After submitting a few orders, verify that the records have been processed and inserted into the NoSQL table:
172+
```bash
173+
# Query the NoSQL table for all orders
174+
oci nosql query execute --statement "SELECT * FROM order_info" --compartment-id <compartment_ocid> --region <region>
175+
```
176+
177+
You should see the orders you submitted through the API in the results.
178+
179+
## Benefits
180+
181+
- **Scalability**: Queue decouples request handling from processing
182+
- **Reliability**: Messages are persisted and can be retried
183+
- **Performance**: API responds immediately while processing happens asynchronously
184+
- **Monitoring**: Queue provides visibility into message processing
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
FROM fnproject/python:3.11-dev as build-stage
2+
WORKDIR /function
3+
ADD requirements.txt /function/
4+
5+
RUN pip3 install --target /python/ --no-cache --no-cache-dir -r requirements.txt &&\
6+
rm -fr ~/.cache/pip /tmp* requirements.txt func.yaml Dockerfile .venv &&\
7+
chmod -R o+r /python
8+
ADD . /function/
9+
RUN rm -fr /function/.pip_cache
10+
FROM fnproject/python:3.11
11+
WORKDIR /function
12+
COPY --from=build-stage /python /python
13+
COPY --from=build-stage /function /function
14+
RUN chmod -R o+r /function
15+
ENV PYTHONPATH=/function:/python
16+
ENTRYPOINT ["/python/bin/fdk", "/function/func.py", "handler"]
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import io
2+
import json
3+
import oci
4+
import logging
5+
import base64
6+
from io import BytesIO
7+
8+
from fdk import response
9+
10+
# Configure logging
11+
logging.basicConfig(level=logging.INFO)
12+
logger = logging.getLogger(__name__)
13+
14+
15+
def handler(ctx, data: str = None):
16+
"""
17+
OCI Function handler to post a message to an OCI Queue using Resource Principal.
18+
19+
Args:
20+
ctx: Function context
21+
data: Input data (JSON string)
22+
23+
Returns:
24+
JSON response indicating success or failure
25+
"""
26+
try:
27+
# Initialize Resource Principal signer
28+
signer = oci.auth.signers.get_resource_principals_signer()
29+
30+
# Get region from function configuration or default to us-ashburn-1
31+
region = ctx.Config().get("OCI_REGION", "us-ashburn-1")
32+
33+
# Set endpoint explicitly
34+
endpoint = f"https://cell-1.queue.messaging.{region}.oci.oraclecloud.com"
35+
36+
# Initialize Queue client with the correct endpoint
37+
config = {
38+
"region": ctx.Config().get("QUEUE_OCID")
39+
}
40+
41+
# Initialize Queue client
42+
queue_client = oci.queue.QueueClient(config=config, signer=signer, service_endpoint= endpoint)
43+
44+
# Get queue OCID from function configuration
45+
queue_ocid = ctx.Config().get("QUEUE_OCID")
46+
if not queue_ocid:
47+
raise ValueError("QUEUE_OCID not found in function configuration")
48+
49+
# Handle input data (string or BytesIO)
50+
if data is None:
51+
raise ValueError("No payload provided in POST body")
52+
53+
# If data is BytesIO (e.g., from fn invoke), read and decode it
54+
if isinstance(data, BytesIO):
55+
data = data.read().decode('utf-8')
56+
57+
# Parse the input as JSON
58+
payload = json.loads(data)
59+
60+
# Validate payload structure
61+
if not isinstance(payload, dict) or "data" not in payload:
62+
raise ValueError("Invalid payload format: 'data' key is missing")
63+
if not all(key in payload["data"] for key in ["order_id", "customer_id", "amount"]):
64+
raise ValueError("Invalid payload: 'order_id', 'customer_id', and 'amount' are required in 'data'")
65+
66+
# Convert payload to JSON string and encode as base64
67+
message_content = json.dumps(payload)
68+
69+
# Prepare message
70+
message = {
71+
"content": message_content,
72+
"contentType": "application/json"
73+
}
74+
75+
put_messages_details = oci.queue.models.PutMessagesDetails(
76+
messages=[oci.queue.models.PutMessagesDetailsEntry(content=message["content"])]
77+
)
78+
79+
response = queue_client.put_messages(
80+
queue_id=queue_ocid,
81+
put_messages_details=put_messages_details
82+
)
83+
84+
# Check response
85+
if response.status == 200:
86+
logger.info(f"Successfully posted message to queue {queue_ocid}")
87+
return {
88+
"status": "success",
89+
"messageId": response.data.messages[0].id
90+
}
91+
else:
92+
logger.error(f"Failed to post message: {response.status}")
93+
return {
94+
"status": "error",
95+
"message": f"Failed to post message: {response.status}"
96+
}
97+
98+
except Exception as e:
99+
logger.error(f"Error in handler: {str(e)}")
100+
return {
101+
"status": "error",
102+
"message": str(e)
103+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
schema_version: 20180708
2+
name: my-python-function
3+
version: 0.0.18
4+
runtime: python
5+
build_image: fnproject/python:3.11-dev
6+
run_image: fnproject/python:3.11
7+
entrypoint: /python/bin/fdk /function/func.py handler
8+
memory: 256
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
fdk>=0.1.96
2+
oci
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
FROM python:3.9-slim
2+
WORKDIR /app
3+
COPY queue_poller.py .
4+
COPY requirements.txt .
5+
RUN pip install --no-cache-dir -r requirements.txt
6+
CMD ["python", "queue_poller.py"]

0 commit comments

Comments
 (0)