Disseny
- Principis generals de programació
- És dolent el teu codi?
- Principis de POO (SOLID)
- Dependències a POO
- Disseny per a la testabilitat
- Arquitectura i fronteres
- Bones pràctiques per escriure codi
- Com anomenar
- REST APIs
- Referències
Principis generals de programació
- DRY (Don’t repeat yourself): no et repeteixis.
- Principi de l’abstracció: cada peça significant s’ha d’implementar en només un lloc del codi font.
- KISS (Keep it simple): mantenir la senzillesa.
- Evita crear YAGNI (no ho necessitaràs).
- Fes la feina més senzilla que sigui funcional.
- No em facis pensar.
- Escriu el codi per qui l’haurà de mantenir.
- El principi de la mínima sorpresa.
- Minimitzar l’acoblament i maximitzar la cohesió.
- Amagar els detalls de la implementació.
- La llei de Demeter: el codi només s’ha de comunicar amb les seves relacions directes.
- Evitar la optimització prematura: només si funciona i es lent.
- Reutilitzar codi és bo: el fa més llegible.
- Separació d’interessos: àrees de diferents funcionalitats han de tenir pocs solapaments.
- Els usuaris d’una classe han de dependre de la seva interfície pública, però la classe no ha de dependre dels usuaris.
És dolent el teu codi?
- És massa rígid? Es poden canviar els detalls interns d’aquest mòdul en el futur sense tocar el codi d’altres mòduls i altres capes? El codi rígid és el que té dependències que serpentegen en tantes direccions que no es pot fer un canvi aïllat sense canviar-ho tot al voltant.
- És massa fràgil? Seria difícil trobar llocs on fer canvis i refactoritzar en el futur? El codi fràgil es trenca de formes estranyes i que no es poden predir.
- Hauria de ser una característica reutilitzable? Si ho fos, el codi depèn de mòduls no desitjats que es podrien evitar? Vols una banana, però el que obtens és un goril·la agafant-la i tota la jungla amb ell.
Si mirem de prop, el fil conductor dels tres problemes esmentats és l’acoblament. Els mòduls depenen els uns dels altres de maneres no desitjades i resulten en un codi espagueti.
El codi hauria d’estar desacoblat entre els mòduls i les capes. Les polítiques d’alt nivell i les abstraccions no haurien de dependre de detalls de baix nivell, sinó d’abstraccions: caldria invertir la dependència dels mòduls als llocs necessaris. I escriure classes que només fan una cosa i només tenen un motiu per canviar.
El codi bo hauria d’explicar què està fent. Hauria de ser avorrit de llegir. Tot hauria de ser perfectament obvi. Això és bon codi - Robert Martin.
Principis de POO (SOLID)
- Responsabilitat única (SRP): un component de codi ha de fer una sola feina i ben definida. Només caldrà modificar-lo si necessitem canviar aquesta feina. Això millora la cohesió i redueix possibles errors. Code smell: quan vull canviar una funcionalitat, una altra no relacionada queda afectada, i cal refer-la.
- Obert/tancat: les entitats software han d’estar obertes a ser esteses i tancades a ser modificades. Si cal estendre la funcionalitat és millor afegir codi que canviar l’existent. Això es pot fer amb abstracció, derivant classes i utilitzant polimorfisme, i amb encapsulació. Code smell: quan vull afegir funcionalitat, es produeix una cascada de canvis.
- Substitució Liskov: qualsevol classe que hereta d’una altra (pare) pot ser utilitzada d’igual forma que la pare sense conèixer les diferències entre elles. Per tant, quan heretem no hem de canviar el comportament que defineix la classe pare. Code smell: codi client que comprova el tipus concret d’un objecte (instanceof, type checks) per decidir com tractar-lo.
- Segregació d’interfície: es millor tenir moltes interfícies de client específiques que una sola de propòsit general. Així, evitem que els clients depenguin de mètodes que no utilitzen. Code smell: classes que implementen mètodes d’una interfície amb cossos buits o llançant excepcions de “no suportat”.
- Inversió de dependència: les dependències han de ser sobre abstraccions, no sobre concrecions. Es resumeix en dues qüestions:
- Els mòduls d’alt nivell (més abstractes) no han d’importar res de mòduls de baix nivell (més concrets). Els dos han de dependre d’abstraccions (p. ex. interfícies).
- Les abstraccions no han de dependre dels detalls. Són els detalls (implementacions concretes) les que han de dependre de les abstraccions.
- Code smell: mòduls d’alt nivell que importen directament classes concretes de baix nivell (p. ex. un servei que instancia directament un client HTTP o una connexió a base de dades).

