El diagrama de clases refleja la Arquitectura Hexagonal adoptada para el proyecto, organizando las clases en tres capas principales:
<aside> <img src="/icons/subtask_gray.svg" alt="/icons/subtask_gray.svg" width="40px" />
</aside>
---
config:
layout: elk
theme: neutral
---
classDiagram
%% ============================================
%% CAPA DE DOMINIO - ENTIDADES
%% ============================================
class User {
-UUID id
-String spotifyId
-String username
-String email
-String profilePictureUrl
-String spotifyAccessToken
-String spotifyRefreshToken
-LocalDateTime tokenExpiresAt
-LocalDateTime createdAt
-LocalDateTime updatedAt
+getId() UUID
+getSpotifyId() String
+getUsername() String
+isTokenExpired() boolean
+updateTokens(accessToken, refreshToken, expiresIn) void
}
class Song {
-UUID id
-String spotifyId
-String title
-String artist
-String album
-String coverUrl
-String previewUrl
-Integer durationMs
-Genre genre
-Integer popularity
-String releaseYear
+getId() UUID
+getSpotifyId() String
+getTitle() String
+getArtist() String
+getFullTitle() String
}
class Swipe {
-UUID id
-User user
-Song song
-SwipeDirection direction
-LocalDateTime swipedAt
-boolean syncedToSpotify
+getId() UUID
+isLike() boolean
+isDislike() boolean
+markAsSynced() void
}
class Playlist {
-UUID id
-String spotifyPlaylistId
-User owner
-String name
-String description
-boolean isPublic
-boolean isDefault
-List~Song~ songs
-LocalDateTime createdAt
-LocalDateTime updatedAt
+getId() UUID
+getSpotifyPlaylistId() String
+isDefault() boolean
+addSong(song) void
+removeSong(song) void
+getSongCount() int
}
%% ============================================
%% CAPA DE DOMINIO - VALUE OBJECTS
%% ============================================
class SwipeDirection {
<<enumeration>>
LIKE
DISLIKE
}
class Genre {
<<enumeration>>
POP
ROCK
HIPHOP
ELECTRONIC
JAZZ
CLASSICAL
REGGAETON
INDIE
OTHER
}
class SwipeFilter {
-List~Genre~ genres
-String mood
-String decade
-Integer minPopularity
-Integer maxPopularity
+isEmpty() boolean
+matches(song) boolean
}
%% ============================================
%% CAPA DE DOMINIO - PUERTOS (INTERFACES)
%% ============================================
class UserRepository {
<<interface>>
+save(user) User
+findById(id) Optional~User~
+findBySpotifyId(spotifyId) Optional~User~
+existsBySpotifyId(spotifyId) boolean
}
class SongRepository {
<<interface>>
+save(song) Song
+findById(id) Optional~Song~
+findBySpotifyId(spotifyId) Optional~Song~
+findByFilters(filters) List~Song~
}
class SwipeRepository {
<<interface>>
+save(swipe) Swipe
+findByUserId(userId) List~Swipe~
+findPendingSync(userId) List~Swipe~
+countLikesByUserId(userId) Long
}
class PlaylistRepository {
<<interface>>
+save(playlist) Playlist
+findById(id) Optional~Playlist~
+findAllByUserId(userId) List~Playlist~
+findDefaultByUserId(userId) Optional~Playlist~
+findBySpotifyPlaylistId(playlistId) Optional~Playlist~
}
class SpotifyMusicProvider {
<<interface>>
+authenticateUser(authCode) SpotifyAuthResponse
+refreshAccessToken(refreshToken) SpotifyAuthResponse
+getUserProfile(accessToken) SpotifyUserProfile
+createPlaylist(userId, name) SpotifyPlaylist
+addSongToPlaylist(playlistId, songId) boolean
+getRecommendations(filters) List~Song~
+getSongById(songId) Optional~Song~
}
%% ============================================
%% CAPA DE APLICACIÓN - SERVICIOS / USE CASES
%% ============================================
class AuthenticationService {
-UserRepository userRepository
-SpotifyMusicProvider spotifyProvider
-PlaylistRepository playlistRepository
+authenticateWithSpotify(authCode) AuthenticationResult
+refreshUserToken(userId) void
+disconnectSpotify(userId) void
}
class SwipeService {
-SwipeRepository swipeRepository
-SongRepository songRepository
-PlaylistService playlistService
-UserRepository userRepository
+recordSwipe(userId, songId, direction) Swipe
+getUserSwipes(userId) List~Swipe~
+syncPendingSwipes(userId) void
}
class PlaylistService {
-PlaylistRepository playlistRepository
-SpotifyMusicProvider spotifyProvider
-UserRepository userRepository
+createDefaultPlaylist(userId) Playlist
+createCustomPlaylist(userId, name, isPublic) Playlist
+addSongToPlaylist(playlistId, songId) void
+addSongToDefaultPlaylist(userId, songId) void
+getUserPlaylists(userId) List~Playlist~
+getDefaultPlaylist(userId) Optional~Playlist~
+updatePlaylistSettings(playlistId, name, isPublic) void
+deletePlaylist(playlistId) void
}
class RecommendationService {
-SongRepository songRepository
-SpotifyMusicProvider spotifyProvider
-SwipeRepository swipeRepository
+getRecommendations(userId, filters) List~Song~
+getNextSong(userId) Song
}
%% ============================================
%% CAPA DE INFRAESTRUCTURA - ADAPTADORES
%% ============================================
class SpotifyAdapter {
<<adapter>>
-RestTemplate restTemplate
-String clientId
-String clientSecret
-int maxRetries
+authenticateUser(authCode) SpotifyAuthResponse
+refreshAccessToken(refreshToken) SpotifyAuthResponse
+getUserProfile(accessToken) SpotifyUserProfile
+createPlaylist(userId, name) SpotifyPlaylist
+addSongToPlaylist(playlistId, songId) boolean
+getRecommendations(filters) List~Song~
-executeWithRetry(request) Response
}
class UserController {
<<controller>>
-AuthenticationService authService
+authenticate(authCode) ResponseEntity
+getProfile(userId) ResponseEntity
+disconnect(userId) ResponseEntity
}
class SwipeController {
<<controller>>
-SwipeService swipeService
-RecommendationService recommendationService
+swipeSong(userId, songId, direction) ResponseEntity
+getSwipeHistory(userId) ResponseEntity
+getNextSong(userId) ResponseEntity
}
class PlaylistController {
<<controller>>
-PlaylistService playlistService
+getUserPlaylists(userId) ResponseEntity
+getDefaultPlaylist(userId) ResponseEntity
+createPlaylist(userId, request) ResponseEntity
+updatePlaylist(playlistId, request) ResponseEntity
+deletePlaylist(playlistId) ResponseEntity
}
%% ============================================
%% RELACIONES - DOMINIO
%% ============================================
User "1" --> "*" Swipe : realiza
Song "1" --> "*" Swipe : es evaluada en
Swipe --> SwipeDirection : tiene
Song --> Genre : pertenece a
User "1" --> "*" Playlist : posee
Playlist "1" --> "*" Song : contiene
%% ============================================
%% RELACIONES - SERVICIOS CON PUERTOS
%% ============================================
AuthenticationService --> UserRepository : usa
AuthenticationService --> SpotifyMusicProvider : usa
AuthenticationService --> PlaylistRepository : usa
SwipeService --> SwipeRepository : usa
SwipeService --> SongRepository : usa
SwipeService --> UserRepository : usa
SwipeService --> PlaylistService : usa
PlaylistService --> PlaylistRepository : usa
PlaylistService --> SpotifyMusicProvider : usa
PlaylistService --> UserRepository : usa
RecommendationService --> SongRepository : usa
RecommendationService --> SpotifyMusicProvider : usa
RecommendationService --> SwipeRepository : usa
%% ============================================
%% RELACIONES - ADAPTADORES IMPLEMENTAN PUERTOS
%% ============================================
SpotifyAdapter ..|> SpotifyMusicProvider : implementa
%% ============================================
%% RELACIONES - CONTROLLERS USAN SERVICIOS
%% ============================================
UserController --> AuthenticationService : usa
SwipeController --> SwipeService : usa
SwipeController --> RecommendationService : usa
PlaylistController --> PlaylistService : usa
Entidades principales:
User: Representa a un usuario de la aplicación con su vinculación a SpotifySong: Representa una canción obtenida de SpotifySwipe: Representa la acción de dar like/dislike a una canciónPlaylist: Representa la playlist del usuario sincronizada con SpotifyValue Objects:
SwipeDirection: Enum que define las direcciones del swipe (LIKE/DISLIKE)Genre: Enum con los géneros musicales soportados