Architecture
System Context
Container Diagram
System Overview
Weevil is structured around a classic event-driven backend. The API server publishes book creation events to Kafka; the worker consumes them, persists the book, and enqueues an enrichment request on a buffered channel for asynchronous processing.
Binary Breakdown
cmd/api โ API Server
Serves two ConnectRPC services over HTTP/2 (h2c):
- BookService โ CRUD for books.
CreateBookgenerates a deterministic UUID from ISBN or title (MD5-namespaced), publishes the entity to Kafka.GetBookalso returns enrichment metadata frombook_metadataif available. - SearchService โ stateless; proxies queries to the Google Books API directly. Returns candidate
Bookobjects without persisting.
Both services are wrapped with CORS middleware (all origins) to support browser access.
Dependency wiring:
psql.Conn โ psql.BookRepo โ book.BookService โ bookgrpc.BookService
โ psql.BookMetadataRepo โ bookgrpc.BookService.WithMetadataRepo
kafka.Conn โ kafka.Producer[*Book] โ bookgrpc.BookService
cmd/worker โ Background Worker
Runs two concurrent processes in one binary:
- BookConsumer โ subscribes to Kafka topic
weevil.v1.Book(consumer groupweevil-service-group). Persists the book, publishes amagpie.v1.Resourceevent, and enqueues an enrichment request on the Enricher’s buffered channel. - Enricher โ drains the channel in a dedicated goroutine, running the six-step enrichment pipeline for each book.
cmd/ui โ Browser SPA
go-app v9 WebAssembly SPA served on port 8000. Calls the API on port 9000 using ConnectRPC.
Routes:
/ โ HomePage
/books โ BookListPage
/books/create โ BookCreatePage
/books/<uuid> โ BookGetPage + enrichment metadata
/searchs/create โ SearchCreatePage
/searchs/<uuid> โ SearchGetPage
Library Packages
lib/weevil/enrichment โ Enrichment Pipeline
The Enricher receives EnrichBookRequest values via a buffered channel (default capacity: 100). Each request is processed synchronously in a single goroutine to avoid stampeding the external APIs. The pipeline mirrors the previous Temporal workflow structure:
| Step | Function | Notes |
|---|---|---|
| 1 | FetchGoogleBooksActivity | tries: GoogleID โ ISBN-13 โ ISBN-9 โ title |
| 2 | FetchOpenLibraryActivity | by ISBN-13 then ISBN-9; skipped if no ISBN |
| 3 | ArchiveGoogleResponseActivity | uploads raw JSON to books/<uuid>/google-books.json |
| 4 | ArchiveOpenLibraryResponseActivity | uploads to books/<uuid>/open-library.json |
| 5 | PersistBookMetadataActivity | upserts book_metadata table โ required |
| 6 | SubmitToMagpieActivity | TODO: not yet implemented |
Steps 1โ4 and 6 are non-fatal (logged and skipped on error). Step 5 is required; failure aborts the pipeline for that book. If the queue is full when Enqueue is called, the request is dropped with a warning log โ enrichment is best-effort.
lib/client/ โ External API Clients
googleโ Google Books REST client usinggo-resty; supports search by title/author/ISBN and full volume fetchgoodreadsโ Goodreads RSS feed parser usinggofeed; paginates and extracts ISBN, author, Goodreads IDopenlibraryโ Open Library Books API client usingnet/http; fetches by ISBN, extracts covers, subjects, publishers
lib/repo/psql โ PostgreSQL Repositories
Connโ wrapsdatabase/sql, runs goose migrations on startupBookRepoโ implements CRUD; maps protoBookfields to/from SQLBookMetadataRepoโ upserts enrichment data; performsON CONFLICT ... DO UPDATE
lib/storage โ Object Storage
BlobClient (alias MinioClient) wraps gocloud.dev/blob backed by an S3-compatible endpoint. Used by enrichment activities to archive raw API responses.
Event Flow: Book Creation
Environment Variables
API Server (cmd/api)
| Variable | Description |
|---|---|
DATABASE_URL | PostgreSQL connection string |
KAFKA_BROKERS | Kafka broker address |
GOOGLE_BOOKS_API_KEY | Optional; improves Google Books rate limits |
Worker (cmd/worker)
| Variable | Description |
|---|---|
DATABASE_URL | PostgreSQL connection string |
KAFKA_BROKERS | Kafka broker address |
MINIO_ENDPOINT | MinIO/S3 endpoint; archiving skipped if unset |
MINIO_ACCESS_KEY | MinIO access key |
MINIO_SECRET_KEY | MinIO secret key |
MINIO_BUCKET | Bucket name (default: weevil) |