Un principi complementari a SOLID és preferir la composició a l’herència: en lloc de reutilitzar comportament heretant d’una classe pare, encapsular-lo en objectes que es combinen. L’herència crea un acoblament fort entre pare i fill que es propaga a tota la jerarquia; la composició permet canviar comportaments de forma independent.
Dependències a POO
A POO, si tenim dues classes A i B, i A necessita a B per fer la seva feina, llavors A té una dependència de B. Per a resoldre aquesta dependència podem fer:
- Que A crei o obtingui un objecte B. La classe A té el control de la dependència.
- Que A rebi un objecte B. Algú altre li proporciona, sense que A s’hagi de preocupar.
A més, quan necessitem un objecte poden passar almenys dues coses:
- Que necessitem una nova instància cada cop. Per exemple, les factories creen múltiples objectes.
- Que necessitem una única instància compartida. Aquesta situació es relaciona amb el patró singleton.
La inversió de control (IoC) és un principi de POO que cedeix a un contenidor o framework la tasca de controlar la creació d’instàncies d’objectes.
Tenim principalment dues formes d’implementar IoC:
- L’injecció de dependència (DI), on un contenidor pren el control i fa crides al nostre codi per proporcionar les dependències d’un objecte. Hi ha principalment dos tipus: de construcció i de setter.
- El Service Locator, que introdueix un nou objecte al nostre codebase, el Locator, que permet resoldre dependències d’una certa classe.
Aquestes tècniques es poden implementar per a subministrar instàncies úniques o múltiples. Per a permetre instàncies úniques, com serveis, utilitzen un mapa o diccionari d’instàncies accessibles pel nom de la classe.
L’objectiu d’aquestes tècniques és seguir el principi d’inversió de dependència: fem les dependències sobre abstraccions (interfícies). Això permet separar l’ús de la construcció, i podem substituir les implementacions sense afectar el codi.
Disseny per a la testabilitat
Les decisions de disseny afecten directament la facilitat amb què el codi es pot verificar. Un codi ben dissenyat és, quasi sempre, un codi fàcil de testejar — i les dificultats per escriure tests solen ser símptomes de problemes de disseny.
Per què el disseny condiciona la testabilitat
Un test unitari necessita tres coses: crear l’objecte, executar-lo amb unes entrades, i verificar el resultat. Quan alguna d’aquestes tres passa a ser difícil, el problema sol ser de disseny:
- Dependències ocultes: si un objecte crea internament les seves dependències (p. ex.
new DatabaseClient()dins d’un mètode), el test no pot substituir-les. La solució és la injecció de dependència — el mateix principi que ja hem vist a la secció anterior, però ara amb una motivació concreta: poder injectar test doubles. - Estat global i singletons: quan el comportament depèn d’estat compartit, els tests es contaminen entre ells. L’ordre d’execució passa a importar i els tests deixen de ser independents.
- Efectes secundaris escampats: si una funció envia un correu, escriu a disc i actualitza una base de dades, testejar-la requereix simular tot això. Empènyer els efectes secundaris cap a les fronteres del sistema (ports, adaptadors) permet que el gruix de la lògica sigui pura i testejable sense infraestructura.
Principis pràctics
- Separar lògica de infraestructura. La lògica de negoci hauria de poder executar-se sense base de dades, xarxa ni sistema de fitxers. Les dependències d’infraestructura es connecten a les fronteres, no al cor.
- Preferir funcions pures. Una funció que no té efectes secundaris i retorna sempre el mateix resultat per als mateixos paràmetres és trivialment testejable. No sempre és possible, però quan ho és, simplifica tant els tests com el raonament sobre el codi.
- Injectar les dependències. Si un objecte necessita un servei extern, rep-lo per constructor o per paràmetre — no el creï internament. Això permet substituir-lo per un test double (dummy, stub, mock o fake) en els tests.
- Mantenir els constructors simples. Un constructor que fa feina (connectar-se a un servei, llegir configuració, iniciar fils) dificulta la creació d’objectes en tests. El constructor hauria d’assignar dependències; la feina hauria de començar després.
La connexió amb l’arquitectura és directa: les fronteres descrites a la secció d’arquitectura hexagonal són els punts on es substitueixen implementacions reals per test doubles. Un sistema amb fronteres ben definides és un sistema fàcil de testejar.
Arquitectura i fronteres
Definició de frontera
L’arquitectura d’un sistema defineix com es divideix en components, quines són les fronteres o límits (boundaries) d’aquests components i com es comuniquen a través d’aquestes. Quan no hi ha fronteres parlem del monòlit.
El propòsit d’aquestes fronteres és facilitar el seu desenvolupament, gestió, manteniment i evolució. Una bona definició de les fronteres redueix l’acoblament, facilitant la flexibilitat de modificar unes parts sense afectar unes altres, i evita la degradació gradual de l’arquitectura. D’això se’n diu arquitectura evolutiva.
- Facilita el desenvolupament, perquè permet desenvolupar les parts de forma independent, a ritmes diferents.
- Facilita les proves, podem crear objectes que simulen comportament (test doubles), com dummies, stubs, mocks o fakes.
- Facilita els canvis i l’evolució, ja que podem canviar la implementació d’un comportament sense afectar la resta.
Les dependències entre components haurien de ser un graf acíclic dirigit. Això evita les dependències circulars, que augmenta l’acoblament dels components i limita la possibilitat de reutilitzar-los de forma individual.
Tipologia de fronteres
Tipus de separació
Existeixen dos enfocaments principals per crear fronteres en sistemes de programari: la separació horitzontal i la separació vertical.
- L’horitzontal crea les fronteres entre àrees tècniques del sistema. Per exemple, una API, la lògica de negoci i la comunicació amb la base de dades. Els canvis impliquen habitualment diferents capes del sistema. Pot ser un problema si les capes les gestionen diferents equips de desenvolupament.
- La vertical crea la frontera entre àrees funcionals del sistema. S’utilitza amb microserveis. Per exemple, la gestió d’usuaris o la creació de comandes. Els canvis en aquest tipus de separació són més àgils.
Mecanismes de separació
Les fronteres es poden implementar mitjançant quatre mecanismes diferents:
- Codi font: utilitzant classes i interfícies per poder comunicar-se mitjançant mètodes sense veure la implementació. Si es fa bé (actuant amb bona fe), permet aïllar les parts per permetre múltiples equips treballant. És l’únic mecanisme dels monòlits.
- Components vinculats dinàmicament: es fa una separació amb components desplegables, per exemple, arxius JAR. Es comuniquen amb crides a mètodes, i poden utilitzar el principi d’inversió de dependència per a establir les relacions entre ells.
- Processos locals: tenim processos locals que estan a la mateixa màquina. Poden comunicar-se utilitzant memòria compartida o sòcols. Permeten utilitzar diferents entorns de desenvolupament i tecnologies, sempre que es comparteixi el protocol.
- Serveis: permet que els serveis estiguin a diferents màquines i utilitzin la xarxa. No se sol compartir la base de dades (mala pràctica). S’estableixen protocols estàndards basats en la xarxa, com REST. Exemple dels microserveis.
Resumint, els mecanismes de separació es poden classificar segons el seu enfocament:
- Basats en interfícies: Codi font i components vinculats dinàmicament.
- Basats en protocols: Processos locals i serveis.
Disseny per contracte
La correcta definició de la frontera és essencial. El disseny per contracte és una forma de dissenyar formalment les interfícies dels components d’un software respecte de qui els crida, o clients. Aquest contracte té dues parts:
- Els requisits que demana el component als clients.
- Les promeses fetes pel component als clients.
Resumint, si el client compleix els requisits, el component promet complir el contracte que defineix.
Si canviem un contracte, volem que els clients no quedin afectats. Per assegurar-nos utilitzem la frase “no requerir més ni prometre menys”: si el canvi no requereix més dels clients ni promet menys, la nova especificació es compatible i no trencarà el funcionament del client.
Algunes pràctiques per a seguir el disseny per contracte:
-
Documentar el contracte amb comentaris, responent quins són els requisits i les promeses que es fan. En resum, explicant exactament què es fa sense haver de saber com. A Java es fa al javadoc de la classe i dels mètodes públics.
-
Validar arguments dels mètodes i constructors públics. És raonable no fer-ho amb els privats, ja que només es criden per la mateixa classe. D’això també se’n diu precondicions. A Java, aquesta validació sol produir excepcions unchecked com IllegalArgumentException, NullPointerException o IllegalStateException que no cal obligatòriament documentar.
-
Validar les promeses que fa un mètode al seu client. D’això també se’n diu postcondicions. Per exemple, comprovar valors, tipus de retorn, errors i excepcions que es produeixen. Es pot fer just abans d’acabar un mètode públic.
-
Validar l’estat de l’objecte. D’això també se’n diu invariants de classe. Implica mantenir una sèrie de condicions sobre l’estat entre les crides a mètodes públics. Si l’objecte és immutable, només cal validar l’estat al constructor. Si és mutable, a cada mètode que canvia l’estat.
-
Opcionalment, aspectes sobre el rendiment (temps i espai).
Estratègia de gestió d’errors
El disseny per contracte defineix què promet un component i què exigeix. L’estratègia de gestió d’errors defineix què passa quan aquestes promeses no es poden complir. És una decisió de disseny que afecta tota l’arquitectura i que convé prendre explícitament — si no es fa, cada part del sistema gestionarà els errors de manera diferent.
Fail-fast vs. defensiu
Hi ha dos enfocaments fonamentals:
- Fail-fast: quan es detecta un estat invàlid, el sistema falla immediatament i de forma visible. Això facilita el debugging perquè l’error es manifesta a prop de la causa. Les precondicions del disseny per contracte segueixen aquest principi: si un argument és invàlid, es llança una excepció al moment.
- Defensiu: el sistema intenta continuar operant malgrat condicions inesperades, amb valors per defecte, reintents o degradació de funcionalitat. Adequat per a sistemes que han de mantenir disponibilitat (p. ex. un frontend que mostra dades parcials en lloc de fallar completament).
La majoria de sistemes combinen els dos: fail-fast a la lògica interna (on un error és un bug) i defensiu a les fronteres (on les entrades són impredictibles).
Tipus d’errors
No tots els errors tenen el mateix tractament. Una classificació útil:
- Errors de programació (bugs): precondicions violades, índexs fora de rang, nulls inesperats. No s’han de gestionar — s’han de corregir. Fail-fast amb una excepció no recuperable.
- Errors de domini: regles de negoci que no es compleixen (saldo insuficient, usuari no autoritzat). Formen part del contracte del component i s’han de comunicar explícitament al client — amb excepcions de domini, tipus de retorn que representin el fracàs, o codis d’error documentats.
- Errors d’infraestructura: xarxa no disponible, disc ple, servei extern caigut. Són temporals i sovint recuperables amb reintents. S’han de gestionar a les fronteres del sistema (adaptadors), no al cor de la lògica.
On capturar els errors
Un error s’hauria de capturar al lloc on es pot prendre una decisió útil sobre què fer. Capturar-lo abans produeix codi que emmascarà problemes; capturar-lo després complica la depuració.
- A les fronteres d’entrada: controladors, handlers d’API, punts d’entrada de CLI. Aquí es transformen errors interns en respostes adequades pel client (codis HTTP, missatges d’error).
- A les fronteres de sortida: adaptadors de base de dades, clients HTTP, sistemes de fitxers. Aquí es decideix si reintentar, degradar o propagar.
- No al mig: la lògica de negoci hauria de propagar errors, no capturar-los. Un
try-catchgenèric dins de la lògica de domini sol ser un senyal que falta una frontera ben definida.
Estils d’arquitectura
L’arquitectura d’una solució pot ser monolítica o distribuïda. La distribuïda sol basar-se en client-servidor, tot i que també tenim la solució peer-to-peer.
A continuació revisarem alguns dels estils d’arquitectura.
- Multicapa (multitier). Basada en la client-servidor, estableix una sèrie de capes independents que permeten desenvolupar cada capa de forma prou independentment. Les capes habituals són:
- Presentació
- Aplicació
- Dades
- Microkernel. Es tracta d’una arquitectura amb una funcionalitat central que permet afegir funcionalitats addicionals en forma de plug-ins.
- Orientada a esdeveniments (event-driven). Basada en la comunicació asíncrona d’esdeveniments. Associada al concepte de “event-broker”, un middleware que s’encarrega d’encaminar esdeveniments entre sistemes que implementen el patró productor-consumidor. Els missatges poden ser guardats a cues fins a ser processats.
- Microserveis. Es tracta d’un ecosistema de serveis d’un propòsit senzill que es comuniquen mitjançant un gateway d’APIs. Cada servei utilitza les seves pròpies dades.
Patrons d’arquitectura
L’arquitectura hexagonal és un model arquitectural que permet separat el core de negoci (o domini) de la infraestructura (UI, base de dades, APIs, frameworks, etc.). Ho fa proposant els adaptadors i els ports.
Els adaptadors fan la interacció de la nostra aplicació cap al món. Tenim dos tipus:
- Els primaris: puts d’entrada de l’aplicació, operats principalment pels usuaris (UI, API Rest, CLIs).
- Els secundaris: actors secundaris com les bases de dades o serveis de tercers.
Si volem seguir el principi d’inversió de dependència, les capes internes no poden dependre de les externes. O sigui, les dependències han de ser de fora a dins: dels adaptadors cap al core.
Els ports són fronteres abstractes a l’exterior, per exemple, utilitzant interfícies, i els adaptadors són implementacions concretes dels ports, habitualment injectades:
- Els adaptadors primaris depenen de ports d’entrada, i dirigeixen l’aplicació (API, GUI, CLI).
- Els adaptadors secundaris implementen ports de sortida, i són dirigits per l’aplicació (BBDD, API clients).
DDD (Domain-drive design) és una estratègia de disseny de software focalitzat en el modelatge del software, i que té l’objectiu de replicar una àrea temàtica o domini (domain) gràcies als coneixements dels experts d’aquesta àrea.
Dins del DDD hi trobem diversos tipus de models, com les entitats, que tenen identitat, o les value objects, objectes immutables sense identitat. També hi ha aggregates, que són altres models dirigits per una entitat arrel, i que són unitats de consistència, concurrència i distribució.
De vegades hem de comunicar el nostre domini amb altres externs, que provoquen una dependència externa. És una mala estratègia permetre que es filtri aquest model extern cap al nostre, i la forma d’evitar-ho és construir una capa anti-corrupció. La idea tècnica es pot implementar utilitzant els patrons Facade i Adapter.
L’arquitectura clean afegeix el concepte d’entitats i casos d’ús al core. Les dues es basen en el DDD. Les capes que defineix, de més interna a més externa són:
- Les entitats: objectes de negoci amb comportament
- Els casos d’ús (use cases): mapejat de la funcionalitat de les user stories
- Els adaptadors de interfícies (controladors, vistes, presentadors)
- Els frameworks i eines (BBDD, serveis de tercers)
El principi de la regla de dependència diu que cap capa interna ha de dependre d’una externa. Això es manifesta de la següent forma:
- Sense dependències:
- Les entitats no depenen de ningú.
- Dependències cap a dins:
- Els casos d’ús depenen de les entitats, la capa més interna.
- Els adaptadors d’entrada depenen dels casos d’ús.
- Dependències cap a fora, però abstractes (inversió de dependència):
- Els casos d’ús depenen dels adaptadors de sortida. Per exemple, per fer persistència.

