Files
truelayer-interview/README.md

136 lines
4.1 KiB
Markdown

# Pokedex API
REST API for the TrueLayer Software Engineering Challenge. It returns basic Pokémon information from PokéAPI and, on request, a fun translated description from FunTranslations.
## Requirements
You can run it either with Docker or with a local Rust toolchain. Common commands are available through `make`; run `make help` for the full list.
### Option A: Docker
Install Docker Desktop or an equivalent Docker runtime, then run:
```bash
make docker-build
make docker-run
```
### Option B: local Rust
Install Rust with rustup:
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
rustup default stable
```
Then run the service:
```bash
make run
```
The API listens on `0.0.0.0:5000` by default.
## CLI helper
With the API running in another terminal, call it through the small helper binary:
```bash
make cli-health
make cli-pokemon
make cli-translated
make cli-pokemon POKEMON=pikachu BASE_URL=http://localhost:5000
```
## Endpoints
```bash
make health
make pokemon
make translated
make metrics
```
Example response:
```json
{
"name": "mewtwo",
"description": "It was created by a scientist after years of horrific gene splicing and DNA engineering experiments.",
"habitat": "rare",
"isLegendary": true
}
```
## Translation rules
`/pokemon/translated/{name}` applies Yoda when the Pokémon is legendary or its habitat is `cave`; otherwise it applies Shakespeare. If translation fails or returns an empty result, the API falls back to the standard PokéAPI description.
## Architecture
```mermaid
flowchart LR
CLI[CLI helper] --> HTTP[HTTP layer]
HTTP --> APP[Application service]
APP --> DOMAIN[Domain]
APP --> CLIENTS[External clients]
HTTP --> OTEL[Telemetry]
APP --> OTEL
```
See `docs/architecture.md` for the design notes.
## Configuration
| Variable | Default | Description |
| --- | --- | --- |
| `BIND_ADDR` | `0.0.0.0:5000` | HTTP bind address |
| `POKEAPI_BASE_URL` | `https://pokeapi.co/api/v2` | PokéAPI base URL |
| `FUN_TRANSLATIONS_BASE_URL` | `https://api.funtranslations.mercxry.me/v1` | FunTranslations base URL |
| `REQUEST_TIMEOUT_SECONDS` | `5` | Upstream HTTP timeout |
| `RATE_LIMIT_PER_SECOND` | `20` | Global token refill rate |
| `RATE_LIMIT_BURST` | `40` | Global burst capacity |
| `OTEL_SERVICE_NAME` | `pokedex-api` | OpenTelemetry service name |
| `RUST_LOG` | `pokedex_api=info,tower_http=info` | JSON tracing log filter |
## Instrumentation
The service includes:
- structured JSON logging via `tracing`
- request spans via `tower-http`
- `x-request-id` generation and propagation
- OpenTelemetry metrics for HTTP requests, HTTP errors, upstream calls, upstream errors and latency histograms
- `/metrics` in Prometheus text format backed by OpenTelemetry instruments
- simple global token-bucket rate limiting returning `429`
## Development checks
```bash
make check
```
The crate denies unsafe code and enables strict Clippy groups: `all`, `pedantic`, `nursery` and `cargo`, with only dependency-version noise allowed.
## External contract checks
Normal tests mock third-party APIs. A scheduled GitHub Actions workflow runs ignored contract tests nightly against the real PokéAPI and FunTranslations APIs to catch provider contract drift early:
```bash
make contract-test
```
## Docker multi-arch build
```bash
make docker-buildx
```
The Dockerfile uses `cargo-chef` and BuildKit cache mounts for dependency and target directory caching.
## Production notes
For a production API I would add per-client or per-token rate limiting instead of one global bucket, retries with bounded exponential backoff, circuit breakers for upstream failures, and a bounded LRU/TTL cache for PokéAPI data. Species content changes rarely, so caching successful responses would avoid unnecessary random upstream hits, reduce latency, and make transient provider failures less visible to users. I would also add stronger health checks that distinguish readiness from liveness, dashboards and alerts from the OTEL metrics, and a dedicated OTEL collector pipeline. I would keep `/metrics` private, add dependency/container scanning, define SLOs, and add contract tests against recorded upstream fixtures.