A Exchange API fornece serviços de conversão de moedas para o domínio store. Ela permite consultar a taxa de câmbio entre duas moedas (from_curr → to_curr), aplicando automaticamente o spread configurado e vinculando a operação ao usuário autenticado.
Trusted layer e segurança
Toda requisição externa entra pelo gateway. As rotas /exchange/** são protegidas: é obrigatório enviar Authorization: Bearer <jwt>.
Visão geral
Service (exchange-service): Microserviço em FastAPI (Python) que consulta um provedor externo de câmbio (HTTP), aplica um spread configurável e retorna as cotações.
classDiagram
class ExchangeService {
+getExchange(from: String, to: String): QuoteOut
}
class QuoteOut {
-Double sell
-Double buy
-String date
-String idAccount
}
Estrutura da requisição
flowchart LR
subgraph api [Trusted Layer]
direction TB
gateway --> account
gateway --> auth
account --> db@{ shape: cyl, label: "Database" }
auth --> account
gateway e1@==> exchange:::red
gateway --> product
gateway --> order
product --> db
order --> db
order --> product
end
exchange e3@==> 3partyapi:::green@{label: "3rd-party API"}
internet e2@==>|request| gateway
e1@{ animate: true }
e2@{ animate: true }
e3@{ animate: true }
classDef red fill:#fcc
classDef green fill:#cfc
click product "#product-api" "Product API"
fromfastapiimportRequest,HTTPExceptionimportjwtfromapp.configimportsettingsasyncdefrequire_auth(request:Request)->dict:# 1) Preferir o header que o gateway já inseriuaccount_id=request.headers.get("id-account")ifaccount_id:return{"id-account":str(account_id)}# 2) Fallback: pegar do Bearer tokenauth=request.headers.get("Authorization","")ifnotauth.startswith("Bearer "):raiseHTTPException(status_code=401,detail="missing bearer token")token=auth.split()[1]try:claims=jwt.decode(token,settings.jwt_secret,algorithms=["HS512"],options={"verify_aud":False},)exceptjwt.InvalidTokenError:raiseHTTPException(status_code=401,detail="invalid token")account_id=claims.get("id-account")orclaims.get("id")orclaims.get("sub")ifnotaccount_id:raiseHTTPException(status_code=400,detail="missing account id")return{"id-account":str(account_id)}
frompydantic_settingsimportBaseSettingsclassSettings(BaseSettings):# porta do uvicorn (só para rodar local)port:int=8083# API de câmbio (sem chave por padrão)rates_base_url:str="https://api.exchangerate.host"# spread para calcular buy/sell (ex.: 0.02 = ±1% em cada lado)spread:float=0.02# verificação de JWT (opcional). Se não informar, só decodifica sem validar assinaturajwt_secret:str|None=None# HS256jwt_public_key:str|None=None# RS256/ES256 (PEM)jwt_algorithm:str|None=None# "HS256", "RS256", ...classConfig:env_prefix="EXCHANGE_"env_file=".env"settings=Settings()
frompydanticimportBaseModel,FieldclassQuoteOut(BaseModel):sell:floatbuy:floatdate:strid_account:str=Field(...,alias="id-account")classConfig:# garante que o FastAPI use o alias no JSON de saídapopulate_by_name=Trueorm_mode=True