El diagrama mostra una arquitectura hexagonal amb conceptes DDD (clean). Les fletxes negres indiquen dependència d’una interfície i les blanques, implementació.
Cal no confondre la direcció de les dependències amb el flux de control. Per exemple, un flux de control d’una aplicació MVP podria ser controlador => cas d'ús => entitats => cas d'ús => presentador.
Finalment, cal dir que el cablejat dels components de l’arquitectura es fa des de fora de l’aplicació, creant les dependències, connectant-les i iniciant l’aplicació. El patró es diu composition root. Aquí sol utilitzar-se la injecció de constructor i, si hi ha contenidors que fan inversió de control, és l’únic lloc on haurien d’aparèixer.
Diagrames d’arquitectura
El model C4 és un model que permet visualitzar l’arquitectura d’una solució. Descriu quatre nivells de diagrames, de més generals a més concrets:
- De context de sistema: mostra el sistema software i el seu context al voltant.
- De contenidor: mostra els contenidors dins d’un sistema software i com es relacionen.
- De component: mostra els components d’un contenidor i les seves interaccions.
- De codi: mostra la implementació del codi amb diagrames UML, diagrames ER o similars.
Hi ha quatre abstraccions que poden aparèixer en el nostre diagrama:
- La persona: actors o rols d’un sistema software.
- El sistema software: el sistema que estàs modelant, i que es relaciona amb altres sistemes externs.
- Un contenidor: una aplicació o un magatzem de dades. Per exemple:
- Un backend web
- Un frontend web
- Una aplicació desktop (client)
- Una app mòbil
- Una aplicació de consola o script
- Una funció serverless (cloud)
- Una base de dades
- Un magatzem al núvol
- El sistema d’arxius
- Un component: grup de funcionalitats encapsulada darrere d’una interfície (o contracte). Tots els components d’un contenidor solen executar-se en el mateix espai de processos o màquina virtual.
Alguns consells per a dibuixar diagrames:
- No hi ha una forma estàndard de fer-ho.
- Han de reflectir la realitat. Abstraccions primer, notació després.
- Conté blocs per als diferents components (SoC). Alguna cosa modular amb una interfície/fronteres. Els components contenen codi.
- Agrupar els components que treballen junts amb caixes (contenidors): DB schema, app mòbil, backend server-side app, console app, windows service.
- Ha d’incloure les dades, la lògica de negoci i la interfície d’usuari.
- Fletxes sempre direccionals per indicar la direcció del flux (petició) amb descripció.
- No cal afegir respostes (fletxes de tornada) dient OK, només qui les origina.
- Ser consistent en forma i color, evitar acrònims no coneguts.
Bones pràctiques per escriure codi
Estàs escrivint codi per llegir-lo en el futur, o bé per un altre…
- Les classes han de ser petites, per sota de 500 línies, i han de tenir un nombre limitat de mètodes
- Els mètodes han de ser petits, per sota de 30 línies, i han de fer una feina concreta
- Has d’escriure el codi perquè s’expliqui a ell mateix, però on no arribis, utilitza comentaris
- No facis línies massa llargues, com a molt de 120 caràcters
- Manté baix el nivell de sagnat del codi, i intenta no superar els 3-4 nivells
- Anomena les classes, els mètodes i les variables amb els criteris ja explicats
- Decideix el teu estil i segueix-lo de forma consistent
Com anomenar
Cada llenguatge de programació té les seves regles tipogràfiques i gramaticals per anomenar els seus elements. Les regles tipogràfiques varien segons el llenguatge; els criteris gramaticals solen ser més universals. Per exemple, les regles tipogràfiques de Java són:
- Els paquets s’escriuen amb lletres minúscules, amb components separats per punts de més genèric a més específic:
org.junit.jupiter.api. - Les classes tenen una o més paraules, sense abreviatures, amb cadascuna en majúscules:
List,FutureTask. - Els mètodes i els camps segueixen la mateixa regla, però la primera lletra ha de ser minúscula:
remove,ensureCapacity. - Les variables locals segueixen el mateix criteri, però permeten abreviatures:
i,houseNum. - Els paràmetres de tipus són una lletra, on T és un tipus qualsevol, E un tipus d’element d’una col·lecció, K/V una clau i un valor, X una excepció, i R un tipus de retorn.
Els criteris gramaticals solen ser més flexibles:
- Les classes són noms o frases nominals:
Thread,PriorityQueue. - Les interfícies poden anomenar-se com les classes o bé com un adjectiu que acaba amb
ableoible:Runnable,Iterable. - Els mètodes que realitzen alguna acció són verbs o frases verbals:
append,drawImage. - Els mètodes que retornen un boolean solen començar per
isohas, i després poden seguir amb un nom, frase nominal o qualsevol paraula o frase que funcioni com adjectiu:isDigit,isProbablePrime,isEmpty,isEnabled,hasSiblings. - Els mètodes que retornen un valor no boolean o un atribut de l’objecte s’anomenen amb un nom o frase nominal, o una frase que comença per get:
size,hashCode,getTime. - Els mètodes getters i setters són una construcció obsoleta en molts casos, utilitzar-los amb cautela.
- Els camps de tipus boolean solen anomenar-se com l’accessor, però ometent ‘is’ o ‘has’:
initialized,started. - Els camps d’altres tipus no boolean solen ser noms o frases nominals:
height,digits,bodyStyle.
REST APIs
Criteris de disseny
- Identificar els recursos que són part de l’API i els seus IDs.
- Definir l’URI del recurs, o també anomenades endpoints. Utilitzen noms (no verbs).
- Quan es retorna un sol recurs es retorna informació completa. Quan es retornen col·leccions se sol reduir la informació a l’estrictament necessària.
- Assignar els mètodes adequats.
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. Tingueu 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 es 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 |
Bones pràctiques
- Utilitzar JSON com a format per a enviar i rebre dades (cos).
- Utilitzar noms en lloc de verbs per als endpoints.
- Els endpoints de col·leccions s’han d’anomenar amb noms plurals.
- No utilitzar verbs a la URI. P. ex. si cal fer una acció, fer-ho sobre un nom amb un paràmetre ‘action’ en la query.
- Envia codis d’estat per a gestionar els errors.
- Utilitza filtres, ordenació i paginació a les dades.
- Genera una bona documentació de l’API (OpenAPI).
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’estat limitat). També pots retornar informació al cos de la resposta que pugui ajudar al client a gestionar el resultat.
Referències
- The C4 model for visualising software architecture
- PlantUML
- UML diagrams with plantUML
- Inversion of Control Containers and the Dependency Injection pattern
- Best Practices for Designing and Implementing a Library in Java
- Dependency injection
- Software Architecture Boundaries
- Acyclic Dependencies Principle
- Top 20 Design Heuristics
- Design Principles (Bob Martin)
- Listing of Arthur Riel’s heuristics
- Design by Contract
- Hexagonal architecture
- The Clean Architecture
- Hexagonal Architecture with Java and Spring
- Domain-centric Architectures (Clean and Hexagonal) for Dummies
- RESTful web API design
- REST API Design: Filtering, Sorting, and Pagination
- What is REST
- OpenAPI
- Architectural boundaries
- Simpler Encapsulation with Immutability
- Effective Java by Joshua Bloch