# vladimirkhmelev/xkcd architecture

> Architecture diagram of the GitHub repository vladimirkhmelev/xkcd, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-08-04). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/vladimirkhmelev/xkcd
- Repository: https://github.com/vladimirkhmelev/xkcd

## Overview

#### Purpose and execution surface
This is a Go microservice system for phrase search across XKCD comics. Docker Compose is the primary deployment boundary (`docker-compose.yaml`), starting five services plus infrastructure. Service entry points are `search-services/api/main.go`, `search-services/frontend/main.go`, `search-services/update/main.go`, `search-services/search/main.go`, and `search-services/words/main.go`.

#### Public HTTP edge and UI
`api` exposes the REST API, Swagger, authentication, and request controls; it translates HTTP calls into gRPC calls to domain services. JWT authentication, rate limiting, concurrency limiting, and metrics are HTTP middleware concerns at `search-services/api/adapters/rest/api.go` and `search-services/api/adapters/rest/middleware/`. User credentials are persisted through the API database adapter (`search-services/api/adapters/db/db.go`).
`frontend` is a separate server-rendered browser client for search, registration/login, and administration, implemented by `search-services/frontend/main.go` and `search-services/frontend/handlers/handlers.go`.

#### API application boundary
The API’s core declares service-facing models and interfaces, keeping transports and service clients replaceable: `search-services/api/core/models.go`, `search-services/api/core/ports.go`. Concrete gRPC client adapters route requests to search, update, and words services: `search-services/api/adapters/search/search.go`, `search-services/api/adapters/update/update.go`, and `search-services/api/adapters/words/words.go`. Shared protobuf contracts define this inter-service boundary in `search-services/proto/search/search.proto`, `search-services/proto/update/update.proto`, and `search-services/proto/words/words.proto`.

#### Update ingestion pipeline
The update service fetches comic data from the external XKCD API, stores comics in PostgreSQL, and publishes update events. Its core orchestration is `search-services/update/core/service.go`; external source and storage adapters are `search-services/update/adapters/xkcd/xkcd.go` and `search-services/update/adapters/db/storage.go`. Database schema lifecycle is local to this service via `search-services/update/adapters/db/migrations/`. The NATS publisher (`search-services/update/adapters/broker/broker.go`) is the asynchronous handoff that triggers search-index refreshes.

#### Search and indexing pipeline
The search service owns query execution and index lifecycle. `search-services/search/core/service.go` coordinates storage, tokenization, cache, and event-driven rebuilding. It receives gRPC requests through `search-services/search/adapters/grpc/server.go`, reads indexed/source data through `search-services/search/adapters/db/storage.go`, and consumes update notifications through `search-services/search/adapters/broker/broker.go` plus initialization logic in `search-services/search/adapters/initiator/initiator.go`. Query results are cached behind a replaceable cache port, with Redis and no-op implementations in `search-services/search/adapters/cache/redis.go` and `search-services/search/adapters/cache/noop.go`.

#### Text processing service
`words` is the shared phrase-normalization boundary used by search and update flows. It is a standalone gRPC service (`search-services/words/main.go`) whose implementation performs tokenization, stop-word handling, and stemming in `search-services/words/words/words.go`. This avoids duplicating index/query normalization rules across services.

#### Runtime infrastructure and observability
PostgreSQL stores users and comics; NATS carries update events; Redis holds search results with TTL. Compose also provisions VictoriaMetrics/Grafana, pgAdmin, and Jaeger. Prometheus-format scrape configuration and Grafana dashboard artifacts are `metrics/promscrape.yml` and `metrics/dashboard.json`. OpenTelemetry tracing is shared through `search-services/pkg/tracing/tracing.go`. Service-specific YAML configuration establishes runtime endpoints and settings, e.g. `search-services/api/config.yaml`, `search-services/search/config.yaml`, and `search-services/update/config.yaml`.

