Microservizi PHP 8.4, API REST JWT, real-time SSE, AI integrata, Docker su Hetzner — tutto progettato per scalabilità e sicurezza enterprise.
WMS Agile è costruito su un'architettura a microservizi indipendenti comunicanti via REST, coordinati da un API Gateway Nginx. Ogni servizio ha il suo database MySQL isolato.
Stack moderno, stabile e battle-tested. Nessuna dipendenza da framework pesanti o vendor lock-in.
| Componente | Tecnologia | Versione | Ruolo |
|---|---|---|---|
| API Gateway | Nginx | 1.27 | Reverse proxy, SSL passthrough, routing regex, gzip, SSE buffering |
| Microservizi PHP | PHP + Slim 4 + Apache 2.4 | 8.4 / 4.x | Business logic, REST API, auth middleware, CORS |
| Database primario | MySQL 8.0 InnoDB | 8.0 | 7 database separati (1 per servizio), utf8mb4, prepared statements |
| Cache | Redis | 7.x | Sessioni, rate limiting, cache query (predisposto) |
| Message broker | RabbitMQ | 3.13 | Event bus asincrono (predisposto per integrazione ERP/webhook) |
| AI engine | Anthropic Claude API | claude-sonnet-4-6 | Function calling, SSE streaming, tool execution WMS |
| Frontend runtime | Vanilla JS | ES2022 | SPA no-framework, IIFE modules, fetch API, EventSource |
Ogni microservizio è un container Docker autonomo con il suo database, dipendenze Composer e processo Apache dedicato. Comunicazione intra-servizi via HTTP interno (rete Docker).
services/{name}-ms/
├── Dockerfile
├── composer.json
├── env.production.example
├── public/
│ └── index.php # Routes Slim 4 + middleware
└── src/
└── Services/
└── {Name}Service.php # Business logic
Librerie condivise montate come volume read-only in ogni container:
shared/
├── auth-lib/ # AuthMiddleware, JwtHelper
├── database-lib/ # Database::getConnection()
└── event-bus-lib/ # RabbitMQ EventBus
Namespace: Wms\Shared\Auth\ — Wms\Shared\Database\
Ogni microservizio ha il suo database MySQL dedicato. L'isolamento previene query cross-service accidentali e permette backup/restore indipendenti.
| Database | Servizio Owner | Tabelle Principali |
|---|---|---|
wms_auth_db | auth-ms | users, tenants, roles, user_roles, sessions, api_keys |
wms_inventory_db | inventory-ms | products, stock_levels, lots, stock_movements, parcel_tracking, parcel_scan_events |
wms_warehouse_db | warehouse-ms | warehouses, zones, bins, missions, equipment, operators, alerts, companies, drivers, delivery_runs, delivery_manifest_items, non_conformities, ncr_events, stock_adjustments |
wms_inbound_db | inbound-ms | inbound_orders, inbound_order_items, quality_checks, putaway_tasks |
wms_outbound_db | outbound-ms | outbound_orders, order_items, picking_tasks, packing_tasks, waves |
wms_report_db | report-ms | daily_kpi_snapshots, reports |
wms_agile_db | meta/shared | cross-service references (opzionale) |
deleted_at TIMESTAMP NULL + filtro IS NULL// Successo
{
"success": true,
"data": { ... } | [ ... ],
"meta": { "total": 42, "page": 1 }
}
// Errore
{
"success": false,
"error": "Descrizione errore",
"code": 404
}
Tutte le route autenticate richiedono header Authorization: Bearer {JWT}. Le route pubbliche sono indicate con [PUBLIC].
{
"message": "Quanti ordini oggi?",
"session_id": "uuid",
"history": [...],
"stream": true
}
AI Tools disponibili: list_missions, get_dashboard_summary, list_products, create_mission, configure_bin, configure_zone, list_non_conformities, create_ncr, get_tracking_summary, list_delivery_runs
WMS Agile usa SSE (non WebSocket) per aggiornamenti server→client. Più semplice, compatibile con tutti i proxy HTTP/2, e nativo nel browser.
?token=JWT (EventSource non supporta headers)heartbeat, missions, alerts, equipment// nginx: proxy_buffering off
// PHP: header('X-Accel-Buffering: no')
// PHP: flush() dopo ogni evento
event: missions
data: {"count":3,"items":[...]}
event: alerts
data: {"critical":1,"high":2,"total":3}
Authorization: Bearer JWT// Client JS riceve:
data: {"type":"delta","text":"I KPI di "}
data: {"type":"delta","text":"oggi sono..."}
data: {"type":"tool_call","name":"get_dashboard"}
data: {"type":"tool_result","data":{...}}
data: {"type":"done"}
Progettato per deployment enterprise con multi-tenancy completa, autenticazione JWT, HTTPS end-to-end e audit trail su tutti i dati.
user_id, tenant_id, role. Ogni richiesta API valida il JWT nel AuthMiddleware.tenant_id estratto dal JWT viene applicato a TUTTE le query SQL come filtro WHERE obbligatorio. È impossibile accedere ai dati di un altro tenant anche con JWT valido.wms-network. Le porte dei servizi PHP sono bound a 127.0.0.1 (non esposte pubblicamente). Solo il gateway :3080 è raggiungibile dall'esterno.admin (pieno accesso), supervisor (senza admin utenti), operatore (solo operazioni, no configurazione). Il frontend mostra solo i portali e le funzioni autorizzate per il ruolo.ncr_events). I movimenti stock sono immutabili. Il log Apache registra ogni request con IP, timestamp e response code..env.production (gitignored). JWT_SECRET generato con openssl rand -hex 32. API_KEY con openssl rand -hex 32. ANTHROPIC_API_KEY mai committata in repo.Deployment su Hetzner Cloud con Docker Compose, 11 container in produzione, healthcheck su ogni servizio.
Ogni container ha healthcheck Docker:
HEALTHCHECK \
--interval=30s \
--timeout=3s \
--start-period=10s \
--retries=3 \
CMD curl -f http://127.0.0.1/health
Tutti i servizi devono rispondere GET /health → 200 JSON
# Full deploy (build + start)
./infrastructure/deploy-hetzner.sh --full
# Solo rebuild immagini
./infrastructure/deploy-hetzner.sh --build
# Verifica stato
./infrastructure/deploy-hetzner.sh --verify
# Rebuild singolo servizio
docker build -t wms-agile-{svc} -f services/{svc}/Dockerfile .
docker stop wms-{svc} && docker rm wms-{svc}
docker run -d --name wms-{svc} --network wms-network \
--env-file /var/www/wms-agile/services/{svc}/.env.production \
-p 127.0.0.1:{PORT}:80 --restart unless-stopped --memory 256m \
wms-agile-{svc}
L'AI ha accesso diretto ai dati del magazzino tramite 10 tools strutturati. Ogni tool è una query PHP sicura al DB con tenant isolation.
| Tool | Descrizione |
|---|---|
get_dashboard_summary | KPI operativi correnti |
list_missions | Lista missioni filtrate per stato/tipo |
create_mission | Crea nuova missione operativa |
list_products | Prodotti con stock e classe ABC |
configure_bin | Modifica attributi di una ubicazione |
configure_zone | Modifica attributi di una zona |
list_non_conformities | NCR filtrate per stato/gravità |
create_ncr | Apre nuova non conformità |
get_tracking_summary | Stato spedizione per numero tracking |
list_delivery_runs | Giri consegna con manifesto |
Browser → POST /api/ai/chat
↓ (stream: true)
ai-ms → Anthropic API
↓ function_calling
Claude seleziona tool
↓
WmsTools::execute($tool, $input)
↓ PDO query su wms_*_db
↓ risultato → Claude
↓ risposta narrativa
SSE stream → Browser
(chunk per chunk)
Il tenant_id viene estratto dal JWT nel middleware prima di passare il controllo all'AI. Claude non può mai accedere a dati di altri tenant.
Retrieval-Augmented Generation per-tenant: ogni azienda ha la propria knowledge base vettoriale isolata. L'AI risponde con contesto aziendale reale (SOP, manuali, procedure) invece di sole conoscenze generali.
| Componente | Tecnologia | Dettaglio |
|---|---|---|
| Embedding model | voyage-3-lite | 512 dim · cosine · Voyage AI API |
| Vector DB | Qdrant | Container Docker · port 6333 interno |
| Collection naming | wms_kb_{tenant_id} | Isolamento dati per tenant |
| Chunking | 250 parole · overlap 40 | ~350 token/chunk · contesto preservato |
| Score threshold | 0.35 cosine similarity | Solo chunk rilevanti al contesto |
| Indicizzazione | Batch · 8 testi/call | Rispetta 3 RPM free tier Voyage AI |
Utente → POST /api/ai/chat
↓ query dell'utente
IngestionService::search()
↓ embed query (voyage-3-lite)
Qdrant similarity search
↓ top-4 chunk con score ≥ 0.35
Contesto iniettato nel prompt Claude
↓ "Fonti aziendali: ..."
Claude Sonnet 4.6 risponde
↓ con contesto reale tenant
SSE stream → Browser
Se Qdrant non ha chunk per il tenant (knowledge base vuota), Claude risponde con conoscenza generale senza errori.