Durant deu mòduls hem construït la xarxa de bicicletes elèctriques de Ribalta capa per capa. Primer un endpoint que retornava quatre estacions en memòria; després el contenidor de Spring i els seus beans; el contracte REST amb els seus DTOs i els seus errors; la persistència sobre PostgreSQL amb Flyway; la seguretat amb JWT i regles per dada; les proves; l'operabilitat; el desplegament; i per fi l'observabilitat, el criteri i la neteja. Cada lliçó va afegir una peça i va mirar gairebé sempre cap a la següent.
Mai no hem mirat CicloUrbana sencera. Aquesta lliçó ho fa: l'arquitectura completa en un diagrama, l'estructura real del repositori, el viatge d'una sola petició des del balancejador fins a la mètrica —citant a cada pas on es va estudiar—, el mapa de què va construir cada mòdul, les decisions de disseny amb les seves alternatives i les seves contrapartides, el pom.xml i la configuració finals, com posar el projecte en marxa des de zero i cap a on continuar portant-lo. Si has seguit el curs, aquí ho reconeixeràs tot; el que és nou és veure-ho junt.
Contingut
- L'arquitectura final
- El repositori complet
- El viatge d'una petició:
POST /api/v1/lloguers - Mapa del curs: què va construir cada mòdul
- Decisions de disseny i les seves alternatives
- El
pom.xmlfinal - La configuració final
- Posar en marxa el projecte des de zero
- Recorregut d'extrem a extrem amb peticions
.http - Llista de comprovació final de posada en producció
- Cap a on continuar amb aquest projecte
- Errors Comuns i Consells
- Exercicis
- L'arquitectura final
flowchart TB
subgraph CLI["Clients"]
M["App mòbil<br/>ciutadans de Ribalta"]
P["Panell de l'ajuntament<br/>operaris i administració"]
end
subgraph BORDE["Vora"]
ALB["ALB / Ingress<br/>TLS, límit de taxa · 08-03, 08-04"]
end
subgraph APP["CicloUrbana · 3 rèpliques · 07-04, 08-04"]
F["Cadena de filtres<br/>FiltreRastreig · FiltreAutenticacioJwt · 03-06, 05-04"]
C["Controladors REST<br/>DTOs i validació · 03-02, 03-05"]
S["Serveis<br/>@Transactional · @PreAuthorize · 04-07, 05-05"]
R["Repositoris<br/>Spring Data JPA · 04-05"]
T["Tasques i asincronia<br/>@Scheduled · @Async · 07-03"]
AC["Actuator<br/>sondes, mètriques · 07-01, 09-03"]
F --> C --> S --> R
S --> T
end
subgraph DAT["Dades"]
BD[("PostgreSQL 16<br/>esquema amb Flyway · 04-08")]
RD[("Redis<br/>memòria cau distribuïda · 09-02")]
end
subgraph EXT["Serveis externs"]
PG["Passarel·la de pagaments<br/>RestClient + Resilience4j · 07-06"]
end
subgraph OBS["Observabilitat · mòdul 9"]
PR["Prometheus"]
LO["Loki"]
TE["Tempo"]
OT["OTel Collector"]
GR["Grafana"]
OT --> TE
PR --> GR
LO --> GR
TE --> GR
end
M --> ALB
P --> ALB
ALB --> F
R --> BD
S --> RD
T --> PG
AC -.->|"scrape /actuator/prometheus"| PR
APP -.->|"logs JSON a stdout"| LO
APP -.->|"OTLP"| OT
Quatre observacions sobre el dibuix, perquè un diagrama sense lectura és decoració.
L'aplicació és un sol desplegable. Els cinc blocs de dins no són serveis: són capes i mòduls dins del mateix procés. És el monòlit modular de 07-05, i les fletxes entre ells són crides a mètodes, no crides de xarxa.
Les dependències apunten cap endins i cap avall. Filtres → controladors → serveis → repositoris → base de dades. Cap fletxa no puja. És la regla de dependència de 10-01, verificada automàticament amb les regles d'ArchUnit de 10-03.
L'observabilitat és un lateral, no una capa. No és al camí de la petició: l'aplicació publica mètriques, logs i traces, i uns altres els recullen. Si Prometheus cau, CicloUrbana continua funcionant.
L'única cosa que pot tombar el servei és PostgreSQL. La memòria cau de Redis degrada a Caffeine, la passarel·la té tallacircuits i respatller de cobrament diferit, i la resta és opcional. Aquesta asimetria és deliberada i és darrere del fet que la sonda readiness sí que consulti la base de dades i la de liveness no.
- El repositori complet
ciclourbana/
├── .github/workflows/
│ ├── ci.yml # compilar, provar, JaCoCo, Spotless · 08-05
│ └── cd.yml # imatge, Trivy, desplegament a pre i prod · 08-05
├── helm/ciclourbana/
│ ├── Chart.yaml · values.yaml · values-pre.yaml · values-prod.yaml
│ └── templates/ # deployment, service, ingress, hpa, job-migracio · 08-04
├── observabilitat/ # prometheus.yml, loki.yml, tempo.yml,
│ # otel-collector.yml, taulers de Grafana · 09-04, 09-06
├── carrega/carrega-hora-punta.js # escenari de k6 · 09-01
├── peticions/ciclourbana.http # col·lecció d'extrem a extrem · apartat 9
├── src/main/java/com/ciclourbana/
│ ├── CicloUrbanaApplication.java # arrel de l'escaneig · 01-04
│ ├── comu/ # transversal
│ │ ├── ConfiguracioComuna.java # Clock, RestClient.Builder · 02-01
│ │ ├── EntitatAuditable.java # createdAt/By, updatedAt/By · 04-03
│ │ ├── PaginaResponse.java # embolcall propi de Page · 04-05
│ │ ├── FiltreRastreig.java # rastreId en MDC i capçalera · 03-06, 09-06
│ │ ├── GestorGlobalExcepcions.java # ProblemDetail RFC 7807 · 03-06
│ │ ├── CicloUrbanaException.java + jerarquia · 03-06
│ │ └── MetriquesCicloUrbana.java # mètriques de negoci · 09-03
│ ├── estacions/ # Estacio, Repositori, Service,
│ │ └── dto/ # Controller, Mapper + DTOs · mòduls 3 i 4
│ ├── bicicletes/ # Bicicleta, EstatBicicleta,
│ │ └── dto/ # @MatriculaBicicleta, RevisorFlota
│ ├── lloguers/ # Lloguer, CalculadoraTarifa i les tres
│ │ └── dto/ # tarifes, SelectorTarifa, CaducadorLloguers
│ ├── usuaris/ # Usuari, rols, moneder
│ ├── seguretat/ # ConfiguracioSeguretat, ServeiJwt,
│ │ # FiltreAutenticacioJwt, UsuariAutenticat,
│ │ # SeguretatLloguers, AuditoriaSeguretat · mòdul 5
│ ├── pagaments/ # ClientPassarelaPagaments + Resilience4j · 07-06
│ └── config/ # ConfiguracioTasques, ConfiguracioAsincronia,
│ # ConfiguracioCache, ConfiguracioOpenApi
├── src/main/resources/
│ ├── application.yml # comú a tots els entorns · 07-02
│ ├── application-{dev,test,pre,prod}.yml # només les diferències
│ ├── logback-spring.xml # JSON a stdout amb rastreId · 09-05
│ └── db/migration/V1__…V9__…sql # esquema versionat · 04-08
├── src/test/java/com/ciclourbana/ # mirall de l'arbre de producció · 06-01
│ ├── …Test.java # unitàries i llesques (Surefire)
│ ├── …IT.java # integració amb Testcontainers (Failsafe)
│ ├── ProvaIntegracioBase.java # context compartit · 06-05
│ └── ReglesArquitecturaTest.java # ArchUnit · 10-03
├── Dockerfile # multietapa, usuari ciclo · 07-04
├── docker-compose.yml # app + PostgreSQL + Redis
├── docker-compose.observabilitat.yml # Prometheus, Grafana, Loki, Tempo, Collector
├── .dockerignore · .gitignore · .env.exemple
├── mvnw · mvnw.cmd · .mvn/
└── pom.xmlDues coses mereixen atenció. src/test és un mirall exacte de src/main, cosa que permet accedir a membres amb visibilitat de paquet sense obrir la classe de producció i fa l'arbre navegable. I la separació *Test / *IT no és cosmètica: Surefire executa els primers a ./mvnw test en segons, i Failsafe els segons a ./mvnw verify amb PostgreSQL real.
- El viatge d'una petició:
POST /api/v1/lloguers
POST /api/v1/lloguersLa Marta obre l'aplicació a l'estació «Plaça Major», tria una bicicleta i prem «llogar». Això és el que passa.
sequenceDiagram
autonumber
participant M as App mòbil
participant L as ALB / Ingress
participant FS as Filtres de Spring Security
participant DS as DispatcherServlet
participant CT as LloguerController
participant SV as LloguerService (proxy)
participant BD as PostgreSQL
participant EV as NotificadorLloguer
participant PG as Passarel·la de pagaments
M->>L: POST /api/v1/lloguers · Bearer …
L->>FS: TLS acabat · X-Forwarded-*
FS->>FS: FiltreRastreig: span arrel + rastreId al MDC
FS->>FS: FiltreAutenticacioJwt: valida signatura i exp
FS->>DS: SecurityContext amb UsuariAutenticat
DS->>CT: binding a IniciarLloguerRequest
CT->>CT: @Valid: matrícula, ids, tarifa
CT->>SV: iniciar(peticio)
SV->>SV: @PreAuthorize: isAuthenticated()
SV->>BD: BEGIN (READ_COMMITTED)
SV->>BD: SELECT … FOR UPDATE (millor bicicleta)
SV->>SV: SelectorTarifa: calcula l'import
SV->>BD: INSERT lloguer · UPDATE bicicleta
SV->>BD: COMMIT
SV-->>CT: LloguerResponse (mapejat dins de la tx)
CT-->>M: 201 Created · Location · X-Rastre-Id
Note over EV,PG: AFTER_COMMIT, ja fora del fil HTTP
SV->>EV: esdeveniment LloguerIniciat
EV->>PG: autoritzar cobrament (@Async + tallacircuits)
El detall pas a pas, amb la seva lliçó:
| # | Què passa | Peça | Lliçó |
|---|---|---|---|
| 1 | El TLS acaba al balancejador, que reenvia amb X-Forwarded-Proto |
ALB / Ingress | 08-03, 08-04 |
| 2 | La petició entra a la cadena de filtres de servlet | SecurityFilterChain |
05-01 |
| 3 | FiltreRastreig obre el span arrel i posa rastreId/spanId al MDC |
FiltreRastreig |
03-06, 09-06 |
| 4 | FiltreAutenticacioJwt valida la signatura HS256 i la caducitat, i construeix l'UsuariAutenticat |
ServeiJwt |
05-04 |
| 5 | AuthorizationFilter comprova que la ruta exigeix autenticació i que la té |
authorizeHttpRequests |
05-02 |
| 6 | El DispatcherServlet resol el handler i deserialitza el cos amb Jackson |
@RequestBody |
03-02 |
| 7 | Bean Validation comprova el DTO: @NotNull, @Positive, @MatriculaBicicleta |
IniciarLloguerRequest |
03-04 |
| 8 | El controlador delega en el servei; aquí no hi ha lògica | LloguerController |
03-03 |
| 9 | El proxy del servei avalua @PreAuthorize abans d'entrar |
@EnableMethodSecurity |
05-05 |
| 10 | El mateix proxy obre la transacció: connexió del pool, autoCommit=false |
@Transactional |
04-07 |
| 11 | SELECT … FOR UPDATE reserva la millor bicicleta disponible i serialitza la cursa |
@Lock(PESSIMISTIC_WRITE) |
04-07 |
| 12 | SelectorTarifa tria la CalculadoraTarifa de l'usuari i calcula l'import |
TarifaEstandard i germanes |
02-02 |
| 13 | INSERT del lloguer i UPDATE de la bicicleta per dirty checking |
Spring Data JPA | 04-05 |
| 14 | El mapatge a LloguerResponse passa dins de la transacció |
LloguerMapper |
03-05 |
| 15 | COMMIT; la connexió torna al pool |
HikariCP | 04-02 |
| 16 | @TransactionalEventListener(AFTER_COMMIT) dispara l'esdeveniment ja confirmat |
LloguerIniciat |
04-07 |
| 17 | El cobrament surt en un altre fil, amb MDC i SecurityContext propagats |
@Async("executorCorreu") |
07-03 |
| 18 | La crida a la passarel·la porta traceparent, temps d'espera i tallacircuits |
RestClient + Resilience4j |
07-06 |
| 19 | El controlador respon 201 amb Location i X-Rastre-Id |
ResponseEntity |
03-03 |
| 20 | MetriquesCicloUrbana incrementa ciclourbana.lloguers.iniciats{tarifa,estacio} |
Micrometer | 09-03 |
| 21 | S'escriu una línia JSON a stdout amb el rastreId, i el span s'exporta per OTLP |
Logback + Tracing | 09-05, 09-06 |
El que es veu en ajuntar-ho. Els passos 9 i 10 els fa el mateix proxy, i per això el parany de l'autoinvocació s'endú per davant la transacció i la comprovació de permisos alhora. El pas 14 no és un detall d'estil: amb open-in-view: false és l'única manera que la resposta es construeixi sense LazyInitializationException. I els passos 16 a 18 són la raó que la Marta rebi el seu 201 en desenes de mil·lisegons mentre el cobrament triga gairebé dos segons en un altre fil.
- Mapa del curs: què va construir cada mòdul
| Mòdul | Què es va construir | Classes principals | Fitxers del repositori |
|---|---|---|---|
| 1 Introducció | El projecte i el seu cicle de vida | CicloUrbanaApplication, CarregadorEstacionsDemo |
pom.xml, mvnw, src/main/java, application.properties |
| 2 Conceptes bàsics | El contenidor, la configuració tipada i el sistema de tarifes | CalculadoraTarifa, TarifaEstandard, TarifaEstudiant, SelectorTarifa, TarifesProperties, XarxaProperties |
comu/ConfiguracioComuna.java, application.yml |
| 3 REST | El contracte públic complet | EstacioController, DTOs record, EstacioMapper, GestorGlobalExcepcions, FiltreRastreig, @MatriculaBicicleta |
*/dto/, comu/, ConfiguracioOpenApi |
| 4 Dades | La persistència real | Estacio, Bicicleta, Lloguer, EntitatAuditable, repositoris, PaginaResponse |
db/migration/V1…V9, application.yml (Hikari, JPA) |
| 5 Seguretat | Autenticació i autorització | ConfiguracioSeguretat, ServeiJwt, FiltreAutenticacioJwt, UsuariAutenticat, SeguretatLloguers, AuditoriaSeguretat |
seguretat/, application-prod.yml |
| 6 Proves | La xarxa de seguretat | TarifaEstandardTest, EstacioControllerTest, LloguerFluxCompletIT, ProvaIntegracioBase |
src/test/, JaCoCo i Failsafe al pom.xml |
| 7 Operabilitat | Iniciativa pròpia i empaquetament | CaducadorLloguers, RecalculadorOcupacio, RevisorFlota, NotificadorLloguer, DecoradorMdc, ClientPassarelaPagaments |
Dockerfile, docker-compose.yml, V7__shedlock.sql |
| 8 Desplegament | El lliurament automàtic | — | .github/workflows/, helm/ |
| 9 Rendiment | Veure i mesurar | MetriquesCicloUrbana, FiltreComptadorConsultes, EstacioResum |
observabilitat/, carrega/, logback-spring.xml |
| 10 Criteri | La reflexió i les regles automàtiques | ReglesArquitecturaTest, TarifaAmbRecarrecPerExces |
Spotless al pom.xml, docs/decisions/ |
- Decisions de disseny i les seves alternatives
Tota decisió d'arquitectura compra alguna cosa i en paga una altra. Aquestes són les sis principals de CicloUrbana, amb el que van costar.
| Decisió | Alternativa descartada | Per què | Contrapartida que assumim |
|---|---|---|---|
| Monòlit modular | Microserveis | Un equip, un domini acoblat, transaccions locals i una traça de pila que ho explica tot | Escalar vol dir replicar l'aplicació sencera, encara que només els lloguers tinguin càrrega |
| JWT sense estat | Sessió amb galeta | Tres rèpliques sense sessió enganxosa ni magatzem compartit; el mòbil no gestiona galetes amb comoditat | Un token no es pot revocar abans que caduqui; cal caducitat curta i refresc rotatori |
| Flyway | ddl-auto: update |
L'esquema es revisa a la pull request, es versiona i es reprodueix igual als quatre entorns | Cada canvi d'entitat exigeix escriure la migració a mà; i reanomenar costa tres desplegaments |
| MapStruct | ModelMapper o mapatge manual | unmappedTargetPolicy=ERROR converteix el camp oblidat en error de compilació, sense cost en execució |
Un processador d'anotacions més i codi generat que cal saber llegir |
| Caffeine + Redis | Només Redis | Caffeine respon en nanosegons i sense xarxa per al que tolera divergència de segons | Dos nivells de memòria cau per invalidar; hi ha dades que poden diferir uns segons entre rèpliques |
| Testcontainers | H2 en mode PostgreSQL | Índexs parcials, tipus, funcions i bloqueigs es comporten de debò com en producció | La suite d'integració triga minuts i necessita Docker a la màquina i a la canonada |
I dues decisions menors que il·lustren la mateixa manera de pensar. open-in-view: false compra que cap consulta no es dispari durant la serialització, i paga amb l'obligació de mapejar a DTO dins de la transacció —cosa que, de passada, reforça una pràctica que volíem igualment—. La denegació per defecte compra que un oblit produeixi un 403 visible en lloc d'un endpoint obert, i paga amb el fet que cada ruta nova exigeixi una regla explícita.
Cap d'aquestes sis no és universalment correcta. El que les fa defensables és que es van prendre amb la contrapartida a la vista i van quedar escrites.
- El
pom.xml final
pom.xml finalLes dependències acumulades, agrupades pel mòdul que les va introduir. La columna de versió és la informació important: només porten versió pròpia les que el BOM de Spring Boot no gestiona, i són exactament les que cal revisar a mà a cada actualització.
| Mòdul | Dependències (org.springframework.boot: llevat d'indicació) |
Àmbit | Versió |
|---|---|---|---|
| 3 REST | spring-boot-starter-web, spring-boot-starter-validation |
compile | BOM |
| 3 | org.springdoc:springdoc-openapi-starter-webmvc-ui |
compile | 2.6.0 |
| 3 | org.mapstruct:mapstruct |
compile | 1.6.3 |
| 4 Dades | spring-boot-starter-data-jpa, org.flywaydb:flyway-core, flyway-database-postgresql |
compile | BOM |
| 4 | org.postgresql:postgresql |
runtime | BOM |
| 5 Seguretat | spring-boot-starter-security |
compile | BOM |
| 5 | io.jsonwebtoken:jjwt-api (+ jjwt-impl i jjwt-jackson en runtime) |
mixt | 0.12.6 |
| 7 Operació | spring-boot-starter-actuator, spring-boot-starter-aop |
compile | BOM |
| 7 | net.javacrumbs.shedlock:shedlock-spring + shedlock-provider-jdbc-template |
compile | 5.16.0 |
| 7 | io.github.resilience4j:resilience4j-spring-boot3 |
compile | 2.2.0 |
| 9 Rendiment | spring-boot-starter-cache, com.github.ben-manes.caffeine:caffeine, spring-boot-starter-data-redis |
compile | BOM |
| 9 | io.micrometer:micrometer-registry-prometheus |
runtime | BOM |
| 9 | io.micrometer:micrometer-tracing-bridge-otel, io.opentelemetry:opentelemetry-exporter-otlp |
compile | BOM |
| 9 | net.logstash.logback:logstash-logback-encoder |
compile | 8.0 |
| 6 Proves | spring-boot-starter-test, spring-security-test, spring-boot-testcontainers, org.testcontainers:postgresql |
test | BOM |
| 10 Criteri | com.tngtech.archunit:archunit-junit5 |
test | 1.3.0 |
| 1 Desenvolupament | spring-boot-devtools (runtime), spring-boot-configuration-processor |
optional | BOM |
I la part del pom.xml que de debò cal llegir amb atenció, perquè és la que trenca la construcció davant d'un problema concret:
<parent> <!-- Mòdul 1: versions coherents -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
</parent>
<groupId>com.ciclourbana</groupId>
<artifactId>ciclourbana</artifactId>
<version>2.4.0</version>
<properties><java.version>21</java.version></properties>
<build>
<plugins>
<plugin><groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId></plugin>
<plugin> <!-- MapStruct: el camp oblidat no compila (03-05) -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path><groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version></path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Amapstruct.unmappedTargetPolicy=ERROR</arg>
<arg>-Amapstruct.defaultComponentModel=spring</arg>
<arg>-parameters</arg> <!-- necessari per a #idUsuari a SpEL (05-05) -->
</compilerArgs>
</configuration>
</plugin>
<plugin> <!-- Failsafe: les *IT a verify, no a test (06-01) -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions><execution>
<goals><goal>integration-test</goal><goal>verify</goal></goals>
</execution></executions>
</plugin>
<plugin> <!-- JaCoCo: mapa del vermell, no puntuació (06-01) -->
<groupId>org.jacoco</groupId><artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution><goals><goal>prepare-agent</goal></goals></execution>
<execution><phase>verify</phase><goals><goal>report</goal></goals></execution>
</executions>
</plugin>
<plugin> <!-- Spotless: l'estil no es discuteix (10-03) -->
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId><version>2.44.0</version>
<configuration><java><palantirJavaFormat/><removeUnusedImports/></java></configuration>
<executions><execution><phase>validate</phase>
<goals><goal>check</goal></goals></execution></executions>
</plugin>
<plugin> <!-- Dependency-Check: falla davant de severitat alta (05-05) -->
<groupId>org.owasp</groupId><artifactId>dependency-check-maven</artifactId>
<version>10.0.4</version>
<configuration><failBuildOnCVSS>7</failBuildOnCVSS></configuration>
</plugin>
</plugins>
</build>Els sis plugins no són adorn: cadascun trenca la construcció davant d'un problema concret —un camp sense mapejar, una prova d'integració fallida, codi sense formatar, una vulnerabilitat alta—. És la idea de fons de 10-01: moure la comprovació del cap d'una persona a la construcció.
- La configuració final
# application.yml — comú a tots els entorns
spring:
application:
name: ciclourbana # es converteix en service.name de les traces (09-06)
threads:
virtual:
enabled: true # fils virtuals de Java 21 (07-03)
jackson:
default-property-inclusion: non_null
deserialization: { fail-on-unknown-properties: false } # tolerància del contracte (03-05)
datasource:
url: ${URL_BASE_DADES} # sense valor per defecte: obligatori (10-01)
username: ${USUARI_BASE_DADES}
password: ${CONTRASENYA_BASE_DADES}
hikari:
pool-name: ciclourbana-pool
maximum-pool-size: 10 # (nuclis × 2) + discos (09-01)
minimum-idle: 10
connection-timeout: 3000 # fallar ràpid, no encuar
leak-detection-threshold: 20000
jpa:
open-in-view: false # decisió estructural del mòdul 4
hibernate: { ddl-auto: validate } # l'esquema el governa Flyway
properties:
hibernate:
jdbc.batch_size: 50
order_inserts: true
batch_versioned_data: true
flyway: { enabled: true, locations: classpath:db/migration }
cache:
type: caffeine
caffeine: { spec: "maximumSize=1000,expireAfterWrite=10m,recordStats" }
task:
execution:
pool: { core-size: 8, max-size: 24, queue-capacity: 200 }
thread-name-prefix: async-ciclo-
shutdown: { await-termination: true, await-termination-period: 30s }
scheduling:
pool: { size: 4 } # el planificador per defecte té UN fil (07-03)
thread-name-prefix: tasca-ciclo-
lifecycle:
timeout-per-shutdown-phase: 40s # més gran que els await-termination-period
server:
shutdown: graceful # aturada ordenada (01-05, 07-03)
forward-headers-strategy: framework # el TLS acaba al balancejador (05-05)
error: { include-stacktrace: never, include-message: never }
compression:
enabled: true
mime-types: application/json,application/problem+json
min-response-size: 1KB
management:
server:
port: 8081 # port de gestió separat (07-01)
endpoints:
web:
exposure:
include: health,info,metrics,prometheus,loggers,scheduledtasks
endpoint:
health:
probes: { enabled: true } # /health/liveness i /health/readiness
show-details: when_authorized
tracing:
sampling: { probability: 1.0 } # el mostreig el decideix el Collector, per cua (09-06)
otlp:
tracing:
endpoint: ${OTLP_ENDPOINT:http://otel-collector:4318/v1/traces}
metrics:
tags: { aplicacio: ciclourbana }
ciclourbana: # propietats pròpies, validades (02-05)
ciutat: Ribalta
tarifes:
desbloqueig: 0.50
preu-minut: 0.12
preu-minut-estudiant: 0.08
xarxa:
capacitat-minima: 8
llindar-bateria: 20
durada-maxima-lloguer: PT2H
estacions-destacades: [Plaça Major, Universitat]
lloguers:
caducador:
interval: PT10M
jwt:
# el secret arriba de l'entorn; vegeu 05-04
secret: ${JWT_SECRET} # obligatori, mai versionat
expiracio: PT15M
expiracio-refresc: P7DI les diferències per entorn, que és l'única cosa que canvia:
| Propietat | dev |
test |
pre |
prod |
|---|---|---|---|---|
| Base de dades | Docker local | Testcontainers | RDS de preproducció | RDS amb rèplica |
flyway.locations |
+ db/demo |
db/migration |
db/migration |
db/migration |
logging.level.com.ciclourbana |
DEBUG |
INFO |
INFO |
INFO |
| Swagger UI | Actiu | Actiu | Actiu | springdoc.api-docs.enabled: false |
| CORS | http://localhost:5173 |
— | https://pre.ribalta.example |
https://panel.ribalta.example |
| Memòria cau | Caffeine | Deshabilitada | Redis | Redis |
tracing.sampling |
1.0 local |
0.0 |
1.0 → Collector |
1.0 → Collector amb cua |
| Actuator exposat | "*" |
health |
Amb ADMIN |
health,info,prometheus |
- Posar en marxa el projecte des de zero
# 1. Clonar i preparar els secrets locals
git clone https://github.com/ajuntament-ribalta/ciclourbana.git
cd ciclourbana
cp .env.exemple .env # emplenar CONTRASENYA_BASE_DADES i JWT_SECRET
openssl rand -base64 48 # generar un secret JWT de 256+ bits
# 2. Aixecar la infraestructura (PostgreSQL 16 + Redis)
docker compose up -d
docker compose ps # esperar que el healthcheck marqui "healthy"
# 3. Construcció completa: format, unitàries, *IT amb Testcontainers, cobertura
./mvnw verify
open target/site/jacoco/index.html
# 4. Arrencar en desenvolupament
./mvnw spring-boot:run -Dspring-boot.run.profiles=dev
# 5. Comprovar que és viva i amb l'esquema al dia
curl -s localhost:8081/actuator/health/readiness | jq
curl -s localhost:8081/actuator/info | jq '.build.version' # 2.4.0
# 6. (Opcional) La pila d'observabilitat completa
docker compose -f docker-compose.observabilitat.yml up -d
# Grafana a http://localhost:3000 · Prometheus 9090 · Tempo 3200El pas 3 mereix un comentari: ./mvnw verify aixeca un PostgreSQL efímer amb Testcontainers i aplica les nou migracions a sobre abans d'executar les *IT. Si aquesta ordre passa a la teva màquina, el projecte està sa de dalt a baix.
- Recorregut d'extrem a extrem amb peticions
.http
.http### peticions/ciclourbana.http
@host = http://localhost:8080/api/v1
### 1. Registre d'una ciutadana de Ribalta
POST {{host}}/auth/registre
Content-Type: application/json
{ "nom": "Marta Serra", "correu": "[email protected]",
"contrasenya": "Bicicleta-2026!", "tipusTarifa": "ESTANDARD" }
> {% client.test("201 i sense dades sensibles", () => {
client.assert(response.status === 201);
client.assert(response.body.contrasenyaHash === undefined); }); %}
### 2. Login: retorna el token d'accés i el de refresc
# @name login
POST {{host}}/auth/login
Content-Type: application/json
{ "correu": "[email protected]", "contrasenya": "Bicicleta-2026!" }
> {% client.global.set("token", response.body.tokenAcces); %}
### 3. Estacions amb bicicletes disponibles (públic, paginat)
GET {{host}}/estacions?pagina=0&mida=20
Authorization: Bearer {{token}}
### 4. Detall de «Plaça Major», amb les seves bicicletes ancorades
GET {{host}}/estacions/1
Authorization: Bearer {{token}}
### 5. Iniciar el lloguer: 201 amb Location i X-Rastre-Id
# @name lloguer
POST {{host}}/lloguers
Authorization: Bearer {{token}}
Content-Type: application/json
{ "estacioOrigenId": 1, "matricula": "RB-0142" }
> {% client.global.set("lloguerId", response.body.id);
client.global.set("rastre", response.headers.valueOf("X-Rastre-Id")); %}
### 6. Finalitzar a «Universitat»: calcula l'import i allibera la bicicleta
PATCH {{host}}/lloguers/{{lloguerId}}/finalitzar
Authorization: Bearer {{token}}
Content-Type: application/json
{ "estacioDestiId": 4 }
### 7. La factura del lloguer
GET {{host}}/lloguers/{{lloguerId}}
Authorization: Bearer {{token}}
### 8. Comprovació de seguretat: el lloguer d'un altre ciutadà dona 403
GET {{host}}/lloguers/9999
Authorization: Bearer {{token}}I les tres comprovacions que tanquen el recorregut, que són les que demostren que l'observabilitat del mòdul 9 funciona de debò:
# La mètrica de negoci s'ha mogut
curl -s localhost:8081/actuator/metrics/ciclourbana.lloguers.iniciats | jq '.measurements'
# El log de la petició és consultable pel seu rastreId (Loki, 09-05)
# {app="ciclourbana"} | json | rastreId = "<el X-Rastre-Id del pas 5>"
# I la traça completa és a Tempo, amb el span del cobrament (09-06)
curl -s "http://localhost:3200/api/traces/<rastreId>" | jq '.batches | length'Si els tres retornen dades, les tres senyals estan connectades: la mètrica detecta, la traça localitza i el log explica.
- Llista de comprovació final de posada en producció
| # | Comprovació | Verificació objectiva |
|---|---|---|
| 1 | Zero secrets al repositori i al seu historial | Detector de secrets amb --log-opts="--all"; rotar el que es trobi |
| 2 | El perfil prod s'activa de debò |
Buscar al log d'arrencada The following 1 profile is active: "prod" |
| 3 | ./mvnw verify en verd, amb les *IT incloses |
La canonada de ci.yml, no la màquina de ningú |
| 4 | Migracions aplicades i compatibles cap enrere | flyway:info i la prova de l'apartat 5 amb la versió anterior corrent |
| 5 | Denegació per defecte i regles per dada | Prova automatitzada de rutes sense token i d'identificadors creuats |
| 6 | Swagger, consola i Actuator tancats | curl a /swagger-ui.html, /h2-console i /actuator/env: 404 o 401 |
| 7 | HTTPS obligatori amb HSTS i certificat vàlid | curl -I sobre HTTP: ha de rebutjar o redirigir |
| 8 | Sondes liveness i readiness diferenciades |
El manifest: livenessProbe no consulta la base de dades |
| 9 | Recursos i HPA dimensionats | kubectl describe hpa i la prova de càrrega de k6 contra pre |
| 10 | Logs en JSON amb rastreId, sense credencials |
Buscar eyJ, Bearer i un correu conegut en un flux complet: zero |
| 11 | Mètriques i alertes actives | Un tauler amb trànsit real i una alerta que es dispara en provocar-la |
| 12 | Revertir és una ordre cronometrada | Executar-la de debò a pre i anotar el temps |
| 13 | Còpies de seguretat de PostgreSQL restaurades | Restaurar una còpia en un entorn a part; una còpia no provada no existeix |
| 14 | Sense vulnerabilitats altes | dependency-check amb failBuildOnCVSS=7 en verd |
| 15 | Revisió de seguretat professional | Informe signat d'auditoria i prova de penetració |
El punt 13 no va aparèixer en cap lliçó i és dels que surten més cars: una còpia de seguretat que mai no s'ha restaurat no és una còpia de seguretat, és una esperança. I el 15 repeteix l'advertiment del mòdul 5: tot el que s'ha construït en aquest curs és un punt de partida didàctic i necessita revisió professional abans d'exposar-se a Internet.
- Cap a on continuar amb aquest projecte
CicloUrbana està acabada com a material de curs, no com a producte. Aquestes set extensions estan ordenades per dificultat i cadascuna combina lliçons que ja coneixes.
| # | Extensió | Dificultat | Què cal combinar |
|---|---|---|---|
| 1 | Informes amb exportació a CSV de l'històric de lloguers | Baixa | Consultes i projeccions 04-06, paginació per keyset 09-01, resposta en flux i @Async 07-03 |
| 2 | Panell d'operari amb la vista interna de la flota | Baixa | DTOs separats 03-05, rol OPERARI i @PreAuthorize 05-05, proves de seguretat 06-04 |
| 3 | Integració amb un mapa i cerca per proximitat | Mitjana | RestClient amb tallacircuits 07-06, memòria cau de geocodificació 09-02, índex geoespacial 04-08 |
| 4 | Reserva anticipada de bicicleta, amb caducitat als 10 minuts | Mitjana | Bloqueig pessimista i estats 04-07, tasca amb ShedLock 07-03, nova migració i invariants al domini 10-03 |
| 5 | Notificacions push al mòbil | Mitjana | Esdeveniments AFTER_COMMIT 04-07, executor propi i mampara 07-03, tolerància a fallades 07-06 |
| 6 | Tarifes dinàmiques per demanda en hora punta | Alta | Una altra CalculadoraTarifa i decoradors 02-02, mètriques d'ocupació 09-03, @ConfigurationProperties 02-05, proves parametritzades 06-02 |
| 7 | Extreure la facturació a un servei propi | Molt alta | El mòdul 7 sencer, traçabilitat prèvia 09-06, patró outbox i saga, contracte versionat 03-01 |
Sobre la número 7, que és la que més gent vol fer primer: és l'última de la llista per una raó. Repartir el sistema en dos processos converteix una transacció local en una saga amb compensacions, una traça de pila en una investigació entre dos desplegaments, i una crida a mètode en una crida de xarxa que pot fallar. Fes-la només quan tinguis traçabilitat distribuïda funcionant i una raó concreta —escalat o equip independent—, no com a exercici d'estil.
I una recomanació sobre el mètode: per a qualsevol de les set, comença per escriure la prova del comportament que vols, segueix per la migració si toca l'esquema, i deixa el controlador per al final. És l'ordre que menys codi llença a les escombraries.
Errors Comuns i Consells
Creure que el diagrama és l'arquitectura. El diagrama de l'apartat 1 és una vista; l'arquitectura real és el que fan les dependències del codi. Per això existeixen les regles d'ArchUnit: són l'únic diagrama que no pot mentir.
Copiar el pom.xml sencer a un projecte nou. Porta Redis, ShedLock, Resilience4j i Testcontainers a un projecte que potser no els necessita, i cada dependència és superfície de manteniment i de vulnerabilitats. Comença per web, data-jpa i test, i afegeix quan alguna cosa faci mal.
Aixecar l'observabilitat sencera en local sempre. Prometheus, Grafana, Loki, Tempo i el Collector consumeixen memòria i temps d'arrencada. En el dia a dia n'hi ha prou amb l'aplicació i PostgreSQL; la pila completa s'aixeca quan s'està treballant en observabilitat.
Oblidar que el .env no és al repositori. És el primer que trenca qui clona el projecte per primera vegada. Un .env.exemple versionat amb les claus i sense els valors estalvia aquesta mitja hora a cada persona nova.
Consell: recorre l'apartat 3 amb un depurador. Posar un punt d'interrupció a LloguerService.iniciar i pujar per la pila de crides fins al filtre és l'exercici que més consolida tot el curs: es veuen, en una sola pantalla, el proxy, la transacció, el SecurityContext i el MDC.
Consell: guarda el registre de decisions. La taula de l'apartat 5 —decisió, alternativa, motiu, contrapartida— és el document que més s'agraeix d'aquí a un any, i l'únic que respon a «per què està fet així?».
Consell: mesura abans d'estendre. Qualsevol de les set extensions de l'apartat 11 canvia el perfil de càrrega. Guarda la línia base de k6 d'avui: serà la referència per saber si l'extensió ha costat alguna cosa.
Exercicis
Exercici 1: seguir el fil d'una fallada
En producció, a les 09:12, l'app mòbil comença a rebre 500 a POST /api/v1/lloguers. El cos de l'error és un ProblemDetail amb "codi": "ERROR_INTERN" i un rastreId. Descriu, pas a pas i amb les eines concretes del projecte, com investigaries l'incident des d'aquest rastreId fins a la causa arrel, indicant quina peça de quin mòdul faries servir a cada pas i què descartaries amb cadascuna.
Exercici 2: dissenyar la reserva anticipada
Dissenya l'extensió número 4 de l'apartat 11: un ciutadà pot reservar una bicicleta concreta durant 10 minuts abans de recollir-la; passat aquest temps, la reserva caduca i la bicicleta torna a estar disponible. Especifica el canvi al model, la migració, el contracte de l'API, el control de concurrència, la tasca de caducitat, les regles de seguretat, les mètriques i les proves que escriuries.
Exercici 3: revisió d'arquitectura
L'ajuntament d'una ciutat veïna vol reutilitzar CicloUrbana. Els seus números: 40 estacions (davant de 4), 300.000 lloguers al mes, dos equips de desenvolupament i un requisit nou —integració en temps real amb el sistema de transport públic—. Analitza quines decisions de l'apartat 5 continuarien sent correctes, quines caldria revisar i en quin ordre abordaries l'adaptació.
Solucions
Solució 1
Pas 1 — de l'alerta al quadre de comandament (09-04). Abans de tocar el rastreId, mirar la fila RED del tauler: és un pic d'errors o un degoteig? va començar amb el desplegament de les 09:05? Amb això es distingeix una fallada sistemàtica d'una de puntual, i es decideix si cal revertir ja.
Pas 2 — el log d'aquella petició exacta (09-05). A Loki: {app="ciclourbana", entorn="prod"} | json | rastreId = "...". Retorna la història completa de la petició, inclosa la traça de pila que no se li va enviar al client. La majoria dels incidents es tanquen aquí, amb el nom de l'excepció i la línia.
Pas 3 — la cascada de spans (09-06). A Tempo, la mateixa traça. Aquí es respon una cosa que el log no diu: on se'n va anar el temps i què va fallar. Una barra vermella al span de la passarel·la descarta que el problema sigui nostre; vint spans select idèntics delaten un N+1 nou; un forat de 900 ms sense instrumentar apunta a espera de connexió o a una pausa de GC.
Pas 4 — descartar recursos (09-03). hikaricp.connections.pending, jvm.gc.pause i resilience4j.circuitbreaker.state a l'instant de la fallada. Si pending és a 60, el problema no és la lògica: alguna cosa reté connexions —una crida HTTP dins d'una transacció és el sospitós número u—.
Pas 5 — reproduir (06-05). Amb la causa identificada, escriure la prova que la reprodueix abans d'arreglar res. Si la fallada depèn de dades reals, una *IT amb Testcontainers i l'estat exacte que la provoca.
Pas 6 — decidir (08-05). Si la fallada va arribar amb el desplegament de les 09:05, revertir primer i diagnosticar després: el temps de restauració és una mètrica DORA i la reversió és una ordre. Si és un problema extern, la decisió és de negoci —degradar, obrir el tallacircuits, esperar—, com en el cas pràctic de 09-06.
El que l'exercici ensenya: el recorregut va del general al particular i del barat al car. La mètrica costa una llambregada, la traça un clic, el log una consulta, i el depurador mig matí. Començar pel depurador és l'error més comú.
Solució 2
Model. EstatBicicleta guanya el valor RESERVADA. Apareix l'entitat Reserva(id, usuari, bicicleta, estacio, creada, expira, estat) amb EstatReserva { ACTIVA, CONSUMIDA, CADUCADA, CANCELLADA }. La reserva és un agregat propi i no un camp de Bicicleta, perquè té cicle de vida, històric i regles pròpies.
Migració V10__reserves.sql. Taula amb seqüència (allocationSize = 50, mai IDENTITY, pels lots de 09-01), claus foranes a usuaris, bicicletes i estacions, columnes d'auditoria, CHECK sobre l'estat, índex (expira) WHERE estat = 'ACTIVA' —parcial, unes desenes de files davant de milions— i un índex únic parcial que impedeix dues reserves actives del mateix usuari, la mateixa tècnica de l'uk_lloguers_usuari_en_curs. L'enum de la columna estat de bicicletes amplia el seu CHECK: canvi compatible cap enrere.
Contracte. POST /api/v1/reserves amb { "bicicletaId": 12 } retorna 201 amb Location i ReservaResponse(id, matricula, estacio, expira, segonsRestants). DELETE /api/v1/reserves/{id} cancel·la (204). I POST /api/v1/lloguers accepta opcionalment reservaId: si ve, consumeix la reserva en lloc de cercar bicicleta.
Concurrència. És el punt delicat. SELECT … FOR UPDATE sobre la bicicleta amb timeout de 3 s, comprovació que continua DISPONIBLE, i canvi a RESERVADA a la mateixa transacció. Sense bloqueig, dos ciutadans reserven la mateixa bicicleta i un s'emporta un 409 innecessari quan el sistema li podria haver ofert una altra.
Caducitat. CaducadorReserves, germà de CaducadorLloguers: @Scheduled(fixedDelayString = "${ciclourbana.reserves.caducador.interval:PT30S}") amb @SchedulerLock —tres rèpliques, una execució—, lògica en un mètode públic invocable per poder provar-la, Clock injectat i idempotència: marcar com a caducada una reserva ja caducada no canvia res.
Seguretat. @PreAuthorize("isAuthenticated()") per crear; @PreAuthorize("hasAnyRole('OPERARI','ADMIN') or @seguretatReserves.esPropietari(#idReserva, principal)") per cancel·lar i consultar, amb un bean nou idèntic en forma a SeguretatLloguers. El llistat filtra a la consulta, mai amb @PostFilter.
Mètriques. Un Counter ciclourbana.reserves.creades{estacio} i un altre ciclourbana.reserves.caducades, amb l'etiqueta d'estació —quatre valors— i mai l'identificador d'usuari. La taxa de caducitat és l'indicador de negoci interessant: si és alta, la finestra de 10 minuts és massa curta o massa llarga.
Proves. Unitària de Reserva.caducar() amb Clock.fixed i del cas límit dels 10 minuts exactes; @DataJpaTest de l'índex únic parcial —dues reserves actives del mateix usuari han de violar la restricció—; llesca de seguretat amb dos ciutadans creuant identificadors; i una *IT del flux complet reservar → llogar → finalitzar, més una altra de reservar → esperar → comprovar que ha caducat, amb el rellotge controlat.
La decisió de disseny que cal saber defensar: la reserva és un agregat propi. Modelar-la com dos camps a Bicicleta sembla més barat i fa impossible l'històric, complica la caducitat i fica estat transitori en una entitat que hauria de ser estable.
Solució 3
El que continua sent correcte sense discussió. Flyway, DTOs, denegació per defecte, @Transactional al servei, Testcontainers, logs estructurats i observabilitat: són decisions el benefici de les quals creix amb la mida, no decreix. MapStruct passa d'opcional a clarament rendible, perquè el nombre de DTOs es multiplica.
El que cal revisar, amb el seu motiu.
| Decisió | Estat amb els números nous | Què fer |
|---|---|---|
| Monòlit modular | Continua sent correcte, i per poc | 300.000 lloguers al mes són ~7 per minut de mitjana: ridícul per a un monòlit. El que empeny no és la càrrega sinó dos equips; la resposta raonable és Spring Modulith i fronteres internes més estrictes abans que dividir |
| Caffeine local | Insuficient | Amb més rèpliques, 40 estacions i dades que canvien, la divergència entre memòries cau locals es nota. Redis com a nivell compartit, Caffeine al davant per al que és immutable |
| Pool de 10 connexions | A recalcular | Depèn del servidor nou, no de l'anterior: fórmula, prova de càrrega i pending com a senyal |
| JWT de 15 minuts | Correcte, amb matisos | Amb dos equips i més clients, cal gestió de claus i rotació; i probablement un proveïdor d'identitat en lloc de signar nosaltres |
estacions sense índex geoespacial |
Insuficient | Amb 40 estacions i cerca per proximitat, PostGIS o almenys un índex sobre les coordenades |
| Integració amb transport públic | Decisió nova | És el primer cas real de missatgeria: horaris en temps real no encaixen en peticions síncrones. Kafka o similar, amb el patró outbox |
L'ordre de l'adaptació, que és el que l'exercici pregunta de debò:
- Mesurar amb les dades noves abans de tocar res. Carregar 40 estacions i 300.000 lloguers a
prei executar l'escenari de k6. Gairebé totes les decisions anteriors es resolen amb dades, no amb opinions. - Índexs i consultes. El que es degrada amb el volum es degrada primer, i és el més barat d'arreglar.
- Fronteres de mòdul abans que equips. Amb dos equips, el conflicte no és tècnic sinó de propietat del codi: regles d'ArchUnit per mòdul i fronteres explícites abans de repartir la feina.
- Memòria cau distribuïda i pool redimensionat, amb la prova de càrrega com a jutge.
- La integració de transport públic, l'última, perquè introdueix una tecnologia nova —missatgeria— i convé fer-ho sobre una base ja estable i mesurada.
El que no faria: dividir en microserveis des del principi «perquè ara és més gran». Quaranta estacions i set lloguers per minut no són un problema d'escala: són un problema d'organització, i aquest es resol amb fronteres internes abans que amb processos separats.
Conclusió
CicloUrbana està completa i, per primera vegada, l'has vista sencera. Tens l'arquitectura final en un diagrama que es llegeix: un sol desplegable amb mòduls interns, dependències que apunten sempre cap endins, l'observabilitat com a lateral i no com a capa, i una única dependència capaç de tombar el servei —PostgreSQL—, cosa que explica per què la sonda readiness la consulta i la de liveness no. Tens l'estructura real del repositori, amb els paquets per funcionalitat, l'arbre de proves com a mirall del de producció, la separació *Test/*IT i tot el que envolta el codi: Dockerfile, docker-compose, helm/, .github/workflows, observabilitat/ i carrega/.
I tens el viatge d'una petició explicat en vint-i-un passos, del balancejador a la mètrica, amb la lliçó de cadascun. Aquest recorregut és el millor resum del curs perquè hi apareix tot: el filtre que obre el span, el JWT que es valida, el binding i la validació del DTO, el proxy que avalua el permís i obre la transacció al mateix punt —d'aquí que l'autoinvocació se'ls endugui tots dos per davant—, el bloqueig pessimista sobre l'última bicicleta de «Plaça Major», el mapatge dins de la transacció que fa possible open-in-view: false, l'AFTER_COMMIT que treu el cobrament del fil HTTP i la mètrica de negoci que tanca el cicle.
Tens també el mapa de què va construir cada mòdul, el pom.xml final on es veu quines versions gestiona el BOM i quines cal revisar a mà, la configuració consolidada amb la taula de l'única cosa que canvia entre entorns, les ordres per posar el projecte en marxa des de zero i la col·lecció .http que recorre registre, login, consulta, lloguer, factura i la comprovació que un ciutadà no pot llegir el lloguer d'un altre —seguida de les tres consultes que demostren que la mètrica, el log i la traça estan units pel mateix rastreId—.
I tens les dues peces que separen un projecte de curs d'un producte: la taula de decisions amb les seves contrapartides, que és el document que respon d'aquí a un any a «per què està fet així?», i la llista de comprovació final amb els seus quinze punts, inclòs el que cap lliçó no havia esmentat —una còpia de seguretat que mai no s'ha restaurat no és una còpia de seguretat— i el que es repeteix des del mòdul 5: això és un punt de partida didàctic i necessita revisió professional abans d'exposar-se a Internet.
El projecte no s'acaba aquí, i aquesta és la idea. Les set extensions de l'apartat 11 estan ordenades per dificultat i cadascuna et diu quines lliçons combinar, des de l'informe amb exportació fins a extreure la facturació a un servei propi —l'última de la llista, i amb motiu—. Construir qualsevol d'elles sobre aquesta base és la millor manera de convertir el que has après en alguna cosa teva.
Queda una última lliçó, i és l'única del curs que no parla de CicloUrbana. Perquè saber Spring Boot no és haver acabat un curs: és haver començat a poder llegir la documentació amb criteri, distingir un recurs actualitzat d'un que confon, planificar una actualització de versió major sense por i triar què aprendre després entre vint camins possibles. Recursos per a Aprenentatge Addicional s'ocupa d'això: la documentació oficial i com llegir-la, el codi font com la millor font, com mantenir-se al dia amb el cicle de publicació de Spring, l'ecosistema Java, els llibres que de debò valen la pena, les certificacions amb una valoració honesta, la comunitat, la pràctica deliberada, els temes naturals per al pas següent i un full de ruta raonat per als propers sis mesos.
Curs de Spring Boot
Mòdul 1: Introducció a Spring Boot
- Què és Spring Boot?
- Configuració del teu entorn de desenvolupament
- Creant la teva primera aplicació Spring Boot
- Entenent l'estructura del projecte
- L'arrencada i el cicle de vida de l'aplicació
Mòdul 2: Conceptes bàsics de Spring Boot
- Anotacions de Spring Boot
- Injecció de dependències a Spring Boot
- Àmbit i cicle de vida dels beans
- Configuració de Spring Boot
- Propietats de Spring Boot
- Autoconfiguració i starters per dins
Mòdul 3: Construint serveis web RESTful
- Introducció als serveis web RESTful
- Creant controladors REST
- Gestió dels mètodes HTTP
- Validació de dades d'entrada
- DTOs i mapatge entre capes
- Gestió d'excepcions a REST
- Documentar l'API amb OpenAPI
Mòdul 4: Accés a dades amb Spring Boot
- Introducció a Spring Data JPA
- Configuració de fonts de dades
- Creació d'entitats JPA
- Relacions entre entitats
- Ús de repositoris de Spring Data
- Mètodes de consulta a Spring Data JPA
- Transaccions i gestió de la persistència
- Migracions d'esquema amb Flyway
Mòdul 5: Seguretat a Spring Boot
- Introducció a Spring Security
- Configuració de Spring Security
- Autenticació i autorització d'usuaris
- Implementació d'autenticació JWT
- Seguretat a nivell de mètode i enduriment de l'API
Mòdul 6: Proves a Spring Boot
- Introducció a les proves
- Proves unitàries amb JUnit
- Simulació amb Mockito
- Proves d'integració
- Proves amb Testcontainers
Mòdul 7: Funcions avançades de Spring Boot
- Spring Boot Actuator
- Perfils de Spring Boot
- Tasques programades i execució asíncrona
- Spring Boot amb Docker
- Spring Boot i microserveis
- Comunicació entre serveis i tolerància a fallades
Mòdul 8: Desplegament d'aplicacions Spring Boot
- Introducció al desplegament
- Desplegant a Heroku
- Desplegant a AWS
- Desplegant a Kubernetes
- Integració i lliurament continus
Mòdul 9: Rendiment i monitoratge
- Ajust de rendiment
- Memòria cau amb Spring Cache
- Monitoratge amb Spring Boot Actuator
- Ús de Prometheus i Grafana
- Gestió de registres i logs
- Traçabilitat distribuïda