Main entry points: [`search-services/api/adapters/rest/api.go`](https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/api/adapters/rest/api.go) (API HTTP edge), [`search-services/search/core/service.go`](https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/search/core/service.go) (Search and index core), [`search-services/update/core/service.go`](https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/update/core/service.go) (Update orchestrator), [`search-services/api/adapters/search/search.go`](https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/api/adapters/search/search.go) (gRPC client adapters)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_clients["Clients"]
  node_browser(("Browser user<br/>client"))
  node_frontend["Frontend<br/>SSR web server<br/>[main.go]"]
end

subgraph group_api["API service"]
  node_api_rest["API HTTP edge<br/>REST and middleware<br/>[api.go]"]
  node_api_core{{"API core ports<br/>application boundary<br/>[ports.go]"}}
  node_api_grpc_clients["gRPC client adapters<br/>service clients<br/>[search.go]"]
  node_proto["Protobuf contracts<br/>gRPC schema<br/>[search.proto]"]
end

subgraph group_domain["Domain services"]
  node_update_core{{"Update orchestrator<br/>ingestion service<br/>[service.go]"}}
  node_xkcd_api(("XKCD API<br/>external source"))
  node_search_grpc["Search gRPC server<br/>transport adapter<br/>[server.go]"]
  node_search_core{{"Search and index core<br/>query and indexing service<br/>[service.go]"}}
  node_words["Words service<br/>normalization gRPC service<br/>[words.go]"]
end

subgraph group_infra["Runtime infrastructure"]
  node_postgres[("PostgreSQL<br/>users and comics store")]
  node_nats["NATS<br/>update event broker"]
  node_redis[("Redis<br/>search result cache<br/>[redis.go]")]
  node_compose["Docker Compose<br/>deployment boundary"]
end

subgraph group_observability["Observability"]
  node_tracing["OpenTelemetry tracing<br/>shared tracing<br/>[tracing.go]"]
  node_metrics["VictoriaMetrics and Grafana<br/>metrics and dashboards<br/>[promscrape.yml]"]
  node_jaeger["Jaeger<br/>trace backend"]
end

node_browser -->|"HTTP"| node_frontend
node_frontend -->|"API calls"| node_api_rest
node_api_rest -->|"invokes"| node_api_core
node_api_rest -->|"user credentials"| node_postgres
node_api_core -->|"ports"| node_api_grpc_clients
node_api_grpc_clients -.->|"uses"| node_proto
node_api_grpc_clients -->|"search gRPC"| node_search_grpc
node_api_grpc_clients -->|"update gRPC"| node_update_core
node_api_grpc_clients -->|"words gRPC"| node_words
node_xkcd_api -->|"comic data"| node_update_core
node_update_core -->|"normalizes text"| node_words
node_update_core -->|"stores comics"| node_postgres
node_update_core -->|"publishes updates"| node_nats
node_search_grpc -->|"requests"| node_search_core
node_nats -->|"update events"| node_search_core
node_search_core -->|"reads comic data"| node_postgres
node_search_core -->|"normalizes queries"| node_words
node_search_core -->|"caches results"| node_redis
node_api_rest -.->|"traces"| node_tracing
node_search_core -.->|"traces"| node_tracing
node_tracing -->|"exports spans"| node_jaeger
node_api_rest -.->|"metrics"| node_metrics
node_compose -.->|"runs"| node_api_rest
node_compose -.->|"runs"| node_update_core
node_compose -.->|"runs"| node_search_grpc
node_compose -.->|"runs"| node_words

