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:
- Opens a PostgreSQL connection (
DATABASE_URL, defaultpostgres://postgres:postgres@localhost:5432/gilamonster?sslmode=disable) and runs embedded goose migrations automatically. - Constructs
CharacterRepoandCharacterImageRepo. - Wraps each repo in a domain service.
- If
KAFKA_BROKERSis set, starts aCompletionConsumergoroutine that subscribes to the image-completion topic (IMAGE_COMPLETION_TOPIC, defaultharpyeagle.texttoimages.completed). - Wraps domain services in ConnectRPC handlers and registers them on an
http.ServeMux. - If
S3_ENDPOINTis set, constructs a MinIO client and registers anupload.HandleratPOST /upload/character-image. - Registers
/healthand/infoendpoints, then serves all traffic over HTTP/2 cleartext (h2c) with OpenTelemetry HTTP instrumentation and per-path CORS middleware. Listens onAPI_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:
| File | Role |
|---|---|
interface.go | Defines service and repository interfaces |
service.go | Concrete implementation; delegates storage to the injected repository |
grpc/service.go | ConnectRPC 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 pendingCharacterImagebyharpy_eagle_uuidand updates itsurl,status,is_active, andcategoryfields.
Repository Layer (lib/repo/)
Built on database/sql + lib/pq with Masterminds/squirrel as a query builder.
conn.goโ Opens the DB and runsgoose.Upwith embedded migrations before returning.character.goโCharacterRepo; serializes[]*Actionto/from JSON (JSONB) for theactionscolumn.characterimage.goโCharacterImageRepo; addsGetByHarpyEagleUUIDandListByCharacterbeyond 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.protoas checked into this repo does not declare acategoryfield, but the generatedlib/schemas/gilamonster/v1/characterimage.pb.godoes (field 11) and application code (completion_consumer.go,consumer.go) actively reads/writesCharacterImage.Category. The checked-in.protosource 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.