Principi di Architettura
L'architettura di Emblema è guidata da principi fondamentali che assicurano scalabilità, affidabilità e manutenibilità nel tempo. Questi principi non sono solo teoria, ma pratiche concrete applicate in ogni componente del sistema.
🎯 Principi Fondamentali
1. Modularità e Separazione delle Responsabilità
Ogni componente di Emblema ha una responsabilità ben definita e opera in modo indipendente.
Implementazione pratica:
// ❌ Sbagliato: Logica mista
class DocumentController {
async uploadDocument(file: File) {
// Validazione, elaborazione, storage, notifiche tutto insieme
if (!file.type.includes("pdf")) throw new Error("Invalid");
const processed = await this.processFile(file);
await this.saveToDatabase(processed);
await this.sendNotification();
}
}
// ✅ Corretto: Responsabilità separate
class DocumentController {
constructor(
private validator: DocumentValidator,
private processor: DocumentProcessor,
private storage: DocumentStorage,
private notifier: NotificationService,
) {}
async uploadDocument(file: File) {
await this.validator.validate(file);
const processed = await this.processor.process(file);
const stored = await this.storage.save(processed);
await this.notifier.notify("document.uploaded", stored);
}
}
2. Scalabilità Orizzontale
Il sistema deve poter crescere aggiungendo risorse, non sostituendole.
Configurazione Docker Compose reale per scaling:
services:
www-emblema:
image: emblema/www:${EMBLEMA_VERSION:-dev}
restart: always
depends_on:
- document-render
- milvus
- minio
- graphql-engine
- keycloak
- litellm
- background-task
environment:
- MILVUS_API_URL=http://milvus:19530/v2/vectordb
- HASURA_API_URL=http://graphql-engine:8080/v1/graphql
- LITELLM_API_URL=http://litellm:4000/v1
- BACKGROUND_TASK_API_URL=http://background-task
labels:
- "traefik.enable=true"
- "traefik.http.routers.emblema-web.rule=Host(`${EMBLEMA_WEB_HOSTNAME}`)"
background-task:
image: emblema/background-task:${EMBLEMA_VERSION:-dev}
restart: always
environment:
- CELERY_BROKER_URL=${CELERY_BROKER_URL}
- MILVUS_URI=http://milvus:19530
- GRAPHQL_URL=http://graphql-engine:8080/v1/graphql
background-task-worker:
image: emblema/background-task:${EMBLEMA_VERSION:-dev}
command: /app/.venv/bin/celery -A app.celery_app:celery_app worker --concurrency=${CELERY_MAX_CONCURRENCY:-1}
deploy:
resources:
reservations:
devices:
- driver: nvidia
device_ids: ["1"]
capabilities: [gpu]
3. Sicurezza by Design
La sicurezza non è un layer aggiuntivo, ma parte integrante di ogni componente.
Principi applicati:
- Zero Trust: Nessuna fiducia implicita
- Least Privilege: Minime autorizzazioni necessarie
- Defense in Depth: Multiple barriere di sicurezza
// Esempio di middleware di sicurezza
export class SecurityMiddleware {
async authenticate(req: Request): Promise<User> {
// 1. Verifica token JWT
const token = this.extractToken(req);
const payload = await this.verifyToken(token);
// 2. Verifica permessi
const user = await this.loadUser(payload.userId);
if (!user.isActive) throw new UnauthorizedError();
// 3. Rate limiting
await this.checkRateLimit(user.id, req.ip);
// 4. Audit logging
await this.logAccess(user, req);
return user;
}
}
4. Resilienza e Fault Tolerance
Il sistema deve continuare a funzionare anche in presenza di errori.
Pattern di resilienza implementati:
Circuit Breaker
class CircuitBreaker {
private failures = 0;
private lastFailTime: Date;
private state: "CLOSED" | "OPEN" | "HALF_OPEN" = "CLOSED";
async call<T>(fn: () => Promise<T>): Promise<T> {
if (this.state === "OPEN") {
if (this.shouldAttemptReset()) {
this.state = "HALF_OPEN";
} else {
throw new Error("Circuit breaker is OPEN");
}
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
}
Retry con Backoff
async function retryWithBackoff<T>(
fn: () => Promise<T>,
maxRetries = 3,
baseDelay = 1000,
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
const delay = baseDelay * Math.pow(2, i);
await sleep(delay + Math.random() * 1000);
}
}
}
5. Osservabilità
Ogni componente deve essere monitorabile e tracciabile.
Implementazione delle metriche:
import { Counter, Histogram, register } from "prom-client";
// Metriche custom
const httpRequestDuration = new Histogram({
name: "http_request_duration_seconds",
help: "Duration of HTTP requests in seconds",
labelNames: ["method", "route", "status"],
});
const documentsProcessed = new Counter({
name: "documents_processed_total",
help: "Total number of documents processed",
labelNames: ["type", "status"],
});
// Middleware per tracking
export function metricsMiddleware(req, res, next) {
const start = Date.now();
res.on("finish", () => {
const duration = (Date.now() - start) / 1000;
httpRequestDuration
.labels(req.method, req.route?.path || "unknown", res.statusCode)
.observe(duration);
});
next();
}
6. Performance First
Le performance non sono un afterthought ma una caratteristica fondamentale.
Strategie implementate:
Caching Multi-livello
class CacheStrategy {
constructor(
private l1: MemoryCache, // In-process cache
private l2: RedisCache, // Distributed cache
private l3: DatabaseCache, // Persistent cache
) {}
async get<T>(key: string): Promise<T | null> {
// L1: Memory (microseconds)
let value = await this.l1.get(key);
if (value) return value;
// L2: Redis (milliseconds)
value = await this.l2.get(key);
if (value) {
await this.l1.set(key, value, 60); // 1 minute
return value;
}
// L3: Database (tens of milliseconds)
value = await this.l3.get(key);
if (value) {
await this.l2.set(key, value, 3600); // 1 hour
await this.l1.set(key, value, 60);
return value;
}
return null;
}
}
Query Optimization
-- Indici ottimizzati per ricerche comuni
CREATE INDEX idx_documents_user_created
ON documents(user_id, created_at DESC)
WHERE deleted_at IS NULL;
-- Materialized view per aggregazioni costose
CREATE MATERIALIZED VIEW document_stats AS
SELECT
user_id,
COUNT(*) as total_documents,
SUM(file_size) as total_size,
AVG(processing_time) as avg_processing_time
FROM documents
WHERE deleted_at IS NULL
GROUP BY user_id;
-- Refresh periodico
CREATE OR REPLACE FUNCTION refresh_document_stats()
RETURNS void AS $$
BEGIN
REFRESH MATERIALIZED VIEW CONCURRENTLY document_stats;
END;
$$ LANGUAGE plpgsql;
7. Automazione e DevOps
Tutto ciò che può essere automatizzato, deve esserlo.
# CI/CD Pipeline esempio
name: Deploy Production
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run tests
run: |
docker-compose -f docker-compose.test.yml up --abort-on-container-exit
build:
needs: test
runs-on: ubuntu-latest
steps:
- name: Build and push images
run: |
docker buildx build --platform linux/amd64,linux/arm64 \
-t emblema/app:${{ github.sha }} \
--push .
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Deploy to Kubernetes
run: |
kubectl set image deployment/web-app \
app=emblema/app:${{ github.sha }} \
--record
🏛️ Architettura Decisionale
Trade-offs Accettati
-
Complessità vs Scalabilità
- Accettiamo la complessità dei microservizi per avere scalabilità indipendente
-
Consistenza vs Performance
- Usiamo eventual consistency dove possibile per migliorare le performance
-
Costo vs Affidabilità
- Investiamo in ridondanza per garantire uptime 99.9%
Anti-Pattern da Evitare
❌ Distributed Monolith: Microservizi troppo accoppiati ❌ Chatty Services: Troppe chiamate tra servizi ❌ Shared Database: Database condiviso tra servizi ❌ Synchronous Everything: Tutto sincrono senza code
📊 Metriche di Successo
Per verificare l'aderenza ai principi, monitoriamo:
- Tempo di deploy: < 10 minuti
- Tempo di recovery: < 5 minuti
- Test coverage: > 80%
- API latency p99: < 200ms
- Uptime: > 99.9%
🔮 Evoluzione Futura
I principi architetturali evolvono con la tecnologia:
- Edge Computing: Elaborazione più vicina all'utente
- Serverless: Per workload variabili
- AI-Driven Ops: Automazione intelligente
- Zero-Trust Architecture: Sicurezza perimetrale zero
🏗️ Prossimo passo: Approfondisci l'High-Level Design per vedere come questi principi si traducono in architettura concreta.