Clon funcional de Spotify construido con una arquitectura de microservicios orientada a eventos: el backend en Spring Boot sirve una API REST que consulta el catálogo de Deezer, cachea respuestas en Redis y emite métricas de reproducción hacia Kafka; el frontend en React ofrece una interfaz editorial en blanco y negro con tema claro/oscuro.
graph TD
A[Frontend React 19 / Vite] -->|HTTP / REST /api| B[Spring Boot API :8080]
B -->|JPA / Hibernate| C[(Oracle DB 23c)]
B -->|Cache TTL 1h| D[(Redis 7)]
B -->|RestClient| E[Deezer API]
B -->|TrackPlayedEvent| F[Kafka Broker]
F -->|Consumidor de analítica| G[TrackEventConsumer]
El flujo de reproducción es asíncrono: el frontend solicita el track, el backend devuelve la URL de preview y, en paralelo, publica un TrackPlayedEvent en el tópico track-played-topic que un consumidor procesa para analítica sin bloquear la respuesta.
| Capa | Tecnología |
|---|---|
| Frontend | React 19, Vite 8, React Router 7, JavaScript (oxlint) |
| Backend | Java 21, Spring Boot 3.4.3 (Web, Data JPA, Validation), Lombok |
| Base de Datos | Oracle Database 23c Free (JDBC thin) |
| Caché | Redis 7 — TTL de 1 hora e invalidación declarativa (@Cacheable, @CacheEvict) |
| Mensajería / Streaming | Apache Kafka 7.6 (modo KRaft) |
| Cliente externo | Deezer API (búsqueda y previews de tracks) |
| Contenedores | Docker Compose (Oracle, Redis, Kafka) |
| CI/CD | GitHub Actions (compilación y empaquetado con Maven) |
.
├── src/main/java/… # Backend Spring Boot (dominios: user, track, playlist)
├── src/main/resources/ # application.yml (configuración de arranque)
├── frontend/ # Aplicación React + Vite
├── docker-compose.yml # Oracle, Redis y Kafka
└── .github/workflows/ # Pipeline de CI (Java CI/CD Pipeline)
- Docker y Docker Compose instalados.
- JDK 21+ instalado.
- Node.js v20+ instalado (se usa npm).
git clone https://github.com/bulletformyvalentinefan/play-something.git
cd play-somethingdocker compose up -dSe levantan tres servicios en segundo plano:
| Servicio | Imagen | Puerto |
|---|---|---|
| Oracle DB | gvenzl/oracle-free:23-slim |
1521 |
| Redis | redis:7-alpine |
6379 |
| Kafka | confluentinc/cp-kafka:7.6.0 |
9092 |
La configuración de arranque del backend vive en
src/main/resources/application.yml, con credenciales por defecto que coinciden con eldocker-compose.yml(baseFREEPDB1, usuariospotify_user). No es necesario crear archivos.envpara un arranque local.
# Linux / macOS
./mvnw spring-boot:run
# Windows
mvnw.cmd spring-boot:runEl backend quedará expuesto en http://localhost:8080 con base de rutas /api/v1/spotify.
cd frontend
npm install
npm run devEl frontend quedará accesible en http://localhost:5173. Vite redirige las peticiones /api hacia el backend en el puerto 8080 (no se requiere configuración CORS).
Base: /api/v1/spotify · Formato de respuesta: JSON · Errores: 400 validación, 404 no encontrado.
| Método | Ruta | Descripción | Caché / Asíncrono |
|---|---|---|---|
POST |
/auth/register |
Registrar un usuario | — |
POST |
/auth/login |
Iniciar sesión | — |
GET |
/user/{id} |
Obtener un usuario | — |
GET |
/tracks/search?q={query} |
Buscar tracks en el catálogo de Deezer | Redis (@Cacheable track_searches) |
GET |
/tracks/{trackId} |
Detalle de un track | Redis (@Cacheable tracks) |
POST |
/tracks/{trackId}/play?userId={userId} |
Obtener el audio y emitir la métrica de reproducción | Kafka (track-played-topic) |
POST |
/playlists |
Crear una playlist | Redis (@CachePut / @CacheEvict) |
GET |
/users/{userId}/playlists |
Listar playlists de un usuario | Redis (@Cacheable user_playlists) |
GET |
/playlists/{playlistId} |
Detalle de una playlist | Redis (@Cacheable playlist) |
POST |
/playlists/{playlistId}/tracks |
Añadir un track a la playlist | Redis (invalidación) |
DELETE |
/playlists/{playlistId}/tracks/{trackId} |
Quitar un track de la playlist | Redis (invalidación) |
DELETE |
/playlists/{playlistId} |
Eliminar una playlist | Redis (invalidación) |
Ejemplo — crear una playlist:
curl -X POST http://localhost:8080/api/v1/spotify/playlists \
-H "Content-Type: application/json" \
-d '{"userId":"<uuid>","titulo":"Mi playlist","descripcion":"favoritas","esPublica":true}'Ejemplo — registrar un usuario:
curl -X POST http://localhost:8080/api/v1/spotify/auth/register \
-H "Content-Type: application/json" \
-d '{"nombre":"Ana","email":"ana@mail.com"}'No hay Swagger/OpenAPI habilitado actualmente; los contratos se documentan en esta tabla.
| Caché | Clave | TTL | Uso |
|---|---|---|---|
track_searches |
query | 1 h | Respuestas de búsqueda de tracks |
tracks |
trackId | 1 h | Detalle individual de tracks |
playlist |
playlistId | 1 h | Detalle de playlists |
user_playlists |
userId | 1 h | Listado de playlists por usuario |
Las escrituras invalidan las entradas afectadas mediante @CachePut/@CacheEvict para mantener consistencia (por ejemplo, añadir/quitar tracks o eliminar una playlist limpia playlist y user_playlists).
- Tópico:
track-played-topic - Productor:
TrackEventProducer— emiteTrackPlayedEvent(trackId,userId,playedAt) al reproducir un track. - Consumidor:
TrackEventConsumer— procesa la analítica de reproducción sin afectar la latencia de la API.