Microservices-based weather alert system. Users register, create alert rules based on weather conditions, and receive email notifications when conditions are met.
graph LR
Client(["👤 Client"])
subgraph GW["NGINX :80"]
NGINX["auth_request\n+ X-User-ID"]
end
subgraph SVC["Microservices"]
direction TB
AUTH["auth-service :8001\nJWT · bcrypt · refresh"]
WEATHER["weather-service :8002\nOpenWeatherMap · cache"]
ALERT["alert-service :8003\nCRUD · worker"]
NOTIFY["notification-service :8004\nKafka · SMTP"]
end
subgraph DATA["Storage"]
direction TB
AUTH_DB[("auth-db")]
REDIS[("Redis")]
ALERT_DB[("alert-db")]
end
KAFKA[["Kafka"]]
OWM(["OpenWeatherMap"])
Email(["📧 Email"])
Client --> NGINX
NGINX --> AUTH
NGINX --> WEATHER
NGINX --> ALERT
AUTH --- AUTH_DB
WEATHER --- REDIS
WEATHER --- OWM
ALERT --- ALERT_DB
ALERT -->|"alert.triggered"| KAFKA
KAFKA --> NOTIFY
NOTIFY --> Email
sequenceDiagram
participant C as Client
participant N as NGINX
participant A as auth-service
participant S as weather/alert-service
C->>N: GET /api/weather/current [Bearer token]
N->>A: GET /api/auth/validate (internal)
alt valid token
A-->>N: 200 OK + X-User-ID
N->>S: GET /api/weather/current [X-User-ID]
S-->>C: 200 OK
else invalid token
A-->>N: 401 Unauthorized
N-->>C: 401 Unauthorized
end
flowchart LR
T(["⏱ every 15 min"])
--> Q["ListActive()\nactive=true\nlast_triggered > 1h ago"]
--> G["group by city"]
--> W["FetchCurrent(city)\nweather-service"]
--> CHK{"condition\nmet?"}
CHK -->|no| T
CHK -->|yes| K["Kafka\nalert.triggered"]
--> M["MarkTriggered()\nlast_triggered = NOW()"]
--> T
| Service | Port | DB | Responsibilities |
|---|---|---|---|
| auth-service | 8001 | PostgreSQL | Registration, login, JWT issue/validate, refresh token rotation |
| weather-service | 8002 | Redis (cache) | Fetch current weather and forecast from OpenWeatherMap |
| alert-service | 8003 | PostgreSQL | Alert CRUD, background condition checker, Kafka producer |
| notification-service | 8004 | — | Kafka consumer, SMTP email sender |
Full spec: docs/openapi.yaml
| Method | Path | Description |
|---|---|---|
POST |
/api/auth/register |
Register, returns token pair |
POST |
/api/auth/login |
Login, returns token pair |
POST |
/api/auth/refresh |
Rotate refresh token |
| Method | Path | Description |
|---|---|---|
GET |
/api/weather/current?city=London |
Current weather |
GET |
/api/weather/forecast?city=London |
5-day forecast |
POST |
/api/alerts |
Create alert rule |
GET |
/api/alerts |
List your alerts |
GET |
/api/alerts/{id} |
Get alert by ID |
PUT |
/api/alerts/{id} |
Update alert (partial) |
DELETE |
/api/alerts/{id} |
Delete alert |
| Type | Description | Threshold |
|---|---|---|
temp_above |
Temperature exceeds threshold | °C |
temp_below |
Temperature drops below threshold | °C |
wind_above |
Wind speed exceeds threshold | m/s |
rain |
Condition is Rain or Drizzle | ignored |
snow |
Condition is Snow | ignored |
All errors follow RFC 9457 (application/problem+json):
{
"type": "about:blank",
"title": "Not Found",
"status": 404,
"detail": "Alert not found.",
"instance": "/api/alerts/873cd8e5-c2b8-417d-93db-7b4d42d90b33"
}# 1. Copy and fill environment file
cp infrastructure/.env.example .env
# Set: OPENWEATHER_API_KEY, JWT_SECRET, SMTP_* credentials
# 2. Start all services
cd infrastructure && docker-compose up -d --build
# 3. Run end-to-end tests
./scripts/test.shKubernetes manifests are stored in k8s/. They run the same application in the weather-app namespace: frontend, API gateway, Go services, PostgreSQL databases with PVCs, Redis, and Kafka.
For minikube with local images:
# Start minikube and build service images inside its Docker daemon
minikube start
eval $(minikube docker-env)
docker build -t weather-api-gateway:local ./api-gateway
docker build -t weather-frontend:local ./frontend
docker build -t weather-auth-service:local ./services/auth-service
docker build -t weather-weather-service:local ./services/weather-service
docker build -t weather-alert-service:local ./services/alert-service
docker build -t weather-notification-service:local ./services/notification-service
# Create namespace and secrets from the root .env file
kubectl apply -f k8s/namespace.yaml
kubectl create secret generic weather-secrets --from-env-file=.env -n weather-app
# Deploy everything
kubectl apply -f k8s/
kubectl get pods -n weather-app
kubectl get svc -n weather-appThe public entry point is api-gateway. For local browser access:
kubectl port-forward -n weather-app svc/api-gateway 8080:80Then open http://localhost:8080. The frontend service itself is ClusterIP, so direct localhost:3000 access is only for temporary debugging and bypasses the gateway.
Useful checks:
kubectl get pods -n weather-app -l app=weather-service
kubectl get endpoints weather-service -n weather-app
kubectl logs -n weather-app -l app=api-gateway
kubectl logs -n weather-app -l app=auth-serviceTo stop the Kubernetes stack:
kubectl delete namespace weather-app
minikube stop| Variable | Service | Default | Description |
|---|---|---|---|
SERVER_ADDR |
auth | :8001 |
Listen address |
WEATHER_SERVICE_ADDR |
weather | :8002 |
Listen address |
ALERT_SERVICE_ADDR |
alert | :8003 |
Listen address |
GRACEFUL_SHUTDOWN_TIMEOUT |
all | 10s |
Graceful shutdown timeout |
JWT_SECRET |
auth | — | Required. Shared with no other service (NGINX validates via auth-service) |
JWT_ACCESS_TTL |
auth | 1h |
Access token lifetime |
BCRYPT_COST |
auth | 12 |
bcrypt cost factor |
USER_DB_URL |
auth | — | PostgreSQL DSN for users |
TOKEN_DB_URL |
auth | — | PostgreSQL DSN for refresh tokens |
OPENWEATHER_API_KEY |
weather | — | Required |
REDIS_URL |
weather | redis:6379 |
Redis address (host:port) |
DB_URL |
alert | — | PostgreSQL DSN |
WEATHER_SERVICE_URL |
alert | http://weather-service:8002 |
Internal URL to weather-service |
KAFKA_BROKER |
alert | kafka:9092 |
Kafka broker address |
ALERT_CHECK_INTERVAL |
alert | 15m |
How often the checker runs |
SMTP_HOST |
notification | — | SMTP server hostname |
SMTP_PORT |
notification | 587 |
587 = STARTTLS, 465 = implicit TLS |
SMTP_USERNAME |
notification | — | SMTP login |
SMTP_PASSWORD |
notification | — | SMTP password |
SMTP_FROM |
notification | — | From address |
SMTP_USE_TLS |
notification | false |
Set true for port 465 |
| Category | Technology |
|---|---|
| Language | Go 1.26, standard net/http |
| API Gateway | NGINX (reverse proxy, JWT validation via auth_request) |
| Auth | JWT (HS256) + bcrypt + opaque refresh tokens |
| Databases | PostgreSQL (auth, alert), Redis (weather cache) |
| Messaging | Apache Kafka |
| Notifications | SMTP email |
| Containerization | Docker, Docker Compose |
| Monitoring | Prometheus (/metrics on each service) |
| External API | OpenWeatherMap |
| Error format | RFC 9457 Problem Details |
Weather-Alert-Service/
├── api-gateway/
│ ├── nginx.conf
│ └── conf.d/api-routes.conf
├── services/
│ ├── auth-service/
│ │ ├── cmd/main.go
│ │ ├── config/config.go
│ │ └── internal/
│ │ ├── api/handlers/ (auth, validate, health, response)
│ │ ├── api/middleware/ (logger, auth, problem)
│ │ ├── domain/ (models, errors)
│ │ ├── repository/ (postgres, migrations)
│ │ ├── service/
│ │ └── utils/ (jwt, bcrypt)
│ ├── weather-service/
│ │ ├── cmd/main.go
│ │ └── internal/
│ │ ├── api/handlers/
│ │ ├── api/middleware/
│ │ ├── cache/redis/
│ │ ├── clients/openweather/
│ │ └── domain/
│ ├── alert-service/
│ │ ├── cmd/main.go
│ │ └── internal/
│ │ ├── api/handlers/ (CRUD)
│ │ ├── api/middleware/ (logger, user_id, problem)
│ │ ├── clients/weather/ (internal HTTP client — worker only)
│ │ ├── domain/
│ │ ├── kafka/ (producer)
│ │ ├── repository/ (postgres, migrations)
│ │ ├── service/
│ │ └── worker/ (background checker)
│ └── notification-service/
│ ├── cmd/main.go
│ └── internal/
│ ├── client/smtp/
│ ├── consumer/kafka/
│ ├── domain/
│ └── service/
├── docs/
│ └── openapi.yaml
├── infrastructure/
│ ├── docker-compose.yml
│ └── .env.example
├── k8s/
│ ├── namespace.yaml
│ ├── api-gateway.yaml
│ ├── frontend.yaml
│ ├── auth-service.yaml
│ ├── weather-service.yaml
│ ├── alert-service.yaml
│ ├── notification-service.yaml
│ ├── auth-db.yaml
│ ├── alert-db.yaml
│ ├── redis.yaml
│ └── kafka.yaml
├── monitoring/
│ └── prometheus/prometheus.yml
└── scripts/
├── init-kafka-topics.sh
└── test.sh
# End-to-end (requires running stack)
./scripts/test.sh
# Individual service build check
cd services/auth-service && go build ./...
cd services/weather-service && go build ./...
cd services/alert-service && go build ./...
cd services/notification-service && go build ./...