click node_frontend "https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/frontend/main.go"
click node_api_rest "https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/api/adapters/rest/api.go"
click node_api_core "https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/api/core/ports.go"
click node_api_grpc_clients "https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/api/adapters/search/search.go"
click node_proto "https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/proto/search/search.proto"
click node_update_core "https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/update/core/service.go"
click node_search_grpc "https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/search/adapters/grpc/server.go"
click node_search_core "https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/search/core/service.go"
click node_words "https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/words/words/words.go"
click node_redis "https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/search/adapters/cache/redis.go"
click node_tracing "https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/pkg/tracing/tracing.go"
click node_metrics "https://github.com/vladimirkhmelev/xkcd/blob/main/metrics/promscrape.yml"
click node_compose "https://github.com/vladimirkhmelev/xkcd/blob/main/docker-compose.yaml"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_browser,node_frontend toneBlue
class node_api_rest,node_api_core,node_api_grpc_clients,node_proto toneAmber
class node_update_core,node_xkcd_api,node_search_grpc,node_search_core,node_words toneMint
class node_postgres,node_nats,node_redis,node_compose toneRose
class node_tracing,node_metrics,node_jaeger toneIndigo
```

## Components

### Clients

- **Browser user**: client
- [Frontend](https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/frontend/main.go): SSR web server · Search, account, and administration UI. · `search-services/frontend/main.go`

### API service

- [API HTTP edge](https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/api/adapters/rest/api.go): REST and middleware · Swagger, JWT, rate and concurrency controls. · `search-services/api/adapters/rest/api.go`
- [API core ports](https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/api/core/ports.go): application boundary · Service models and replaceable interfaces. · `search-services/api/core/ports.go`
- [gRPC client adapters](https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/api/adapters/search/search.go): service clients · Routes API operations to domain services. · `search-services/api/adapters/search/search.go`
- [Protobuf contracts](https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/proto/search/search.proto): gRPC schema · Shared search, update, and words service contracts. · `search-services/proto/search/search.proto`

### Domain services

- [Update orchestrator](https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/update/core/service.go): ingestion service · Fetches, stores, and publishes comic updates. · `search-services/update/core/service.go`
- **XKCD API**: external source
- [Search gRPC server](https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/search/adapters/grpc/server.go): transport adapter · `search-services/search/adapters/grpc/server.go`
- [Search and index core](https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/search/core/service.go): query and indexing service · Coordinates querying, normalization, caching, and rebuilds. · `search-services/search/core/service.go`
- [Words service](https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/words/words/words.go): normalization gRPC service · Shared tokenization, stop-word handling, and stemming. · `search-services/words/words/words.go`

### Runtime infrastructure

- **PostgreSQL**: users and comics store
- **NATS**: update event broker
- [Redis](https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/search/adapters/cache/redis.go): search result cache · TTL-backed results cache with a no-op fallback. · `search-services/search/adapters/cache/redis.go`
- [Docker Compose](https://github.com/vladimirkhmelev/xkcd/blob/main/docker-compose.yaml): deployment boundary · Starts services and local infrastructure. · `docker-compose.yaml`

### Observability

- [OpenTelemetry tracing](https://github.com/vladimirkhmelev/xkcd/blob/main/search-services/pkg/tracing/tracing.go): shared tracing · `search-services/pkg/tracing/tracing.go`
- [VictoriaMetrics and Grafana](https://github.com/vladimirkhmelev/xkcd/blob/main/metrics/promscrape.yml): metrics and dashboards · Scrapes Prometheus metrics and presents dashboards. · `metrics/promscrape.yml`
- **Jaeger**: trace backend

## Connections

- Browser user → Frontend: HTTP
- Frontend → API HTTP edge: API calls
- API HTTP edge → API core ports: invokes
- API HTTP edge → PostgreSQL: user credentials
- API core ports → gRPC client adapters: ports
- gRPC client adapters → Protobuf contracts: uses
- gRPC client adapters → Search gRPC server: search gRPC
- gRPC client adapters → Update orchestrator: update gRPC
- gRPC client adapters → Words service: words gRPC
- XKCD API → Update orchestrator: comic data
- Update orchestrator → Words service: normalizes text
- Update orchestrator → PostgreSQL: stores comics
- Update orchestrator → NATS: publishes updates
- Search gRPC server → Search and index core: requests
- NATS → Search and index core: update events · Triggers index refresh.
- Search and index core → PostgreSQL: reads comic data
- Search and index core → Words service: normalizes queries
- Search and index core → Redis: caches results
- API HTTP edge → OpenTelemetry tracing: traces
- Search and index core → OpenTelemetry tracing: traces
- OpenTelemetry tracing → Jaeger: exports spans
- API HTTP edge → VictoriaMetrics and Grafana: metrics
- Docker Compose → API HTTP edge: runs
- Docker Compose → Update orchestrator: runs
- Docker Compose → Search gRPC server: runs
- Docker Compose → Words service: runs
