Shoppingcart

A walkthrough of an API on humus: a cart service for shopping carts on openapi/v3, behind JWT bearer tokens, that prices each item from a catalog service of its own over mTLS, both on PostgreSQL. These pages build it step by step.

The shoppingcart is two services, each an openapi/v3 API served by httpserver:

  • the cart keeps shopping carts and their items, each cart its owner’s;
  • the catalog keeps the products for sale and their prices.

Adding an item to a cart crosses both:

  1. the client sends POST /carts/{cartId}/items to the cart, with a bearer token whose scopes hold carts:write;
  2. the cart verifies the token against the identity provider’s key set;
  3. the cart asks the catalog for the product’s price, over mTLS;
  4. the cart stores the item, with that price, in its own database.
client ──HTTPS + bearer──▶ cart (httpserver, openapi/v3) ──HTTPS, mTLS──▶ catalog (httpserver, openapi/v3)
                               │                                           │
                               ▼                                           ▼
                          PostgreSQL: carts                         PostgreSQL: catalog

Each service owns its data, and neither reads the other’s.

The files it ends with

Its code is a module of its own, at example/http/shoppingcart:

example/http/shoppingcart/
├── main.go                 the cart: httpserver.Main(app.Init, ...)
├── app/                    the cart's Init, settings, trust, readiness, BuildTelemetry and Faults
├── endpoint/               the cart's API: its operations, on a Store and a Catalog
├── database/               the Store on PostgreSQL, its injection point, migrations/
├── catalogclient/          the cart's client of the catalog: the request, and what each answer means
├── catalog/
│   ├── main.go             the catalog: httpserver.Main(app.Init, ...)
│   ├── app/                its Init, settings, trust, probes, BuildTelemetry and Faults
│   ├── endpoint/           its API: its operations on products, and who may call each
│   └── database/           its Store on PostgreSQL, its injection point, migrations/
├── migrate/                the migrations' init container: its Init and its job
├── cmd/migrate/            humus.Main(migrate.Init, ...)
├── cmd/carttoken/          a CLI that makes the key set and signs tokens, to call the cart by hand
├── internal/certfile/      certificates served and presented from their files, read again when renewed
├── internal/latch/         a monitor that is healthy for good once its check first succeeds
├── internal/postgrestest/  PostgreSQL in a container, for the tests
├── cases/                  the test cases the in-process tests, the container tests and kind share
├── functional/             the cases against kind
├── deploy/                 the base, the kind overlay and up.sh, the alerts and the dashboard
└── Dockerfile              one image, with the cart, the catalog and migrate

These pages build the cart’s API, who may call it, and the schema, then the catalog and the cart’s calls to it, then each service’s Init and main. Then they test both services, deploy them, and observe them, and end with the checklist they meet.


The cart's API

The cart’s endpoint package: its bodies, the Store and the Catalog it is served on, its paths and parameters, its six operations on openapi/v3, the answers each error becomes, the API that serves them, and the document.

Who may do what

The cart’s bearer tokens: the claims it reads, verifying JWTs against the identity provider’s key set, a token with no subject, a key set that is down, the carts:write scope, and keeping each cart to its owner.

The schema

Each service’s schema as versioned migrations, migrate, a humus job that applies them under tern’s lock and grants the service’s role read and write, the init container that runs it, the roles, and the cart’s Store on PostgreSQL with its injection point.

The catalog

The catalog: its two operations on openapi/v3, a read that needs a client certificate and a write that needs catalog:write, the TLS that asks for client certificates, its Store with its injection point, the scenarios it trusts, and its probes.

Calling the catalog

The cart’s calls to the catalog: an httpclient client over mTLS with its default retries and breaker, ForwardScenario, the catalogclient package that turns each of the catalog’s answers into the cart’s, and the readiness call through the same client.

Init

Each service’s Init: its settings, sourced and checked, and every variable it reads; its secrets and certificates, read from files and read again when renewed; the pool through the Host; readiness; TrustScenario; BuildTelemetry and Faults; and main.

Testing

How the cart and the catalog are tested: each service’s Init and the catalog’s client as units, on fakes and the humustest Host; the Stores against PostgreSQL; the shared cases, their Target, Parallel and Alone, run inside synctest, against containers and on kind; and the failure injection cases.

Deploying

How the cart and the catalog are deployed: their image, the kustomization, the Deployments with their migrations, the disruption budgets and the drain, and the kind overlay. Then running them on kind with up.sh, and calling the cart with a token from carttoken.

Observing

What the running cart and catalog tell you in Grafana on kind: an add’s trace across both services in Tempo, their log records in Loki, humus’s metrics and the Deployments’ in Mimir, the alert rules in deploy/alerts.yaml, and the dashboard in deploy/dashboard.json.

Production-ready

The checklist the cart and the catalog meet, and how they meet each item, with the page that builds it. Check your own API against it.