Una arquitectura no es jutja només per la funcionalitat que suporta, sinó per com la suporta: com de disponible, ràpida, segura i modificable és. Aquestes propietats s'anomenen atributs de qualitat i rarament sorgeixen per casualitat. S'aconsegueixen mitjançant tàctiques arquitectòniques: decisions de disseny concretes i catalogades que influeixen sobre la resposta del sistema davant d'un estímul. El concepte prové del Software Engineering Institute (SEI) i del llibre Software Architecture in Practice. En aquesta lliçó estudiarem les tàctiques més importants per a disponibilitat, rendiment, seguretat i modificabilitat, amb exemples concrets i una taula de referència que relaciona cada atribut amb les seves tàctiques.
Contingut
- Què és un atribut de qualitat i què és una tàctica
- Escenaris d'atribut de qualitat
- Tàctiques de disponibilitat
- Tàctiques de rendiment
- Tàctiques de seguretat
- Tàctiques de modificabilitat
- Taula resum: atribut → tàctiques
- Errors comuns i consells
- Exercicis
- Conclusió
- Què és un atribut de qualitat i què és una tàctica
Un atribut de qualitat (quality attribute o "-itat") és una propietat mesurable que indica com de bé satisfà el sistema les necessitats dels seus interessats: disponibilitat, rendiment, seguretat, modificabilitat, escalabilitat, usabilitat, etc.
Una tàctica és una decisió de disseny que afecta la resposta davant d'un estímul concret relacionat amb un atribut. A diferència d'un patró (que combina diverses tàctiques i estructures), la tàctica és un àtom de disseny: granular i combinable.
- Patró = recepta completa (p. ex. Circuit Breaker, Cache-Aside).
- Tàctica = ingredient (p. ex. "detectar errors amb heartbeat", "introduir concurrència").
- Escenaris d'atribut de qualitat
Per dissenyar i mesurar, els atributs de qualitat s'expressen com a escenaris amb sis parts. Això converteix un requisit vague ("el sistema ha de ser ràpid") en quelcom verificable.
| Part | Descripció | Exemple |
|---|---|---|
| Font | Qui genera l'estímul | Usuari extern |
| Estímul | L'esdeveniment que arriba | 1000 peticions/seg |
| Artefacte | Quina part es veu afectada | API de catàleg |
| Entorn | Condicions del sistema | Càrrega normal |
| Resposta | Què fa el sistema | Processa les peticions |
| Mesura | Criteri d'èxit | Latència p95 < 200 ms |
Escenari complet d'exemple: "Sota càrrega normal (1000 req/s), l'API de catàleg respon amb una latència p95 inferior a 200 ms". Les tàctiques es trien per satisfer escenaris com aquest.
- Tàctiques de disponibilitat
La disponibilitat és la capacitat del sistema d'estar operatiu i accessible quan se'l necessita. Les tàctiques s'agrupen en tres categories: detectar errors, recuperar-se'n i prevenir-los.
Detecció d'errors:
- Ping/Echo i Heartbeat: un component envia senyals periòdics; la seva absència indica caiguda.
- Monitorització i timeouts: es vigila el temps de resposta i s'assumeix error en expirar.
- Comprovació de salut (health check): endpoints que verifiquen l'estat intern.
Recuperació davant d'errors:
- Redundància activa/passiva: rèpliques a punt per assumir la càrrega.
- Reintents amb backoff: reintentar operacions idempotents amb espera creixent.
- Circuit Breaker: deixar de cridar un servei que falla per no agreujar la situació.
- Degradació elegant: oferir servei reduït en lloc de caure completament.
Prevenció d'errors:
- Eliminació del servei del pool davant d'anomalies.
- Transaccions per mantenir consistència.
Exemple de Circuit Breaker (declaratiu, amb Resilience4j):
@CircuitBreaker(name = "inventario", fallbackMethod = "stockPorDefecto")
public int consultarStock(String producto) {
return clienteInventario.obtenerStock(producto); // crida remota
}
// Mètode de suport: s'invoca quan el circuit està obert o la crida falla
public int stockPorDefecto(String producto, Throwable t) {
return 0; // degradació elegant: assumim sense stock en lloc de fallar
}Explicació: el @CircuitBreaker embolcalla la crida remota. Si el servei d'inventari acumula errors, el circuit s'"obre" i deixa d'invocar-lo, tot retornant el fallbackMethod. Això evita saturar un servei caigut i protegeix la disponibilitat global. La mesura típica de l'escenari seria "el sistema continua responent encara que inventari estigui caigut".
- Tàctiques de rendiment
El rendiment mesura la capacitat de respondre dins d'uns límits de temps (latència) i volum (throughput). Les tàctiques es divideixen entre gestionar la demanda de recursos i gestionar els propis recursos.
Gestió de la demanda:
- Memòria cau (caché): emmagatzemar resultats costosos per reutilitzar-los.
- Limitació de taxa (rate limiting) i mostreig d'esdeveniments.
- Reduir overhead: menys capes, menys serialització innecessària.
Gestió de recursos:
- Concurrència / paral·lelisme: processar en paral·lel.
- Augmentar recursos / escalat: més CPU, memòria o nodes.
- Pool de connexions / fils: reutilitzar recursos cars de crear.
- Processament asíncron: desacoblar la petició del seu processament.
Exemple de tàctica de memòria cau (patró Cache-Aside):
public Producto obtenerProducto(String id) {
Producto cacheado = cache.get(id); // 1. mirem primer la memòria cau
if (cacheado != null) {
return cacheado; // 2. encert: resposta immediata
}
Producto p = repositorio.buscar(id); // 3. error: anem a la BD (lent)
cache.put(id, p, Duration.ofMinutes(10)); // 4. guardem amb expiració (TTL)
return p;
}Explicació pas a pas: (1) consultem la memòria cau; (2) si hi ha encert evitem la base de dades; (3) si no, recorrem a l'origen lent; (4) emmagatzemem el resultat amb un temps de vida (TTL) per no servir dades eternament obsoletes. La memòria cau redueix dràsticament la latència i la càrrega del backend, a canvi de gestionar la coherència de les dades.
- Tàctiques de seguretat
La seguretat protegeix la confidencialitat, integritat i disponibilitat de les dades i serveis. Les tàctiques s'agrupen en resistir, detectar i recuperar-se d'atacs.
Resistir atacs:
- Autenticació: verificar la identitat (tokens, OAuth2, certificats).
- Autorització: controlar què pot fer cada identitat (RBAC, ABAC).
- Xifratge: dades en trànsit (TLS) i en repòs.
- Validació d'entrada: prevenir injecció i dades malicioses.
- Mínim privilegi: cada component només amb els permisos que necessita.
Detectar atacs:
- Auditoria i registre (logging) d'esdeveniments de seguretat.
- Detecció d'intrusions i anomalies.
Recuperar-se d'atacs:
- Revocació de credencials, còpies de seguretat, traçabilitat per a forense.
Exemple d'autorització declarativa (Spring Security):
@PreAuthorize("hasRole('ADMIN')") // només administradors
public void eliminarUsuario(String id) {
repositorio.eliminar(id);
}
@PreAuthorize("#propietario == authentication.name") // el propietari del recurs
public Pedido verPedido(String propietario, String pedidoId) {
return repositorio.buscar(pedidoId);
}Explicació: @PreAuthorize avalua una expressió abans d'executar el mètode. El primer cas aplica control d'accés basat en rols (RBAC); el segon, una regla més fina que comprova que qui crida sigui el propietari del recurs. Combinat amb validació d'entrada i xifratge en trànsit, conforma una defensa en profunditat. Recorda que aquestes decisions tenen implicacions de compliment (per exemple, protecció de dades) i s'han de revisar amb criteri professional.
- Tàctiques de modificabilitat
La modificabilitat és la facilitat i el cost amb què es realitzen canvis. És l'atribut més lligat als principis de disseny vistos anteriorment. Les tàctiques busquen localitzar l'impacte del canvi, prevenir efectes col·laterals i diferir el binding.
Reduir la mida dels mòduls:
- Dividir mòduls grans en peces cohesionades.
Augmentar la cohesió:
- Agrupar responsabilitats afins (mantenir la cohesió funcional).
Reduir l'acoblament:
- Encapsular darrere d'interfícies.
- Fer servir intermediaris (façanes, adaptadors, brokers).
- Restringir dependències (regles de capes, mòduls).
- Refactoritzar per eliminar duplicació.
Diferir el moment d'enllaç (binding):
- Configuració externa, injecció de dependències, plugins, feature flags.
Exemple de diferir el binding mitjançant configuració externa i injecció:
public interface PasarelaPago {
void cobrar(double importe);
}
@Service
public class ServicioCheckout {
private final PasarelaPago pasarela;
// la implementació concreta es decideix fora del codi, per configuració
public ServicioCheckout(PasarelaPago pasarela) { this.pasarela = pasarela; }
public void finalizar(double total) { pasarela.cobrar(total); }
}# application.yml: canviar de proveïdor de pagament sense tocar el codi compilat pagos: proveedor: stripe # només cal canviar aquest valor a "paypal"
Explicació: el servei depèn de la interfície PasarelaPago, no d'un proveïdor concret. Quina implementació s'injecta es decideix per configuració (application.yml). Canviar de Stripe a PayPal no requereix modificar ni recompilar la lògica de negoci: l'impacte del canvi queda localitzat, que és just l'objectiu de la modificabilitat.
- Taula resum: atribut → tàctiques
| Atribut de qualitat | Tàctiques principals | Exemple de mecanisme |
|---|---|---|
| Disponibilitat | Detecció (heartbeat, health check), recuperació (redundància, reintents, circuit breaker, degradació), prevenció | Resilience4j, rèpliques, balancejadors |
| Rendiment | Memòria cau, concurrència, asíncron, pools, escalat, rate limiting | Redis, fils, cues, autoscaling |
| Seguretat | Autenticació, autorització, xifratge, validació, mínim privilegi, auditoria | OAuth2/TLS, RBAC, WAF, logs |
| Modificabilitat | Reduir acoblament, augmentar cohesió, encapsular, diferir binding | Interfícies, DI, plugins, feature flags |
| Escalabilitat | Particionat, statelessness, balanceig de càrrega | Sharding, serveis sense estat |
| Testabilitat | Injecció de dependències, interfícies, control d'estat | Mocks, ports i adaptadors |
Nota important: les tàctiques interactuen i competeixen. Afegir redundància (disponibilitat) pot complicar la consistència; afegir xifratge (seguretat) penalitza el rendiment; afegir capes d'abstracció (modificabilitat) afegeix latència. L'arquitectura consisteix a trobar l'equilibri adequat segons els escenaris prioritaris.
Errors Comuns i Consells
- Optimitzar atributs que ningú no ha prioritzat. Sense escenaris concrets, s'inverteix esforç on no aporta. Defineix escenaris mesurables abans de triar tàctiques.
- Ignorar les contrapartides (trade-offs). Cada tàctica té un cost en un altre atribut. Fes-ho explícit.
- Confondre tàctica amb patró. El circuit breaker és un patró que materialitza diverses tàctiques; no les barregis conceptualment.
- Afegir memòria cau sense estratègia d'invalidació. És la font número u de bugs de dades obsoletes. Defineix sempre TTL o invalidació.
- Tractar la seguretat com una capa final. S'ha de dissenyar des del principi (security by design) i revisar-se professionalment, especialment en dominis regulats.
- Consell: documenta les decisions de tàctiques i els seus trade-offs en registres de decisions d'arquitectura (ADR).
Exercicis
Exercici 1. Escriu un escenari d'atribut de qualitat de 6 parts per a la disponibilitat d'un servei de pagaments.
Exercici 2. Un endpoint consulta una taula de configuració que canvia una vegada al dia però es llegeix milers de vegades per minut, i va lent. Quina tàctica de rendiment aplicaries i per què?
Exercici 3. Indica quina tàctica de modificabilitat permet canviar el proveïdor d'enviament d'SMS sense recompilar l'aplicació, i esbossa'n el disseny.
Solucions
Solució 1. Per exemple:
Font: node del servei de pagaments. Estímul: el node deixa de respondre. Artefacte: servei de pagaments. Entorn: operació normal. Resposta: un node redundant assumeix el trànsit automàticament. Mesura: la disponibilitat mensual es manté per sobre del 99,95% i la commutació triga menys de 5 segons.
Solució 2. Tàctica de memòria cau: les dades canvien una vegada al dia (baixa volatilitat) però es llegeixen massivament. Emmagatzemar el resultat a la memòria cau amb un TTL de minuts (o invalidació en actualitzar) elimina la immensa majoria d'accessos a l'origen lent, tot reduint latència i càrrega. Encaixa perfectament amb el patró Cache-Aside.
Solució 3. Tàctica de diferir el binding mitjançant interfície + injecció + configuració externa:
public interface PasarelaSms { void enviar(String numero, String texto); }
@Service
public class ServicioAvisos {
private final PasarelaSms sms;
public ServicioAvisos(PasarelaSms sms) { this.sms = sms; }
public void avisar(String numero) { sms.enviar(numero, "Aviso"); }
}
// La implementació concreta (Twilio, Vonage...) se selecciona per configuració,
// sense tocar ServicioAvisos ni recompilar la lògica de negoci.Conclusió
Les tàctiques arquitectòniques són el repertori de decisions concretes amb què un arquitecte persegueix els atributs de qualitat. Hem vist com expressar requisits com a escenaris mesurables i hem recorregut tàctiques per a disponibilitat (detecció, recuperació, prevenció), rendiment (memòria cau, concurrència, asincronia), seguretat (resistir, detectar, recuperar-se) i modificabilitat (reduir acoblament, diferir binding). La taula resum i l'advertència sobre els trade-offs són les eines que t'enduràs. Tota decisió tècnica té un cost i una vida útil; quan aquestes decisions s'acumulen sense gestionar-se, generen deute tècnic, el tema central de la següent i última lliçó d'aquest mòdul.
Curs d'Arquitectura d'Aplicacions
Mòdul 1: Fonaments de l'Arquitectura d'Aplicacions
- Què és l'Arquitectura d'Aplicacions?
- El Rol de l'Arquitecte de Programari
- Atributs de Qualitat i Requisits No Funcionals
- Decisions Arquitectòniques i Compromisos (Trade-offs)
- Documentació d'Arquitectura: Vistes i el Model C4
Mòdul 2: Principis i Tàctiques de Disseny
- Acoblament, Cohesió i Separació de Responsabilitats
- Principis SOLID Aplicats a l'Arquitectura
- DRY, KISS, YAGNI i Altres Principis de Disseny
- Tàctiques Arquitectòniques per als Atributs de Qualitat
- Gestió del Deute Tècnic
Mòdul 3: Estils i Patrons Arquitectònics
- Arquitectura Monolítica
- Arquitectura en Capes (N-Tier)
- Arquitectura Client-Servidor
- Arquitectura Hexagonal (Ports i Adaptadors)
- Arquitectura Neta i Ceba (Clean & Onion)
Mòdul 4: Arquitectures Distribuïdes i Microserveis
- Introducció als Sistemes Distribuïts
- Arquitectura de Microserveis
- Descomposició de Serveis i Bounded Contexts
- API Gateway, Service Discovery i Comunicació entre Serveis
- Patrons de Resiliència: Circuit Breaker, Retry i Bulkhead
- El Teorema CAP i la Consistència de Dades
Mòdul 5: Arquitectures Dirigides per Esdeveniments i Missatgeria
- Fonaments de l'Arquitectura Orientada a Esdeveniments
- Missatgeria Asíncrona: Cues i Brokers
- Patrons d'Esdeveniments: Event Sourcing i CQRS
- Gestió de Transaccions Distribuïdes: Patró Saga
- Streaming de Dades en Temps Real
Mòdul 6: Disseny Dirigit pel Domini (DDD)
- Conceptes Fonamentals del DDD
- Disseny Estratègic: Bounded Contexts i Llenguatge Ubic
- Disseny Tàctic: Entitats, Agregats i Repositoris
- Mapatge de Contextos (Context Mapping)
Mòdul 7: Dades i Persistència
- Estratègies de Persistència: SQL vs NoSQL
- Patrons d'Accés a Dades: Repository, Unit of Work i DAO
- Base de Dades per Servei i Gestió de Dades Distribuïdes
- Cau i Estratègies d'Invalidació
Mòdul 8: Arquitectura al Núvol i Desplegament
- Fonaments del Cloud Computing (IaaS, PaaS, SaaS)
- Contenidors i Orquestració amb Docker i Kubernetes
- Arquitectura Serverless
- Patrons de Disseny Cloud-Native
- Infraestructura com a Codi (IaC)
Mòdul 9: Qualitat, Seguretat i Observabilitat
- Escalabilitat: Horitzontal vs Vertical i Balanceig de Càrrega
- Alta Disponibilitat i Tolerància a Fallades
- Seguretat per Disseny i Autenticació/Autorització
- Observabilitat: Logging, Mètriques i Traçabilitat
- Rendiment i Proves de Càrrega
