Fronteres de servei
- Síncron: petició/resposta
- Asíncron: missatgeria i esdeveniments
- Streaming: connexió persistent
- Referències
Els conceptes d’arquitectura diuen on posem les fronteres. Aquesta pàgina mira com es comuniquen els dos costats de la més comuna en sistemes distribuïts, la frontera de servei.
Es pot comunicar de tres maneres fonamentals, distingides pel model d’interacció, no per qui hi ha a cada costat (tots tres estils apareixen tant de client a servei com de servei a servei):
- Síncrona (petició/resposta): el client envia una petició i espera la resposta. És el model de REST sobre HTTP. Senzill de raonar i de depurar, però acobla el client i el servei en el temps: si el servei no respon, el client queda bloquejat o falla.
- Asíncrona (missatgeria i esdeveniments): el productor diposita un missatge en un intermediari i continua sense esperar; un consumidor el processa més tard. Desacobla els dos costats en el temps, a canvi de més complexitat i consistència eventual.
- Streaming (connexió persistent): client i servei mantenen una connexió oberta per la qual les dades flueixen a mesura que es produeixen. Ni petició puntual ni intermediari: un canal continu i en temps real.
La majoria de sistemes reals combinen aquests estils segons el cas. Els microserveis en són l’exemple paradigmàtic: cada servei exposa aquesta frontera i s’hi comunica amb la resta, sovint amb REST per a les consultes síncrones i amb missatgeria per als esdeveniments, tot passant per un gateway d’APIs com hem vist als estils d’arquitectura.
Síncron: petició/resposta
REST (Representational State Transfer) és l’estil de petició/resposta més estès, habitualment sobre HTTP amb cossos en format JSON, i és el que veurem en detall. Dins d’aquest mateix model, però, en la pràctica també trobaràs:
- gRPC: crides a procediment remot (RPC) sobre HTTP/2, amb missatges binaris definits amb Protocol Buffers i un contracte fortament tipat. Eficient i molt usat en la comunicació interna entre microserveis; també admet streaming.
- GraphQL: un únic endpoint on el client demana exactament les dades que necessita, i evita obtenir-ne de més o de menys (over-fetching i under-fetching). Habitual en APIs orientades a clients amb necessitats de dades diverses.
- SOAP i XML-RPC: estils RPC més antics basats en XML, que encara apareixen en sistemes empresarials i integracions heretades.
En la pràctica:
- Protocol de xarxa: HTTP sobre TCP. REST i SOAP solen anar sobre HTTP/1.1; gRPC exigeix HTTP/2, que multiplexa moltes crides en una sola connexió.
- Ús des de la solució: fas una crida amb un client HTTP (per exemple
fetch,axios,HttpClientoWebClient) i esperes l’objecte de resposta; sovint amb un client o SDK generat a partir del contracte (obligatori a gRPC, a partir del fitxer.proto). - Quan usar-lo: quan el client necessita la resposta immediata i la interacció és clarament una consulta o una ordre amb resultat: CRUD sobre recursos, lectures o validacions.
Criteris de disseny
- Identificar els recursos que formen part de l’API i els seus identificadors.
- Definir l’URI de cada recurs, o endpoint, amb noms i mai amb verbs. Si cal executar una acció,
expressar-la sobre un nom, per exemple amb un paràmetre
actiona la query. - Anomenar en plural els endpoints de col·leccions.
- Retornar la informació completa quan es demana un sol recurs, i reduir-la a l’estrictament necessària quan es retornen col·leccions.
- Assignar a cada endpoint els mètodes HTTP adequats, amb els codis d’estat corresponents.
- Utilitzar JSON com a format del cos, tant per enviar com per rebre dades.
- Aplicar filtres, ordenació i paginació a les col·leccions.
- Generar documentació de l’API amb OpenAPI.
Mètodes
El content type a utilitzar amb les peticions és “application/json”. Usos dels mètodes HTTP:
- GET recupera una representació del recurs a l’URI especificat.
- Si es troba, 200 (Ok). El cos del missatge de resposta conté els detalls del recurs sol·licitat.
- Si no es troba, 404 (Not Found).
- També es pot retornar 204 (No Content) si ha anat bé, però no es retorna cap contingut.
- Si les dades enviades no són vàlides, 400 (Bad Request). El cos pot incloure informació addicional sobre el problema.
- POST crea un recurs nou a l’URI especificat. El cos del missatge de sol·licitud proporciona
els detalls del nou recurs. Cal tenir en compte que POST també es pot utilitzar per activar
operacions que en realitat no creen recursos.
- Si es crea un nou recurs, 201 (Created). El recurs pot retornar-se al cos.
- Si es fa algun procés, però no es crea res, 200 (Ok). El cos pot incloure el resultat de l’operació. Alternativament, si no hi ha resultat, es pot retornar 204 (No Content) sense cos.
- Si les dades enviades no són vàlides, 400 (Bad Request). El cos pot incloure informació addicional sobre el problema.
- PUT crea o substitueix el recurs a l’URI especificat. El cos del missatge de sol·licitud
especifica el recurs que s’ha de crear o actualitzar.
- Si es crea un nou recurs, 201 (Created).
- Si s’actualitza, 200 (Ok) o 204 (No Content).
- Si no és possible l’actualització, 409 (Conflict).
- PATCH realitza una actualització parcial d’un recurs. El cos de la sol·licitud especifica el conjunt de canvis que cal aplicar al recurs. Les respostes podrien ser com les de PUT.
- DELETE elimina el recurs a l’URI especificat.
- Si funciona, 204 (No Content), sense retornar cap informació.
- Si es retorna alguna informació també es pot utilitzar 200 (Ok).
- Si no existeix, 404 (Not Found).
Aquests són alguns exemples d’endpoints i com se solen utilitzar segons els mètodes:
| Recurs | POST | GET | PUT | DELETE |
|---|---|---|---|---|
| /customers | Crear un nou client | Obtenir tots els clients | Actualitzar tots els clients | Esborrar tots els clients |
| /customers/1 | N/A | Obtenir els detalls del client 1 | Actualitzar els detalls del client 1, si existeix | Esborrar client 1 |
| /customers/1/orders | Crear una nova comanda per al client 1 | Obtenir totes les comandes del client 1 | Actualitzar totes les comandes del client 1 | Esborrar totes les comandes del client 1 |
Filtres, ordenació i paginació
Quan s’exposa un conjunt de recursos a un endpoint, cal evitar retornar una quantitat molt gran de
dades. L’API hauria de permetre especificar filtres a l’URI:
recursos?filtre1=valor1&filtre2=valor2…. També caldria especificar a l’URI com obtenir només una
part dels resultats, quan poden ser molts.
L’ordenació també es pot realitzar amb un paràmetre del tipus recursos?order_by=criteri.
Cal utilitzar paginació sempre que una col·lecció de recursos pugui ser gran perquè pugui créixer sense límit. Es pot fer principalment de tres formes:
- offset:
recursos?limit=nombre&offset=nombrepermet utilitzar els paràmetres SQL per limitar els resultats. L’opció més senzilla, però poc òptima per a offsets alts: cal obtenir tots els registres anteriors en la query. - keyset: filtra pel valor d’un camp que defineix l’ordre. Per exemple, la data de creació.
Podem utilitzar
recursos?limit=nombre&from_date=data. - seek: similar a l’anterior, però utilitzant una primary key. Podem utilitzar
recursos?limit=nombre&after_id=id.
Per a la paginació, pots utilitzar un valor de límit per defecte si no es diu res. Per exemple, 20. No s’hauria de permetre un valor qualsevol per a aquest paràmetre (ha d’estar limitat). També pots retornar informació al cos de la resposta que pugui ajudar al client a gestionar el resultat.
Versions i compatibilitat
Un contracte publicat no es pot canviar, només es pot fer créixer. La regla és la del disseny per contracte, no requerir més ni prometre menys, aplicada ara entre dos desplegables: pots demanar menys al client i donar-li més, mai a l’inrevés.
| Canvis compatibles | Canvis que trenquen el client |
|---|---|
| Afegir un endpoint nou | Eliminar o reanomenar un camp o un endpoint |
| Afegir un camp opcional a la resposta | Canviar el tipus, el format o el significat d’un camp |
| Afegir un paràmetre opcional amb valor per defecte | Fer obligatori un paràmetre que no ho era |
| Relaxar una validació o ampliar un límit | Endurir una validació o reduir un límit |
Els valors nous en un camp enumerat són el cas dubtós: només són compatibles si els clients estan escrits per ignorar el que no reconeixen. D’aquesta pràctica se’n diu client tolerant (tolerant reader), i és la manera més barata d’evitar que un canvi menor trenqui res.
Quan el canvi trenca i no es pot evitar, cal publicar una versió nova. Les tres formes
habituals són posar-la a la ruta (/api/v1/comandes), en un paràmetre de consulta, o en la
capçalera Accept amb un tipus de mitjà propi (application/vnd.exemple.v2+json). La de la ruta
és la més estesa perquè es veu al registre, al navegador i a qualsevol exemple de curl.
Publicar la versió nova no és el final. Les dues han de conviure un temps, cal anunciar la retirada
de l’antiga (les capçaleres Deprecation i Sunset serveixen exactament per això) i cal mesurar
qui la fa servir encara abans d’apagar-la. Amb clients que no controles, com una app mòbil, aquest
període es compta en mesos, i és el motiu pel qual la compatibilitat es tracta com una obligació i
no com una bona pràctica: vegeu Quan el client és una app
mòbil.
Asíncron: missatgeria i esdeveniments
En comptes d’una conversa directa, el productor diposita missatges en un intermediari (broker) que els encamina cap als consumidors. És l’estil de l’arquitectura orientada a esdeveniments que hem vist als estils d’arquitectura. Hi ha tres patrons bàsics:
- Cues (point-to-point): cada missatge el processa un únic consumidor. Serveix per repartir feina i posar-la a la cua quan hi ha pics de càrrega.
- Publicació/subscripció: un esdeveniment es difon a tots els subscriptors interessats. El productor no sap qui l’escolta, cosa que desacobla fortament els sistemes.
- Registre d’esdeveniments (log): els esdeveniments s’afegeixen a un registre append-only (només s’hi afegeix, mai es modifica) i s’hi conserven; diversos consumidors el llegeixen al seu ritme i en poden rellegir l’historial. És la base de l’streaming d’esdeveniments d’alt volum.
En la pràctica:
- Protocol de xarxa: els protocols de missatgeria van sobre TCP i són oberts, de manera que productors i consumidors de tecnologies diferents es comuniquen sense acoblar-se a una implementació. Els més habituals són AMQP (Advanced Message Queuing Protocol, cues fiables), MQTT (lleuger, pub/sub, molt usat en IoT) i STOMP (senzill, basat en text).
- Ús des de la solució: amb la llibreria client del broker, el productor publica un missatge en una cua o tema i continua; el consumidor registra un listener (una funció de callback) que s’executa quan arriba cada missatge, sovint en un procés treballador en segon pla.
- Quan usar-lo: quan vulguis desacoblar productors i consumidors, processar en segon pla, anivellar pics de càrrega o difondre esdeveniments a diversos sistemes, i no calgui una resposta immediata.
Els compromisos respecte del model síncron són:
- A favor: desacoblament en el temps (el consumidor pot estar caigut i processar més tard), resiliència, absorció de pics de càrrega i escalabilitat independent de productor i consumidor.
- En contra: més peces mòbils (el broker), consistència eventual, i cal gestionar l’ordre i els missatges duplicats. Les garanties de lliurament habituals són “com a mínim un cop” (at-least-once), “com a molt un cop” (at-most-once) i “exactament un cop” (exactly-once), aquesta última difícil i costosa.
- El flux és més difícil de seguir, perquè no hi ha una traça de petició/resposta única.
Una variant sense broker molt estesa són els webhooks: en lloc de consultar repetidament un servei per saber si ha passat res (polling), hi registres una URL i el servei t’hi fa una petició HTTP (un callback) quan es produeix l’esdeveniment. És el mecanisme habitual per rebre notificacions de serveis externs, com passarel·les de pagament o repositoris de codi.
Streaming: connexió persistent
En comptes de peticions puntuals o de missatges a través d’un intermediari, els dos extrems obren una connexió persistent per la qual el servei empeny dades a mesura que es produeixen. Els dos protocols habituals són:
- Server-Sent Events (SSE): unidireccional, només del servidor cap al client, amb reconnexió automàtica.
- WebSockets: bidireccional i full-duplex.
En la pràctica:
- Protocol de xarxa: tots dos comencen amb un handshake HTTP i després mantenen una connexió TCP
oberta. SSE continua sent HTTP (un flux
text/event-stream); WebSockets fa un canvi (upgrade) a un protocol propi de trames sobre la mateixa connexió TCP. - Ús des de la solució: al navegador, SSE s’usa amb l’objecte
EventSourcei WebSockets amb l’APIWebSocket(o llibreries com Socket.IO); en tots dos casos registres callbacks (onmessage,onopen) per reaccionar a les dades que arriben. Al servidor mantens el handler que empeny esdeveniments mentre la connexió és viva. - Quan usar-lo: quan calen actualitzacions contínues en temps real i baixa latència (notificacions en viu, xats, taulers, edició col·laborativa). Tria SSE si el flux va només del servidor al client; WebSockets si els dos costats han d’enviar dades.
A diferència de la missatgeria, aquí no hi ha broker que desacobli els extrems ni que emmagatzemi els missatges: si la connexió es talla, cal restablir-la (SSE ho fa sol; amb WebSockets ho gestiona l’aplicació). És el model amb menys latència, a canvi de mantenir connexions obertes, cosa que afegeix pressió sobre els recursos del servidor.
Abans que SSE i WebSockets fossin habituals, el mateix efecte de temps real s’aconseguia amb el long polling (el client fa una petició que el servidor manté oberta fins que té dades noves, i llavors en fa una altra). Encara es troba com a alternativa de compatibilitat quan SSE o WebSockets no estan disponibles.
Aquests tres estils són els que ha de saber parlar l’aplicació que escrius. Quins porta de sèrie un framework i quins delega a un servei a part ho veiem a Arquitectura dels frameworks, i els protocols amb detall, a Protocols.