Demostración de Stelut Grigore Tomoiaga
Todo se levanta con Docker Compose (app + redis + kafka). La BBDD maestra sigue siendo remota y se configura en .env.
Compose incluye healthcheck en app, redis y kafka, y app espera a que redis/kafka esten saludables para arrancar.
## Variables mínimas:
DATABASE_URL=jdbc:postgresql://host:5432/db?sslmode=require
POSTGRES_USER=usuario
POSTGRES_PASSWORD=clave
JWT_SECRET=change-this-secret-to-at-least-32-bytes-long
REDIS_HOST=redis
REDIS_PORT=6379
KAFKA_BOOTSTRAP_SERVERS=kafka:9092
APP_SOCIAL_ASYNC_ENABLED=true
APP_SOCIAL_REDIS_ENABLED=true
APP_SOCIAL_KAFKA_TOPIC=social-events
docker compose build --no-cache
docker compose up -d
# Conjunto
docker compose up -d --build
# Logs
docker compose logs -f appComprobar readiness manualmente:
curl http://localhost:${APP_PUBLIC_PORT:-8080}/actuator/health/readinessLogs en fichero (persistidos en la raíz del proyecto):
ls -la logs/
tail -f logs/demostracion.logAudit de GET /api/v1/auth/me:
- Se registra por middleware con
method,path,status,ip,user,durationMs. - Ejemplo:
auth_me_audit method=GET path=/api/v1/auth/me status=401 ip=... user=anonymous durationMs=...
Para ver logs:
docker compose logs -f appPara parar y limpiar:
docker compose downPuedes servir HTTPS directamente desde Spring Boot en Docker con certificados PEM de Let's Encrypt.
sudo certbot certonly --standalone --preferred-challenges http -d spapi.tudominio.comEn .env del servidor:
SPRING_PROFILES_ACTIVE=https
SERVER_PORT=443
APP_PUBLIC_PORT=443
SSL_CERTS_DIR=/etc/letsencrypt/live/spapi.tudominio.com
SERVER_SSL_CERTIFICATE=/certs/fullchain.pem
SERVER_SSL_PRIVATE_KEY=/certs/privkey.pemdocker compose up -d --buildComprobar:
curl https://spapi.tudominio.com/actuator/health/readinessPara poner SSL simplemente hay que seguir estos pasos:
https://docs.spring.io/spring-boot/reference/features/ssl.html
"As a rule, you should use the prototype scope for all stateful beans and the singleton scope for stateless beans."
https://docs.spring.io/spring-boot/reference/testing/test-modules.html
https://docs.spring.io/spring-boot/reference/features/logging.html#page-title
http://localhost:8080/swagger-ui/index.html
Spring Web
Spring Security
Spring Data JPA
Spring Batch
PostgreSQL Driver
Spring Data Redis
Spring Kafka
Lombok
Docker Compose Support
Spring Boot Starter Flyway (org.springframework.boot:spring-boot-starter-flyway)
Actuator: expone endpoints de administración como health y readiness, además de métricas y info de la app. Se usa para saber si el servicio está vivo y listo para recibir tráfico.
Micrometer: librería de métricas. Recolecta tiempos de respuesta, contadores, memoria, etc., y los exporta a sistemas como Prometheus/Grafana.
Migraciones (Flyway): versionan y aplican cambios del esquema de la base de datos. En vez de crear tablas a mano, la app ejecuta scripts al arrancar y mantiene un historial de cambios.
Las migraciones van en src/main/resources/db/migration con nombres tipo V1__init.sql, V2__add_table.sql.
NOTA: Al arrancar la app, Flyway aplica las migraciones pendientes automáticamente.
# Genera DDL en target/generated-schema.sql usando el perfil ddl
./mvnw -Dspring-boot.run.profiles=ddl \
-Dspring-boot.run.arguments=--spring.main.web-application-type=none \
spring-boot:runPasos de la creación:
- Modelar entidades/repos/servicios.
- Genera DDL a
target/generated-schema.sql. - Crea una nueva migración
src/main/resources/db/migration/Vx__descripcion.sql. - Copia/limpia SQL relevante al
Vx__...sql(no copiar todo a ciegas). - Mantén
ddl-auto=validate+ Flyway para producción.
BBDD remota (p.ej. Neon o proveedor gestionado). La app se conecta usando DATABASE_URL.
Objetivo: reducir presión de escrituras/lecturas sobre Neon sin cambiar el contrato REST.
Flujo:
- API recibe
like/view/comment. - Actualiza contador en Redis (respuesta rápida).
- Publica evento en Kafka.
- Consumidor persiste en PostgreSQL (Neon) con
INSERT ... ON CONFLICT.
Fallback:
- Si Kafka no está disponible, la app hace escritura directa a PostgreSQL.
- Si Redis no está disponible, los contadores salen desde PostgreSQL.
Nota:
- Con async habilitado,
like/view/commentson eventually consistent en BBDD. - El
feedprioriza contadores en Redis y, si faltan, recalcula desde BBDD.
TTL en Redis:
stats(contadores) expiran conAPP_SOCIAL_REDIS_STATS_TTL.likes:users(set de usuarios) expira conAPP_SOCIAL_REDIS_LIKES_TTLpara dedupe temporal.posts(set de posts conocidos) expira conAPP_SOCIAL_REDIS_POSTS_TTL.
Esto no lo “borra Kafka”; Redis expira por TTL. Kafka solo asegura la escritura final en PostgreSQL.
Métricas (Actuator):
GET /actuator/metricslista métricas.GET /actuator/metrics/social.redis.cache(hits/miss).GET /actuator/metrics/social.kafka.publishedGET /actuator/metrics/social.kafka.failedGET /actuator/metrics/social.kafka.consumedGET /actuator/metrics/social.kafka.db_errorGET /actuator/metrics/social.db.fallback
En producción, protege /actuator/** (ahora están abiertas para debug local).
Cuando sube mucho el tráfico, primero suele escalarse horizontalmente (más réplicas de la app) en vez de solo meter una máquina más grande.
En Docker Compose (modo normal), el escalado se hace así:
docker compose up -d --scale app=3Nota:
deploy.replicasse usa en Swarm, no endocker composeclásico.
Qué resuelve Compose bien:
- Levantar varias instancias de
app. - Reiniciar contenedores caídos (
restart: unless-stopped). - Mantener configuración simple en un solo host.
Qué no resuelve bien por sí solo:
- Autoscaling real por CPU/RAM.
- Balanceo avanzado entre varios nodos/hosts.
- Orquestación completa de rolling updates sin fricción.
Importante sobre BBDD:
- La app Spring Boot es stateless y se replica fácil.
- PostgreSQL es stateful; no se replica igual que la app.
- Lo habitual: app replicada + una BBDD gestionada/externa (como ya tienes).
Flujo práctico de despliegue:
git push.- CI construye imagen con tu Dockerfile multi-stage.
- CI sube imagen al registry.
- Servidor hace
docker compose pull+docker compose up -d.
Auth:
POST /api/v1/auth/registerPOST /api/v1/auth/loginPOST /api/v1/auth/refreshGET /api/v1/auth/me(requiereAuthorization: Bearer <token>)GET /api/v1/auth/preferences/language(requiere token)PUT /api/v1/auth/preferences/language(body:{ "language": "es" | "en" })
Posts (requiere Authorization: Bearer <token>):
POST /api/v1/posts(crear post)POST /api/v1/posts/{postId}/like(like idempotente por usuario)DELETE /api/v1/posts/{postId}/like(quitar like idempotente por usuario)POST /api/v1/posts/{postId}/view(incrementa vistas)POST /api/v1/posts/{postId}/comments(crear comentario)GET /api/v1/posts/{postId}/comments?size=50(listar comentarios)GET /api/v1/posts/feed?page=0&size=20(feed paginado)
Search (requiere Authorization: Bearer <token>):
GET /api/v1/search/suggestions?q=spring&type=posts|users&limit=10(autocompletado, por defectoposts)GET /api/v1/search/results?q=spring&type=posts|users&page=0&size=20(resultados paginados conlikes/comments/views)
Analytics (publico):
GET /api/v1/analytics/summary(motor pesadoHeavyAnalyticsEnginecon@Lazy)- Incluye:
topWords,topPosts,hourlyHeatmapypostsEvolution(30 dias). Nota: este endpoint esta publico para la vista frontend de resumen.
Payloads de ejemplo:
POST /api/v1/posts
{
"content": "Mi primer post",
"authorDisplayName": "stelut"
}POST /api/v1/posts/{postId}/comments
{
"content": "Buen post!"
}Estrategia por slices (unitarios/aislados):
@WebMvcTest+MockMvcpara Controller/Security.@DataJpaTestpara repositorios JPA.@SpringBootTest+@AutoConfigureMockMvccomo integración completa (opcional).
Dependencias de test recomendadas para este stack:
org.springframework.boot:spring-boot-starter-testorg.springframework.boot:spring-boot-starter-security-testorg.springframework.boot:spring-boot-starter-webmvc-testorg.springframework.boot:spring-boot-starter-data-jpa-testcom.h2database:h2(solo test, para@DataJpaTest)- Opcional integración real con PostgreSQL:
org.springframework.boot:spring-boot-starter-flyway-test+ Testcontainers.
Casos cubiertos ahora:
AuthControllerWebMvcTestGET /api/v1/auth/mesin token ->401.POST /api/v1/auth/registercon password débil ->400.UserRepositoryDataJpaTestexistsByEmail(...)devuelvetruecuando existe usuario.- restricción
uniqueenemaildispara excepción al duplicar.
Cómo ejecutar tests:
# Todos los tests
./mvnw test
# Solo slice web/security
./mvnw -Dtest=AuthControllerWebMvcTest test
# Solo slice JPA
./mvnw -Dtest=UserRepositoryDataJpaTest testFlyway: más simple. Migraciones en SQL con nombres versionados. Ideal para demos y proyectos pequeños.
Liquibase: más flexible. Permite XML/YAML/JSON/SQL, soporta “labels/contexts” y precondiciones. Mejor cuando necesitas despliegues selectivos y mayor control.
Para este proyecto demo, elegimos Flyway.
openssl rand -hex 64Se incluye script Python para meter datos demo directamente en PostgreSQL (Neon o similar):
scripts/main.pyscripts/requirements.txtscripts/.env(local, ignorado por git)scripts/.env.example
Uso:
cd scripts
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# ajusta DATABASE_URL
python main.pyEl seed crea:
- usuarios (1 admin + resto user)
- posts
- likes
- views (con
view_count) - comments
Password por defecto de usuarios seed: SEED_DEFAULT_PASSWORD.
Se incluye frontend en frontend/preact-front con nombre Springram by Stelut Tomoiaga.
Config local:
cd frontend/preact-front
cp .env.example .env
# ajusta VITE_API_BASE_URL
npm install
npm run devPara Cloudflare Pages:
- Build command:
npm run build - Output directory:
dist - Variable:
VITE_API_BASE_URL=https://tu-api-dominio
Rutas frontend:
/landing + intro del proyecto/authregistro/login/appperfil + feed + acciones de post
# Seed
cd scripts
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python main.py