Architecture

System Overview

Gila Monster is a Go monorepo that compiles to two binaries sharing a common library tree. The central concern is managing RPG character data and character images through a structured, proto-defined API.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Clients                                                         โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚  โ”‚   CLI (cobra)    โ”‚                  โ”‚  Harpy Eagle service โ”‚ โ”‚
โ”‚  โ”‚  (gRPC client)   โ”‚                  โ”‚  (completion events) โ”‚ โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚           โ”‚ gRPC                                   โ”‚ Kafka       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
            โ–ผ                                          โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  API Server  (cmd/api/main.go)                                  โ”‚
โ”‚                                                                 โ”‚
โ”‚  CharacterService (grpc)  โ”‚  CharacterImageService (grpc)       โ”‚
โ”‚  (ConnectRPC handler)     โ”‚  (ConnectRPC handler)               โ”‚
โ”‚           โ”‚                           โ”‚                         โ”‚
โ”‚  character.characterService  characterimage.charImgService       โ”‚
โ”‚           โ”‚                           โ”‚                         โ”‚
โ”‚  CharacterRepo            โ”‚  CharacterImageRepo                 โ”‚
โ”‚           โ”‚                           โ”‚                         โ”‚
โ”‚  PostgreSQL               โ”‚  PostgreSQL + MinIO (uploads)       โ”‚
โ”‚                                                                 โ”‚
โ”‚  CompletionConsumer โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Kafka     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

There is no browser client in this repo today. A go-app (WASM SPA) dependency remains in go.mod, but the scaffolded cmd/ui and lib/ui directories were deliberately deleted (see commit 8cb4f35, “remove scaffolded UI dirs”); the dependency is currently unused dead weight rather than an active client.

Binary Separation

cmd/api/main.go โ€” API Server

The primary production binary. On startup it:

  1. Opens a PostgreSQL connection (DATABASE_URL, default postgres://postgres:postgres@localhost:5432/gilamonster?sslmode=disable) and runs embedded goose migrations automatically.
  2. Constructs CharacterRepo and CharacterImageRepo.
  3. Wraps each repo in a domain service.
  4. If KAFKA_BROKERS is set, starts a CompletionConsumer goroutine that subscribes to the image-completion topic (IMAGE_COMPLETION_TOPIC, default harpyeagle.texttoimages.completed).
  5. Wraps domain services in ConnectRPC handlers and registers them on an http.ServeMux.
  6. If S3_ENDPOINT is set, constructs a MinIO client and registers an upload.Handler at POST /upload/character-image.
  7. Registers /health and /info endpoints, then serves all traffic over HTTP/2 cleartext (h2c) with OpenTelemetry HTTP instrumentation and per-path CORS middleware. Listens on API_ADDR (default :8080).

main.go + cmd/ โ€” CLI Client

A Cobra CLI that opens a native gRPC connection to the API server (API_ADDR env var, default localhost:50051 โ€” note this differs from the API server’s own default listen address of :8080, so API_ADDR typically needs to be set on one side or the other to match) and exposes list, get, create, update, and delete subcommands for both resources.

Layer Breakdown

Transport: ConnectRPC + gRPC

The API server only ever registers Connect-generated HTTP handlers (servicesconnect.NewCharacterServiceHandler / NewCharacterImageServiceHandler) on a single http.ServeMux โ€” there is no separate grpc.Server. Connect’s generated handlers natively speak the Connect protocol, gRPC, and gRPC-Web on the same endpoint, which is what lets the CLI talk to it as a plain gRPC client. buf.gen.yaml drives code generation with three plugins: protocolbuffers/go, connectrpc/go, and go-grpc.

Domain Services

Each resource domain follows the same three-file pattern:

FileRole
interface.goDefines service and repository interfaces
service.goConcrete implementation; delegates storage to the injected repository
grpc/service.goConnectRPC handler; adapts Connect request/response to domain service calls

Additionally:

  • consumer.go โ€” Generic Kafka consumer for importing proto-encoded messages (not wired in current API server startup).
  • completion_consumer.go (characterimage only) โ€” Kafka consumer for JSON-encoded Harpy Eagle completion events; looks up the pending CharacterImage by harpy_eagle_uuid and updates its url, status, is_active, and category fields.

Repository Layer (lib/repo/)

Built on database/sql + lib/pq with Masterminds/squirrel as a query builder.

  • conn.go โ€” Opens the DB and runs goose.Up with embedded migrations before returning.
  • character.go โ€” CharacterRepo; serializes []*Action to/from JSON (JSONB) for the actions column.
  • characterimage.go โ€” CharacterImageRepo; adds GetByHarpyEagleUUID and ListByCharacter beyond standard CRUD.

Migrations (lib/repo/migrations/), in order: create characters, create characterimages, add actions JSONB to characters, add category to characterimages, and most recently add sprite_uuid (00000005_character_sprite.up.sql) โ€” a free-text reference to a Harpy Eagle Sprite.uuid used for maze/battle rendering; empty means no sprite is selected and the character falls back to current text/no-render behavior.

Note: schemas/gilamonster/v1/characterimage.proto as checked into this repo does not declare a category field, but the generated lib/schemas/gilamonster/v1/characterimage.pb.go does (field 11) and application code (completion_consumer.go, consumer.go) actively reads/writes CharacterImage.Category. The checked-in .proto source and the generated code are out of sync for this one field โ€” worth reconciling next time schemas are regenerated.

archaea Framework

Uses github.com/holmes89/archaea/base for generic interface contracts. The proto service request types implement base.ListRequest, base.GetRequest[T], base.CreateRequest[T], and base.UpdateRequest[T] directly, allowing the gRPC handler to pass proto messages straight into the domain service without intermediate translation structs.

Upload Handler (lib/gilamonster/upload/handler.go)

Accepts multipart/form-data POST requests with a file field. Assigns a UUID-based object key under character-images/, uploads to MinIO using minio-go, and returns {"url": "<public_url>/<key>"}. Supports JPEG, PNG, GIF, and WebP.

Kafka / Redpanda Integration

The only actively wired consumer is the CompletionConsumer in characterimage/completion_consumer.go. It listens for JSON events from Harpy Eagle:

{
  "harpy_eagle_uuid": "<uuid>",
  "correlation_id": "<character_uuid>",
  "url": "<final image url>",
  "category": "<category string>"
}

On receipt it looks up the CharacterImage by harpy_eagle_uuid, sets status = "completed", is_active = true, url, and category, then calls Update.

CI/CD

GitHub Actions (.github/workflows/docker-publish.yml) runs on every push and PR to main: go vet, golangci-lint, unit tests (go test -race ./...), and integration tests (go test -race -tags=integration ./...). On pushes to main (not on PRs) it then builds and pushes the API Docker image to ghcr.io/holmes89/gila-monster. The image is a minimal scratch-based image containing only the api binary, embedded migrations, and TLS CA certificates, and exposes port 8080.