Os leitores de minhas publicações provavelmente estão familiarizados com a ideia de empregar uma abordagem API First para desenvolver microsserviços. Inúmeras vezes, percebi os benefícios de descrever os URIs previstos e os modelos de objetos subjacentes antes do início de qualquer desenvolvimento.
Em meus mais de 30 anos navegando na tecnologia, porém, passei a esperar a realidade dos fluxos alternativos . Em outras palavras, espero que haja situações em que o API First simplesmente não seja possível.
Para este artigo, gostaria de apresentar um exemplo de como as equipes que produzem microsserviços ainda podem ter sucesso no fornecimento de uma especificação OpenAPI para outros consumirem sem definir manualmente um arquivo openapi.json.
Eu também queria sair da minha zona de conforto e fazer isso sem usar Java, .NET ou mesmo JavaScript.
Na conclusão da maioria dos meus artigos, menciono frequentemente a minha declaração de missão pessoal:
“Concentre seu tempo em fornecer recursos/funcionalidades que ampliem o valor de sua propriedade intelectual. Aproveite estruturas, produtos e serviços para todo o resto.” – J. Vester
Meu objetivo nesta declaração de missão é responsabilizar-me por fazer o melhor uso do meu tempo ao tentar alcançar metas e objetivos definidos em um nível superior. Basicamente, se nosso foco é vender mais widgets, meu tempo deveria ser gasto procurando maneiras de tornar isso possível – evitando desafios que já foram resolvidos por estruturas, produtos ou serviços existentes.
Escolhi Python como linguagem de programação para meu novo microsserviço. Até o momento, 99% do código Python que escrevi em meus artigos anteriores foi o resultado de respostas orientadas por Stack Overflow Driven Development (SODD) ou ChatGPT. Claramente, Python está fora da minha zona de conforto.
Agora que defini o nível das coisas, queria criar um novo microsserviço RESTful baseado em Python que seguisse minha declaração de missão pessoal com experiência mínima na linguagem de origem.
Foi quando encontrei FastAPI .
FastAPI existe desde 2018 e é uma estrutura focada no fornecimento de APIs RESTful usando dicas do tipo Python. A melhor parte do FastAPI é a capacidade de gerar automaticamente especificações OpenAPI 3 sem nenhum esforço adicional da perspectiva do desenvolvedor.
Para este artigo, surgiu a ideia de uma API de artigo, fornecendo uma API RESTful que permite aos consumidores recuperar uma lista de meus artigos publicados recentemente.
Para simplificar, vamos supor que um determinado Article
contenha as seguintes propriedades:
id
– propriedade de identificador simples e exclusivo (número)title
– o título do artigo (string)url
– o URL completo do artigo (string)year
– o ano em que o artigo foi publicado (número)
A API do artigo incluirá os seguintes URIs:
/articles
– irá recuperar uma lista de artigos/articles/{article_id}
– recuperará um único artigo pela propriedade id/articles
– adiciona um novo artigoNo meu terminal, criei um novo projeto Python chamado fast-api-demo e executei os seguintes comandos:
$ pip install --upgrade pip $ pip install fastapi $ pip install uvicorn
Criei um novo arquivo Python chamado api.py
e adicionei algumas importações, além de estabelecer uma variável app
:
from fastapi import FastAPI, HTTPException from pydantic import BaseModel app = FastAPI() if __name__ == "__main__": import uvicorn uvicorn.run(app, host="localhost", port=8000)
A seguir, defini um objeto Article
para corresponder ao caso de uso da API Article:
class Article(BaseModel): id: int title: str url: str year: int
Com o modelo estabelecido, precisei adicionar as URIs… o que acabou sendo bem fácil:
# Route to add a new article @app.post("/articles") def create_article(article: Article): articles.append(article) return article # Route to get all articles @app.get("/articles") def get_articles(): return articles # Route to get a specific article by ID @app.get("/articles/{article_id}") def get_article(article_id: int): for article in articles: if article.id == article_id: return article raise HTTPException(status_code=404, detail="Article not found")
Para evitar envolver um armazenamento de dados externo, decidi adicionar alguns dos meus artigos publicados recentemente de forma programática:
articles = [ Article(id=1, title="Distributed Cloud Architecture for Resilient Systems: Rethink Your Approach To Resilient Cloud Services", url="https://dzone.com/articles/distributed-cloud-architecture-for-resilient-syste", year=2023), Article(id=2, title="Using Unblocked to Fix a Service That Nobody Owns", url="https://dzone.com/articles/using-unblocked-to-fix-a-service-that-nobody-owns", year=2023), Article(id=3, title="Exploring the Horizon of Microservices With KubeMQ's New Control Center", url="https://dzone.com/articles/exploring-the-horizon-of-microservices-with-kubemq", year=2024), Article(id=4, title="Build a Digital Collectibles Portal Using Flow and Cadence (Part 1)", url="https://dzone.com/articles/build-a-digital-collectibles-portal-using-flow-and-1", year=2024), Article(id=5, title="Build a Flow Collectibles Portal Using Cadence (Part 2)", url="https://dzone.com/articles/build-a-flow-collectibles-portal-using-cadence-par-1", year=2024), Article(id=6, title="Eliminate Human-Based Actions With Automated Deployments: Improving Commit-to-Deploy Ratios Along the Way", url="https://dzone.com/articles/eliminate-human-based-actions-with-automated-deplo", year=2024), Article(id=7, title="Vector Tutorial: Conducting Similarity Search in Enterprise Data", url="https://dzone.com/articles/using-pgvector-to-locate-similarities-in-enterpris", year=2024), Article(id=8, title="DevSecOps: It's Time To Pay for Your Demand, Not Ingestion", url="https://dzone.com/articles/devsecops-its-time-to-pay-for-your-demand", year=2024), ]
Acredite ou não, isso conclui o desenvolvimento do microsserviço Article API.
Para uma verificação rápida de integridade, ativei meu serviço de API localmente:
$ python api.py INFO: Started server process [320774] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://localhost:8000 (Press CTRL+C to quit)
Então, em outra janela do terminal, enviei uma solicitação curl (e canalizei para json_pp
):
$ curl localhost:8000/articles/1 | json_pp { "id": 1, "title": "Distributed Cloud Architecture for Resilient Systems: Rethink Your Approach To Resilient Cloud Services", "url": "https://dzone.com/articles/distributed-cloud-architecture-for-resilient-syste", "year": 2023 }
Em vez de apenas executar a API do artigo localmente, pensei em ver como seria fácil implantar o microsserviço. Como nunca havia implantado um microsserviço Python no Heroku antes, achei que agora seria um ótimo momento para tentar.
Antes de mergulhar no Heroku, precisei criar um arquivo requirements.txt
para descrever as dependências do serviço. Para fazer isso, instalei e executei pipreqs
:
$ pip install pipreqs $ pipreqs
Isso criou um arquivo requirements.txt
para mim, com as seguintes informações:
fastapi==0.110.1 pydantic==2.6.4 uvicorn==0.29.0
Eu também precisava de um arquivo chamado Procfile
que informa ao Heroku como ativar meu microsserviço com uvicorn
. Seu conteúdo ficou assim:
web: uvicorn api:app --host=0.0.0.0 --port=${PORT}
Para aqueles que são novos em Python (como eu), usei a documentação Introdução ao Heroku com Python como um guia útil.
Como já tinha o Heroku CLI instalado, só precisei fazer login no ecossistema Heroku a partir do meu terminal:
$ heroku login
Fiz questão de verificar todas as minhas atualizações em meu repositório no GitLab.
A seguir, a criação de um novo aplicativo no Heroku pode ser realizada usando a CLI por meio do seguinte comando:
$ heroku create
A CLI respondeu com um nome de aplicativo exclusivo, juntamente com a URL do aplicativo e o repositório baseado em git associado ao aplicativo:
Creating app... done, powerful-bayou-23686 https://powerful-bayou-23686-2d5be7cf118b.herokuapp.com/ | https://git.heroku.com/powerful-bayou-23686.git
Observe: no momento em que você ler este artigo, meu aplicativo não estará mais online.
Veja isso. Quando emito um comando git remote, posso ver que um controle remoto foi adicionado automaticamente ao ecossistema Heroku:
$ git remote heroku origin
Para implantar o aplicativo fast-api-demo
no Heroku, tudo o que preciso fazer é usar o seguinte comando:
$ git push heroku main
Com tudo definido, consegui validar que meu novo serviço baseado em Python está instalado e funcionando no painel do Heroku:
Com o serviço em execução, é possível recuperar o Article
com id = 1
da API do Artigo emitindo o seguinte comando curl:
$ curl --location 'https://powerful-bayou-23686-2d5be7cf118b.herokuapp.com/articles/1'
O comando curl retorna uma resposta 200 OK e a seguinte carga JSON:
{ "id": 1, "title": "Distributed Cloud Architecture for Resilient Systems: Rethink Your Approach To Resilient Cloud Services", "url": "https://dzone.com/articles/distributed-cloud-architecture-for-resilient-syste", "year": 2023 }
Aproveitar a funcionalidade OpenAPI integrada do FastAPI permite que os consumidores recebam uma especificação v3 totalmente funcional navegando até o URI /docs
gerado automaticamente:
https://powerful-bayou-23686-2d5be7cf118b.herokuapp.com/docs
Chamar esse URL retorna o microsserviço da API Article usando a UI Swagger amplamente adotada:
Para quem procura um arquivo openapi.json
para gerar clientes para consumir a API do artigo, o URI /openapi.json
pode ser usado:
https://powerful-bayou-23686-2d5be7cf118b.herokuapp.com/openapi.json
Para meu exemplo, a especificação OpenAPI v3 baseada em JSON aparece conforme mostrado abaixo:
{ "openapi": "3.1.0", "info": { "title": "FastAPI", "version": "0.1.0" }, "paths": { "/articles": { "get": { "summary": "Get Articles", "operationId": "get_articles_articles_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { } } } } } }, "post": { "summary": "Create Article", "operationId": "create_article_articles_post", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Article" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/articles/{article_id}": { "get": { "summary": "Get Article", "operationId": "get_article_articles__article_id__get", "parameters": [ { "name": "article_id", "in": "path", "required": true, "schema": { "type": "integer", "title": "Article Id" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } } }, "components": { "schemas": { "Article": { "properties": { "id": { "type": "integer", "title": "Id" }, "title": { "type": "string", "title": "Title" }, "url": { "type": "string", "title": "Url" }, "year": { "type": "integer", "title": "Year" } }, "type": "object", "required": [ "id", "title", "url", "year" ], "title": "Article" }, "HTTPValidationError": { "properties": { "detail": { "items": { "$ref": "#/components/schemas/ValidationError" }, "type": "array", "title": "Detail" } }, "type": "object", "title": "HTTPValidationError" }, "ValidationError": { "properties": { "loc": { "items": { "anyOf": [ { "type": "string" }, { "type": "integer" } ] }, "type": "array", "title": "Location" }, "msg": { "type": "string", "title": "Message" }, "type": { "type": "string", "title": "Error Type" } }, "type": "object", "required": [ "loc", "msg", "type" ], "title": "ValidationError" } } } }
Como resultado, a especificação a seguir pode ser usada para gerar clientes em diversas linguagens diferentes por meio do OpenAPI Generator .
No início deste artigo, eu estava pronto para lutar e enfrentar qualquer pessoa que não estivesse interessada em usar uma abordagem API First. O que aprendi com este exercício é que um produto como FastAPI pode ajudar a definir e produzir um microsserviço RESTful funcional rapidamente, ao mesmo tempo que inclui uma especificação OpenAPI v3 totalmente consumível… automaticamente.
Acontece que o FastAPI permite que as equipes permaneçam focadas em suas metas e objetivos, aproveitando uma estrutura que produz um contrato padronizado no qual outros podem confiar. Como resultado, surgiu outro caminho para aderir à minha declaração de missão pessoal.
Ao longo do caminho, usei o Heroku pela primeira vez para implantar um serviço baseado em Python. Isso acabou exigindo pouco esforço de minha parte, além de revisar alguma documentação bem escrita. Portanto, outro bônus de declaração de missão também precisa ser mencionado para a plataforma Heroku.
Se você estiver interessado no código-fonte deste artigo, poderá encontrá-lo no GitLab .
Tenha um ótimo dia!