Systeme d'alarme IoT evenementiel : hexagonal, CQRS, Kafka
Backend d'un systeme de video-surveillance dont le capteur est un iPhone recycle. Le probleme est simple, la structure est le sujet : - domain/ sans aucun import de framework (verifie par 7 regles ArchUnit qui tournent aussi pendant le docker build) - CQRS strict : write et read ne partagent ni modele ni port - AlarmDecision en sealed interface -> switch exhaustif verifie a la compilation, plutot qu'une chaine de if - Kafka en KRaft (Zookeeper est supprime depuis Kafka 4.0), cle de partition = deviceId pour garantir l'ordre par capteur - ErrorHandlingDeserializer + DLQ : un message malforme ne bloque pas le flux des evenements suivants - notification WebSocket differee apres commit, pour ne jamais afficher une intrusion qui partirait en rollback 31 tests au vert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
63ead8a966
81 changed files with 5051 additions and 0 deletions
138
docker-compose.yml
Normal file
138
docker-compose.yml
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
# =============================================================================
|
||||
# iot-alarm-system
|
||||
#
|
||||
# Deux reseaux :
|
||||
# - "default" : prive, Kafka et PostgreSQL n'y sont joignables que par l'app.
|
||||
# Aucun port n'est publie sur l'hote - le broker n'est pas expose a Internet.
|
||||
# - "edge" : reseau externe partage, ou le reverse proxy Caddy du serveur
|
||||
# vient chercher l'application pour la servir en HTTPS.
|
||||
#
|
||||
# Note sur Zookeeper : absent volontairement. Kafka tourne en mode KRaft, son
|
||||
# quorum interne. Zookeeper est deprecie depuis Kafka 3.5 et supprime en 4.0 ;
|
||||
# le garder ajouterait un conteneur, ~400 Mo de RAM et une piece a maintenir.
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Bus d'evenements - Kafka en KRaft mono-noeud
|
||||
# ---------------------------------------------------------------------------
|
||||
kafka:
|
||||
image: apache/kafka:3.9.1
|
||||
container_name: iot-alarm-kafka
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
CLUSTER_ID: "iot-alarm-cluster-0001"
|
||||
KAFKA_NODE_ID: 1
|
||||
KAFKA_PROCESS_ROLES: broker,controller
|
||||
KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
|
||||
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
|
||||
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
|
||||
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
|
||||
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
|
||||
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
|
||||
# Mono-noeud : aucune replication possible, on l'assume explicitement.
|
||||
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
|
||||
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
|
||||
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
|
||||
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
|
||||
KAFKA_LOG_RETENTION_HOURS: 72
|
||||
KAFKA_LOG_DIRS: /var/lib/kafka/data
|
||||
KAFKA_HEAP_OPTS: "-Xmx400m -Xms256m"
|
||||
volumes:
|
||||
- kafka_data:/var/lib/kafka/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "/opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server localhost:9092 >/dev/null 2>&1"]
|
||||
interval: 15s
|
||||
timeout: 10s
|
||||
retries: 12
|
||||
start_period: 40s
|
||||
mem_limit: 800m
|
||||
networks: [default]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Persistance - write model et read model
|
||||
# ---------------------------------------------------------------------------
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: iot-alarm-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: iotalarm
|
||||
POSTGRES_USER: iotalarm
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD est obligatoire, voir .env.example}
|
||||
# Le checksum des pages detecte une corruption disque silencieuse.
|
||||
POSTGRES_INITDB_ARGS: "--data-checksums"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U iotalarm -d iotalarm"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 15s
|
||||
mem_limit: 256m
|
||||
networks: [default]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Application Spring Boot
|
||||
# ---------------------------------------------------------------------------
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: iot-alarm-system:1.0.0
|
||||
container_name: iot-alarm-app
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
kafka:
|
||||
condition: service_healthy
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
SPRING_PROFILES_ACTIVE: prod
|
||||
KAFKA_BOOTSTRAP_SERVERS: kafka:9092
|
||||
DB_URL: jdbc:postgresql://postgres:5432/iotalarm
|
||||
DB_USERNAME: iotalarm
|
||||
DB_PASSWORD: ${DB_PASSWORD}
|
||||
SIMULATOR_ENABLED: ${SIMULATOR_ENABLED:-true}
|
||||
SIMULATOR_INTERVAL: ${SIMULATOR_INTERVAL:-8s}
|
||||
ALARM_EXIT_DELAY: ${ALARM_EXIT_DELAY:-30s}
|
||||
ALARM_MIN_CONFIDENCE: ${ALARM_MIN_CONFIDENCE:-60}
|
||||
LOG_LEVEL: ${LOG_LEVEL:-INFO}
|
||||
mem_limit: 512m
|
||||
# Joint les deux reseaux : "default" pour parler a Kafka et PostgreSQL,
|
||||
# "edge" pour etre joignable par le reverse proxy.
|
||||
networks: [default, edge]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Nginx - profil "standalone" uniquement
|
||||
#
|
||||
# Sur le serveur de production, le frontal est le Caddy deja en place (il
|
||||
# detient 80/443 et sert cinq autres domaines) : voir deploy/Caddyfile.snippet.
|
||||
# Ce Nginx sert au cas "je clone le depot et je lance tout sur ma machine" :
|
||||
# docker compose --profile standalone up -d -> http://localhost:8081
|
||||
# ---------------------------------------------------------------------------
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
container_name: iot-alarm-nginx
|
||||
profiles: [standalone]
|
||||
restart: unless-stopped
|
||||
depends_on: [app]
|
||||
ports:
|
||||
- "8081:80"
|
||||
volumes:
|
||||
- ./deploy/nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
mem_limit: 64m
|
||||
networks: [default]
|
||||
|
||||
volumes:
|
||||
kafka_data:
|
||||
postgres_data:
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: iot-alarm-internal
|
||||
edge:
|
||||
external: true
|
||||
name: edge
|
||||
Loading…
Add table
Add a link
Reference in a new issue