Integrando uma Web API com Datastore Emulator
Tags: python, gcp, dataengineering, webdev
Published: 2023-02-21 15:12:12

O custo elevado do faturamento associado aos projetos do Google Cloud Platform (GCP) é algo que sempre devemos ter em mente durante todo o ciclo de desenvolvimento de um produto.

A fim de mitigar esse problema, uma das abordagens é o uso de emuladores que simulam alguns serviços localmente, acarretando em custo zero para o projeto.

Hoje, iremos ver como rodar o emulador oficial do Datastore localmente com Docker, e como integrá-lo com uma Web API!

## Desenvolvimento da solução

Iremos desenvolver uma breve solução através do notebook para demonstrar o funcionamento do emulator do Datastore. Para tal, iremos desenvolver uma Web API bem simples que irá se integrar com o Datastore local.

Toda a solução será desenvolvida por meio de contêineres via Docker.

### Web API

Desenvolvimento de uma API responsável pelo cadastro de usuários. Os dados serão persistidos localmente no contêiner do Datastore.

- `POST /users`: salva um usuário no Datastore;
- `GET /users`: recupera todos os usuários persistidos no Datastore;

Também será desenvolvido um arquivo Dockerfile para fazer a instalação das bibliotecas necessárias e subir o servidor de desenvolvimento da API.

```bash
$ mkdir src
$ touch src/app.py
```

```python
import os

from fastapi import FastAPI
from google.cloud import datastore
from pydantic import BaseModel


PROJECT_ID = os.getenv("DATASTORE_PROJECT_ID")

client = datastore.Client(PROJECT_ID)
app = FastAPI()


class User(BaseModel):
    name: str
    age: int


@app.post("/users", status_code=201)
def create_user(user: User):
    kind = "users"

    key = client.key(kind)
    user_entity = client.entity(key)
    user_entity.update(user)

    client.put(user_entity)
    return user


@app.get("/users")
def get_users():
    query = client.query(kind="users")
    users = list(query.fetch())

    return users
```

```bash
$ touch requirements.txt
fastapi==0.91.0
uvicorn==0.20.0
google-cloud-datastore==2.13.2
```

```bash
$touch web-api.Dockerfile
FROM python:3.11-alpine


WORKDIR /app

COPY requirements.txt requirements.txt
RUN pip install --no-cache-dir --upgrade -r requirements.txt

COPY . .

CMD exec python -m uvicorn app:app --reload --app-dir=./src --host=0.0.0.0 --port=8000
```

### Datastore

O desenvolvimento de um contêiner do Datastore consiste nos seguintes passos:

- Desenvolvimento de uma imagem customizada tendo como base a imagem oficial dos emuladores do GCP;
- Iniciar o servidor web do Datastore;
-

```bash
$ touch datastore.Dockerfile
FROM gcr.io/google.com/cloudsdktool/google-cloud-cli:emulators


WORKDIR /datastore

CMD exec gcloud beta emulators datastore start --project my-local-project --host-port 0.0.0.0
```

### Desenvolvimento dos contêineres

Por fim, vamos desenvolver um arquivo docker-compose que irá orquestrar os contêineres construídos com base em nossas imagens.

> Nota: observe que é necessário configurar as variáveis de ambiente do Datastore no contêiner da API. Isso se faz necessário para que o SDK do Datastore envie as solicitações diretamente para o contêiner local, não para os servidores do GCP.

```bash
$ touch docker-compose.yml
version: '3.9'

services:
  web-api:
    build:
      dockerfile: ./web-api.Dockerfile
    environment:
      DATASTORE_DATASET: my-local-dataset
      DATASTORE_EMULATOR_HOST: datastore:8081
      DATASTORE_EMULATOR_HOST_PATH: datastore:8081/datastore
      DATASTORE_HOST: http://datastore:8081
      DATASTORE_PROJECT_ID: my-local-project
    volumes:
      - ./:/app
    ports:
      - 8000:8000

  datastore:
    build:
      dockerfile: ./datastore.Dockerfile
    ports:
      - 8081:8081
```

```bash
$ docker compose up -d
$ docker compose ps
```

### Vamos testar!

Iremos fazer três solicitações para nossa API. As duas primeiras serão solicitações POST, que irá salvar dois usuários no Datastore, e a última será uma solicitação GET que irá recuperar os dois usuários persistidos no banco de dados.

```bash
$ curl -X 'POST' \
  'http://localhost:8000/users' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "John",
  "age": 20
}'

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    57  100    24  100    33     54     75 --:--:-- --:--:-- --:--:--   130
{"name":"John","age":20}
```

```bash
$ curl -X 'POST' \
  'http://localhost:8000/users' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "Mary",
  "age": 18
}'

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    57  100    24  100    33   1411   1941 --:--:-- --:--:-- --:--:--  3352
{"name":"Mary","age":18}
```

```bash
$ curl -X 'GET' \
  'http://localhost:8000/users' \
  -H 'accept: application/json'

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    51  100    51    0     0    850      0 --:--:-- --:--:-- --:--:--   850
[{"name":"John","age":20},{"name":"Mary","age":18}]
```

## Considerações finais

O uso dos emuladores do Google Cloud Platform podem ser uma abordagem interessante durante o ciclo de desenvolvimento de um produto. Com eles, podemos testar nossas soluções quantas vezes forem necessárias sem acarretar em um custo elevado no faturamento do projeto, visto que todas as solicitações das bibliotecas do Google Cloud serão feitas localmente ao invés de serem feitas para os servidores da nuvem.

Obrigado por ter me acompanhado até aqui 💚. Até a próxima!

## Referências

- [Como executar o Emulador do Datastore](https://cloud.google.com/datastore/docs/tools/datastore-emulator?hl=pt-br);
- [Gcloud beta emulators](https://cloud.google.com/sdk/gcloud/reference/beta/emulators);