Construindo uma API Serverless na AWS com Lambda, API Gateway e DynamoDB
Aprenda a construir uma API serverless na AWS utilizando API Gateway, AWS Lambda, DynamoDB, IAM e CloudWatch. Neste artigo, você verá na prática como criar uma API de tarefas, configurar permissões, persistir dados, monitorar a aplicação e entender os principais trade-offs dessa arquitetura.
Do zero ao Serverless: construindo uma API com AWS Lambda, API Gateway e DynamoDB
Construir uma API normalmente envolve pensar em servidores, sistema operacional, escalabilidade, disponibilidade, atualizações e infraestrutura. Porém, em muitos projetos modernos, não precisamos administrar diretamente toda essa estrutura.
É justamente nesse cenário que arquiteturas serverless se tornam interessantes.
Neste artigo, vamos construir do zero uma API RESTful simples utilizando serviços gerenciados da AWS. Nossa aplicação permitirá cadastrar, consultar, atualizar e excluir tarefas utilizando uma arquitetura composta por Amazon API Gateway, AWS Lambda, Amazon DynamoDB, AWS IAM e Amazon CloudWatch.
O objetivo não é apenas fazer a API funcionar. Também vamos entender as decisões arquiteturais envolvidas, o papel de cada serviço e alguns pontos que deveriam ser considerados antes de utilizar essa arquitetura em produção.
O que vamos construir
Vamos desenvolver uma pequena Tasks API, responsável por gerenciar tarefas.
Nossa API terá os seguintes endpoints:
| Método | Endpoint | Ação |
|---|---|---|
| POST | /tasks | Criar uma tarefa |
| GET | /tasks | Listar tarefas |
| GET | /tasks/{id} | Buscar uma tarefa |
| PUT | /tasks/{id} | Atualizar uma tarefa |
| DELETE | /tasks/{id} | Excluir uma tarefa |
Uma tarefa terá uma estrutura semelhante a esta:
1
2
3
4
5
6
7
{
"id": "2eed806f-d439-48dd-84d7-7aa95d00be91",
"title": "Estudar AWS Lambda",
"description": "Criar minha primeira API Serverless",
"done": false,
"createdAt": "2026-10-07T10:30:00+00:00"
}Arquitetura da solução
A arquitetura será:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Cliente
│
│ HTTP Request
▼
┌─────────────────────┐
│ Amazon API Gateway │
└──────────┬──────────┘
│
▼
┌────────────────┐
│ AWS Lambda │
└───────┬────────┘
│
┌───────┴─────────┐
▼ ▼
┌─────────────────┐ ┌───────────────┐
│ Amazon DynamoDB │ │ CloudWatch │
└─────────────────┘ └───────────────┘O cliente envia uma requisição HTTP para o API Gateway.
O API Gateway identifica a rota solicitada e encaminha a requisição para nossa função Lambda. Em uma integração Lambda Proxy, o API Gateway envia os dados da chamada para a função e utiliza a resposta retornada pelo Lambda para responder ao cliente.
O Lambda executa nossa regra de negócio e acessa o DynamoDB quando for necessário salvar ou recuperar informações.
Durante a execução, os logs gerados pela função podem ser enviados automaticamente ao CloudWatch Logs.
Por que utilizar Serverless?
O termo serverless pode causar uma pequena confusão.
Naturalmente, servidores continuam existindo. A diferença é que o desenvolvedor não precisa administrar diretamente esses servidores.
Quando utilizamos AWS Lambda, a AWS é responsável por boa parte da infraestrutura necessária para executar nossa função.
Isso muda bastante nossa responsabilidade como desenvolvedor.
Em vez de provisionar uma máquina virtual, instalar sistema operacional, configurar servidor web e gerenciar capacidade, podemos concentrar nossa atenção principalmente na aplicação e nas integrações entre os serviços.
Isso não significa que serverless seja sempre a melhor arquitetura.
Toda escolha arquitetural possui vantagens e trade-offs, algo que discutiremos no final do artigo.
Serviços utilizados
Para este projeto utilizaremos:
- Amazon API Gateway: será a porta de entrada HTTP da nossa aplicação.
- AWS Lambda: executará nossa lógica de negócio.
- Amazon DynamoDB: armazenará nossas tarefas.
- AWS IAM: controlará quais recursos nossa função poderá acessar.
- Amazon CloudWatch: armazenará logs e ajudará na observabilidade da aplicação.
- Python: será utilizado para implementar nossa função Lambda.
Amazon API Gateway
O API Gateway será responsável por expor nossa aplicação por meio de endpoints HTTP.
Neste projeto utilizaremos uma HTTP API.
A AWS oferece tanto REST APIs quanto HTTP APIs no API Gateway. As HTTP APIs possuem um conjunto de funcionalidades mais enxuto e podem integrar diretamente com funções Lambda ou outros endpoints HTTP.
Para uma API simples como a nossa, isso é suficiente.
AWS Lambda
O AWS Lambda será responsável por executar nosso código.
Nossa função receberá os eventos enviados pelo API Gateway, identificará o método HTTP utilizado e executará a operação correspondente.
Por exemplo:
1
2
3
4
5
6
7
8
9
10
POST /tasks
│
▼
Lambda
│
▼
PutItem
│
▼
DynamoDBEnquanto uma requisição:
1
2
3
4
5
6
7
8
9
10
GET /tasks
│
▼
Lambda
│
▼
Scan
│
▼
DynamoDBNeste projeto teremos uma única função responsável pelas operações para tornar o exemplo mais simples.
Em aplicações maiores, poderíamos dividir essas responsabilidades entre diversas funções.
Amazon DynamoDB
O DynamoDB será nosso banco de dados.
Criaremos uma tabela chamada:
1
TasksNossa chave de partição será:
1
iddo tipo:
1
StringCada tarefa terá um UUID diferente.
Exemplo:
1
2
3
4
5
6
{
"id": "094ec620-85d6-4ac4-83cb-d12b05531ed7",
"title": "Estudar DynamoDB",
"description": "Entender bancos NoSQL na AWS",
"done": false
}Passo 1 — Criando a tabela no DynamoDB
Entre no AWS Management Console e procure por:
1
DynamoDBAcesse:
1
Tablese selecione:
1
Create tableUtilize:
1
2
3
4
5
6
7
Table name: Tasks
Partition key:
id
Type:
StringPara este laboratório, podemos manter as demais configurações simples e criar a tabela.
Após alguns segundos, o DynamoDB disponibilizará nossa tabela.
Passo 2 — Criando nossa função Lambda
Agora procure por:
1
Lambdano console da AWS.
Selecione:
1
Create functionEscolha a opção:
1
Author from scratchUtilize um nome semelhante a:
1
tasks-apiSelecione um runtime Python atualmente suportado pelo AWS Lambda.
Crie a função.
Depois da criação, teremos acesso ao editor de código do Lambda.
Passo 3 — Criando a variável de ambiente
Não é uma boa ideia colocar diretamente no código informações de configuração que podem mudar entre ambientes.
Por isso, vamos utilizar uma variável de ambiente para informar qual tabela nossa função deve utilizar.
Nas configurações da função Lambda, abra:
1
2
Configuration
→ Environment variablesAdicione:
1
2
3
4
5
Key:
TABLE_NAME
Value:
TasksNo Python podemos recuperar essa variável utilizando:
1
2
3
import os
table_name = os.environ.get("TABLE_NAME")Variáveis de ambiente são suportadas pelo Lambda e podem ser acessadas durante a execução da função.
Isso também facilita trabalhar futuramente com ambientes diferentes.
Por exemplo:
1
2
3
TasksDevelopment
TasksStaging
TasksProductionsem precisar alterar o código da aplicação.
Passo 4 — Implementando nossa API
Substitua o código da função Lambda pelo seguinte:
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
import json
import os
import uuid
import logging
from datetime import datetime, timezone
import boto3
logger = logging.getLogger()
logger.setLevel(logging.INFO)
dynamodb = boto3.resource("dynamodb")
TABLE_NAME = os.environ.get("TABLE_NAME", "Tasks")
table = dynamodb.Table(TABLE_NAME)
def response(status_code, body):
return {
"statusCode": status_code,
"headers": {
"Content-Type": "application/json"
},
"body": json.dumps(body)
}
def create_task(event):
try:
body = json.loads(event.get("body") or "{}")
except json.JSONDecodeError:
return response(400, {
"message": "JSON inválido."
})
title = body.get("title")
if not title:
return response(400, {
"message": "O campo 'title' é obrigatório."
})
task = {
"id": str(uuid.uuid4()),
"title": title,
"description": body.get("description", ""),
"done": False,
"createdAt": datetime.now(timezone.utc).isoformat()
}
table.put_item(Item=task)
logger.info("Task criada: %s", task["id"])
return response(201, task)
def list_tasks():
result = table.scan()
return response(200, {
"items": result.get("Items", [])
})
def get_task(task_id):
result = table.get_item(
Key={
"id": task_id
}
)
task = result.get("Item")
if not task:
return response(404, {
"message": "Tarefa não encontrada."
})
return response(200, task)
def update_task(task_id, event):
existing = table.get_item(
Key={
"id": task_id
}
).get("Item")
if not existing:
return response(404, {
"message": "Tarefa não encontrada."
})
try:
body = json.loads(event.get("body") or "{}")
except json.JSONDecodeError:
return response(400, {
"message": "JSON inválido."
})
allowed_fields = [
"title",
"description",
"done"
]
updates = {
key: value
for key, value in body.items()
if key in allowed_fields
}
if not updates:
return response(400, {
"message": "Nenhum campo válido informado."
})
expression_parts = []
expression_values = {}
expression_names = {}
for index, (key, value) in enumerate(updates.items()):
name_placeholder = f"#field{index}"
value_placeholder = f":value{index}"
expression_parts.append(
f"{name_placeholder} = {value_placeholder}"
)
expression_names[name_placeholder] = key
expression_values[value_placeholder] = value
result = table.update_item(
Key={
"id": task_id
},
UpdateExpression="SET " + ", ".join(expression_parts),
ExpressionAttributeNames=expression_names,
ExpressionAttributeValues=expression_values,
ReturnValues="ALL_NEW"
)
logger.info("Task atualizada: %s", task_id)
return response(
200,
result["Attributes"]
)
def delete_task(task_id):
existing = table.get_item(
Key={
"id": task_id
}
).get("Item")
if not existing:
return response(404, {
"message": "Tarefa não encontrada."
})
table.delete_item(
Key={
"id": task_id
}
)
logger.info("Task excluída: %s", task_id)
return response(200, {
"message": "Tarefa excluída com sucesso."
})
def lambda_handler(event, context):
logger.info("Request recebida")
try:
http = event["requestContext"]["http"]
method = http["method"]
path = event.get("rawPath", "")
if method == "POST" and path == "/tasks":
return create_task(event)
if method == "GET" and path == "/tasks":
return list_tasks()
task_id = (
event.get("pathParameters") or {}
).get("id")
if task_id:
if method == "GET":
return get_task(task_id)
if method == "PUT":
return update_task(task_id, event)
if method == "DELETE":
return delete_task(task_id)
return response(404, {
"message": "Rota não encontrada."
})
except Exception:
logger.exception(
"Erro inesperado durante processamento da requisição"
)
return response(500, {
"message": "Erro interno do servidor."
})Depois de inserir o código, faça o deploy da função.
Entendendo o código
Uma parte importante está logo no início:
1
2
3
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(TABLE_NAME)Observe que criamos o recurso do DynamoDB fora do:
1
lambda_handlerIsso é proposital.
A AWS recomenda aproveitar a reutilização do ambiente de execução do Lambda e inicializar clientes SDK e conexões fora do handler quando apropriado. Invocações posteriores executadas no mesmo ambiente podem reutilizar esses objetos.
Depois temos:
1
def lambda_handler(event, context):Esse é o ponto de entrada da função.
Quando o API Gateway chamar nosso Lambda, receberemos um evento contendo informações da requisição.
Pegamos o método HTTP através de:
1
event["requestContext"]["http"]["method"]e o caminho através de:
1
event["rawPath"]Depois direcionamos a execução.
Por exemplo:
1
2
if method == "POST" and path == "/tasks":
return create_task(event)Passo 5 — Permissões com IAM
Nossa função Lambda precisa acessar o DynamoDB.
Entretanto, isso não deveria significar dar acesso total a todos os recursos do DynamoDB da conta.
É aqui que entra o IAM — Identity and Access Management.
Cada função Lambda utiliza um execution role, que determina quais ações aquela função pode executar em outros recursos AWS.
Vamos conceder apenas as permissões necessárias para nossa tabela.
Uma política poderia ser semelhante a:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:PutItem",
"dynamodb:GetItem",
"dynamodb:Scan",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem"
],
"Resource": "ARN_DA_SUA_TABELA"
}
]
}Substitua:
1
ARN_DA_SUA_TABELApelo ARN da tabela
Tasks.Podemos encontrá-lo nas informações da tabela dentro do DynamoDB.
Essa abordagem segue um princípio importante de segurança chamado:
1
Principle of Least Privilegeou:
1
Princípio do menor privilégioA ideia é simples: cada aplicação deve possuir apenas as permissões necessárias para executar sua função.
A própria AWS recomenda restringir as permissões da função às ações e recursos realmente necessários antes de colocar uma aplicação em produção.
Passo 6 — Criando nossa HTTP API
Agora precisamos disponibilizar nossa função para clientes externos.
Abra:
1
API Gatewaye crie uma:
1
HTTP APIAdicione nossa função:
1
tasks-apicomo integração Lambda.
Vamos configurar as seguintes rotas:
1
2
3
4
5
6
7
8
9
POST /tasks
GET /tasks
GET /tasks/{id}
PUT /tasks/{id}
DELETE /tasks/{id}Todas essas rotas serão direcionadas para nossa função Lambda.
No final teremos algo conceitualmente semelhante a:
1
2
3
4
5
6
7
8
9
POST /tasks ──────────┐
│
GET /tasks ───────────┤
│
GET /tasks/{id} ──────┤
├──► Lambda ──► DynamoDB
PUT /tasks/{id} ──────┤
│
DELETE /tasks/{id} ───┘Após configurar e publicar a API, o API Gateway fornecerá um endereço para nossa aplicação.
Ele terá uma estrutura semelhante a:
1
https://xxxxxxxxxx.execute-api.REGION.amazonaws.comA partir desse momento podemos testar nossa API.
Testando a criação de uma tarefa
Podemos utilizar Postman, Insomnia, Bruno, curl ou outro cliente HTTP.
Faça:
1
POST /taskscom:
1
2
3
4
{
"title": "Estudar AWS",
"description": "Criar uma API Serverless"
}A resposta esperada será semelhante a:
1
2
3
4
5
6
7
{
"id": "878e674a-c4cc-48b4-b12c-5ceff7fcf7b6",
"title": "Estudar AWS",
"description": "Criar uma API Serverless",
"done": false,
"createdAt": "2026-10-07T10:30:00+00:00"
}O status HTTP deverá ser:
1
201 CreatedPodemos abrir o DynamoDB e verificar que nosso registro foi salvo.
Nesse momento nosso fluxo está funcionando:
1
2
3
4
5
6
7
Cliente
↓
API Gateway
↓
Lambda
↓
DynamoDBListando as tarefas
Agora faça:
1
GET /tasksA resposta será semelhante a:
1
2
3
4
5
6
7
8
9
10
11
{
"items": [
{
"id": "878e674a-c4cc-48b4-b12c-5ceff7fcf7b6",
"title": "Estudar AWS",
"description": "Criar uma API Serverless",
"done": false,
"createdAt": "2026-10-07T10:30:00+00:00"
}
]
}Consultando uma tarefa específica
Pegue o ID criado anteriormente:
1
878e674a-c4cc-48b4-b12c-5ceff7fcf7b6e faça:
1
GET /tasks/878e674a-c4cc-48b4-b12c-5ceff7fcf7b6Se o registro existir, receberemos:
1
2
3
4
5
6
7
{
"id": "878e674a-c4cc-48b4-b12c-5ceff7fcf7b6",
"title": "Estudar AWS",
"description": "Criar uma API Serverless",
"done": false,
"createdAt": "2026-10-07T10:30:00+00:00"
}Caso contrário:
1
2
3
{
"message": "Tarefa não encontrada."
}com status:
1
404Atualizando uma tarefa
Para concluir uma tarefa podemos realizar:
1
PUT /tasks/{id}enviando:
1
2
3
{
"done": true
}Nossa função utiliza:
1
table.update_item()para atualizar o registro existente.
A resposta será semelhante a:
1
2
3
4
5
6
7
{
"id": "878e674a-c4cc-48b4-b12c-5ceff7fcf7b6",
"title": "Estudar AWS",
"description": "Criar uma API Serverless",
"done": true,
"createdAt": "2026-10-07T10:30:00+00:00"
}Excluindo uma tarefa
Podemos utilizar:
1
DELETE /tasks/{id}Ao excluir com sucesso:
1
2
3
{
"message": "Tarefa excluída com sucesso."
}Observabilidade com CloudWatch
Uma aplicação não termina quando conseguimos fazê-la funcionar.
Também precisamos entender o que está acontecendo quando alguma coisa dá errado.
No nosso código utilizamos:
1
2
3
4
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)e posteriormente:
1
logger.info("Task criada: %s", task["id"])Caso ocorra uma exceção inesperada:
1
2
3
logger.exception(
"Erro inesperado durante processamento da requisição"
)O AWS Lambda envia logs da execução para o Amazon CloudWatch Logs quando a função possui as permissões necessárias.
Isso permite investigar erros sem precisar acessar diretamente nenhuma máquina.
No CloudWatch conseguiremos encontrar informações relacionadas às execuções da nossa função e aos logs produzidos pela aplicação.
Em aplicações de produção poderíamos avançar ainda mais utilizando métricas, alarmes, logs estruturados e tracing distribuído.
O que acontece quando chegam várias requisições?
Aqui aparece uma das características interessantes do modelo serverless.
Não criamos manualmente um servidor:
1
EC2nem configuramos diretamente:
1
2
3
4
Nginx
Apache
Auto Scaling Group
Load Balancerpara este exemplo.
Estamos utilizando serviços gerenciados que abstraem boa parte dessa infraestrutura.
O Lambda pode criar ambientes adicionais de execução conforme necessário para atender invocações concorrentes, sujeito às configurações e quotas do serviço.
Porém, isso também significa que devemos pensar na escalabilidade dos componentes que nossa função utiliza.
Imagine que nossa aplicação receba milhares de chamadas simultaneamente.
Não basta pensar:
1
"O Lambda escala."Precisamos pensar:
1
2
3
4
5
6
7
8
9
10
11
12
13
O banco suporta essa carga?
Existe algum serviço externo sendo chamado?
Possuímos limites de concorrência?
Quanto cada requisição custa?
Nossa aplicação é idempotente?
Como tratamos throttling?
Como lidamos com erros e retentativas?Arquitetura não é apenas escolher serviços.
É entender como esses serviços se comportam juntos.
Segurança
Nossa API atual foi construída principalmente para aprendizado.
Em produção, alguns pontos precisariam ser avaliados.
Um dos mais importantes seria autenticação e autorização.
Dependendo da aplicação, poderíamos utilizar mecanismos como Amazon Cognito, JWT Authorizers ou outros controles de autenticação compatíveis com o API Gateway.
Também precisaríamos avaliar validação de entrada, proteção contra abuso, tratamento de dados sensíveis, políticas IAM, gerenciamento de segredos e observabilidade.
Outro ponto importante está no nosso código:
1
except Exception:Não retornamos detalhes internos da exceção diretamente para o usuário.
Para o cliente retornamos apenas:
1
2
3
{
"message": "Erro interno do servidor."
}Enquanto a informação técnica fica registrada nos logs.
Isso evita expor detalhes internos desnecessariamente.
Uma observação importante sobre nosso GET /tasks
Para simplificar o tutorial utilizamos:
1
table.scan()Isso funciona bem para nosso laboratório.
Entretanto, existe uma diferença importante entre:
1
Scane:
1
Queryno DynamoDB.
Um
Scan examina os itens da tabela e pode se tornar uma escolha ruim para determinados padrões de acesso em tabelas grandes.Em um projeto real, deveríamos primeiro estudar nossos access patterns e desenhar a estrutura da tabela de acordo com as consultas que nossa aplicação precisa executar.
Esse é um exemplo de uma diferença importante entre:
1
fazer funcionare:
1
desenhar uma arquitetura preparada para produção.Uma Lambda ou várias Lambdas?
Neste projeto utilizamos:
1
2
3
4
5
6
7
API Gateway
│
▼
1 Lambda
│
▼
DynamoDBIsso deixa o tutorial mais simples.
Porém, poderíamos utilizar:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
POST /tasks
↓
CreateTask Lambda
GET /tasks
↓
ListTasks Lambda
GET /tasks/{id}
↓
GetTask Lambda
PUT /tasks/{id}
↓
UpdateTask Lambda
DELETE /tasks/{id}
↓
DeleteTask LambdaEssa segunda abordagem permite maior isolamento entre algumas responsabilidades e permissões.
Por exemplo:
1
GetTask Lambdapoderia possuir apenas:
1
dynamodb:GetItemenquanto:
1
DeleteTask Lambdapossuiria:
1
dynamodb:DeleteItemPor outro lado, mais funções também significam mais recursos para organizar, configurar, observar e implantar.
Não existe uma regra dizendo que uma dessas abordagens é sempre correta.
A decisão depende do domínio, tamanho do projeto, requisitos de segurança, organização do time, manutenção e complexidade operacional desejada.
Esse é justamente um dos pontos interessantes da arquitetura de software:
arquitetura é um conjunto de decisões e trade-offs.
Serverless significa custo zero?
Não.
Serverless não significa:
1
freeSignifica que o modelo de infraestrutura e cobrança é diferente daquele encontrado em um servidor tradicional provisionado continuamente.
API Gateway, Lambda, DynamoDB e CloudWatch possuem seus próprios modelos de cobrança e possíveis franquias gratuitas, dependendo do serviço e das condições da conta.
Por isso, antes de colocar uma aplicação real em produção é importante consultar a página atual de preços de cada serviço e estimar o comportamento da carga.
Uma arquitetura pode ser extremamente barata para determinado padrão de uso e pouco interessante financeiramente para outro.
O custo também é uma decisão arquitetural.
Pontos positivos dessa arquitetura
Nossa solução possui algumas características interessantes.
Não precisamos administrar diretamente servidores para executar nossa API.
Os serviços utilizados possuem integração nativa dentro do ecossistema AWS.
Nossa aplicação também ficou bastante pequena:
1
2
3
4
5
API Gateway
↓
Lambda
↓
DynamoDBIsso permite construir APIs e microsserviços relativamente rapidamente.
A arquitetura também facilita experimentar aplicações orientadas a eventos.
No futuro, poderíamos adicionar outros serviços.
Por exemplo:
1
2
3
4
5
6
7
8
9
┌──► DynamoDB
│
API Gateway ─► Lambda
│
├──► SQS
│
├──► SNS
│
└──► EventBridgeUma tarefa criada poderia gerar um evento.
Outro serviço poderia consumir esse evento posteriormente sem aumentar diretamente a responsabilidade da requisição original.
Limitações e trade-offs
Nenhuma arquitetura é perfeita.
Serverless também possui pontos que precisam ser avaliados.
Dependendo da aplicação podemos encontrar requisitos relacionados a latência, duração da execução, concorrência, limites dos serviços, observabilidade distribuída e dependência maior da plataforma utilizada.
Também existe o conhecido conceito de:
1
cold startQuando um ambiente de execução precisa ser inicializado, pode existir latência adicional antes da execução da função.
O impacto disso varia conforme runtime, configuração, código e características da aplicação.
Isso não significa que Lambda seja ruim.
Significa apenas que a decisão deve estar relacionada aos requisitos do sistema.
Uma aplicação interna que recebe algumas centenas de requisições por dia possui necessidades muito diferentes de um sistema financeiro extremamente sensível à latência.
E se esse projeto fosse para produção?
Nossa arquitetura inicial é:
1
2
3
4
5
API Gateway
↓
Lambda
↓
DynamoDBPara uma aplicação real poderíamos evoluir para algo como:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──────────────┐
│ Cognito │
└──────┬───────┘
│
▼
Cliente ───────────► API Gateway
│
▼
Lambda
┌─────┴─────┐
▼ ▼
DynamoDB EventBridge
│
┌────┴────┐
▼ ▼
SQS Lambda
Observabilidade:
CloudWatch + métricas + alarmes + tracingTambém poderíamos utilizar Infrastructure as Code para que nossa infraestrutura pudesse ser versionada juntamente com a aplicação.
Ferramentas como AWS SAM, AWS CDK ou CloudFormation poderiam entrar nessa evolução.
Assim sairíamos de:
1
criar recursos manualmentepara:
1
descrever nossa infraestrutura como códigopermitindo maior repetibilidade entre ambientes.
O que aprendemos
Começamos este artigo com uma necessidade simples:
1
Criar uma API de tarefas.Porém, ao longo da implementação acabamos explorando diversos conceitos importantes de cloud computing e arquitetura de software.
Criamos uma camada HTTP utilizando API Gateway.
Executamos nossa aplicação utilizando AWS Lambda.
Persistimos os dados utilizando DynamoDB.
Controlamos acesso aos recursos com IAM.
Adicionamos observabilidade através do CloudWatch.
E, principalmente, discutimos as decisões envolvidas nessa arquitetura.
Nossa arquitetura final ficou:
1
2
3
4
5
6
7
8
9
10
Internet
│
▼
Amazon API Gateway
│
▼
AWS Lambda
│ │
▼ ▼
DynamoDB CloudWatchÉ uma arquitetura relativamente pequena, mas já demonstra vários conceitos presentes em sistemas reais.
Próximos passos
Este projeto ainda possui bastante espaço para evolução.
Uma próxima versão poderia introduzir autenticação, Infrastructure as Code, testes automatizados, CI/CD e uma arquitetura orientada a eventos.
Também poderíamos substituir a configuração manual por AWS SAM ou AWS CDK e criar toda a infraestrutura através de código.
Outra evolução interessante seria adicionar:
1
2
3
4
5
6
7
8
9
10
11
POST /tasks
↓
Lambda
↓
DynamoDB
↓
DynamoDB Streams
↓
Lambda
↓
NotificaçãoIsso permitiria começar a explorar arquiteturas event-driven dentro da AWS.
Conclusão
Construir uma aplicação na nuvem não significa apenas mover código para algum servidor remoto.
Cloud computing permite repensar a própria forma como estruturamos nossos sistemas.
Neste projeto conseguimos construir uma API completa utilizando serviços gerenciados e sem administrar diretamente um servidor.
Mais importante do que conhecer individualmente Lambda, API Gateway ou DynamoDB é entender como eles se relacionam e por que escolheríamos cada um deles.
Essa mudança de pensamento é importante para quem começa a estudar arquitetura de software.
Em vez de perguntar apenas:
1
"Qual tecnologia devo usar?"podemos começar a perguntar:
1
2
3
4
5
6
7
8
9
10
11
12
13
"Qual problema estou tentando resolver?"
"Quais requisitos meu sistema possui?"
"Quais são os trade-offs dessa decisão?"
"Como essa arquitetura se comportará conforme o sistema crescer?"
"Quanto ela custa?"
"Como vou monitorá-la?"
"Como vou protegê-la?"É nesse momento que deixamos de simplesmente conectar serviços e começamos a realmente pensar em arquitetura.
Arquitetura final
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
┌────────────────┐
│ Client │
└───────┬────────┘
│ HTTPS
▼
┌───────────────────┐
│ API Gateway │
│ HTTP API │
└─────────┬─────────┘
│
▼
┌───────────────────┐
│ AWS Lambda │
│ Python │
└──────┬─────┬──────┘
│ │
│ └─────────────┐
▼ ▼
┌─────────────────┐ ┌──────────────┐
│ Amazon DynamoDB │ │ CloudWatch │
│ Tasks │ │ Logs │
└─────────────────┘ └──────────────┘
▲
│
IAM Execution RoleTecnologias utilizadas: AWS Lambda, Amazon API Gateway, Amazon DynamoDB, AWS IAM, Amazon CloudWatch e Python.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article