PFP Playground for Music - Real-time music party room platform
A sophisticated Spring Boot backend service powering PFPlay, a real-time collaborative music party platform where users can create virtual party rooms, share music, DJ together, and interact in real-time.
- Features
- Technology Stack
- Architecture
- Getting Started
- API Documentation
- WebSocket Events
- Project Structure
- Configuration
- Resources
- Real-time Music Party Rooms: Create and manage virtual party rooms with live music playback
- DJ Queue System: Automated DJ rotation with playlist-based music sharing
- Live Playback Synchronization: Real-time music playback coordination across all users
- Room Management: Configure stage types, playback limits, notices, and room settings
- Dual User System:
- Members (OAuth-authenticated users)
- Guests (temporary anonymous access)
- Authority Tiers: Flexible user privilege system (FM, AM, GT)
- Profile & Avatar System: Customizable user profiles with avatar resources
- Activity Tracking: Track DJ points and various user activities
- Hierarchical Roles: HOST, COMMUNITY_MANAGER, MODERATOR, CLUBBER, LISTENER
- Comprehensive Moderation Tools:
- Grade management (promote/demote)
- Chat penalties and bans
- User expulsion and blocking
- Message removal
- Crew History Tracking: Complete audit trail for all moderation actions
- WebSocket Integration: STOMP-based real-time messaging
- Group Chat: Per-room chat with Redis pub/sub distribution
- Live Event Broadcasting: 15+ event types for real-time updates
- Playback events (start, skip, reactions)
- User actions (join, leave, grade changes)
- Room updates (notice changes, deactivation)
- User Playlists: Create, organize, and manage personal playlists
- YouTube Integration: Search and add music via YouTube API
- Track Metadata: Store and manage track information
- Grab Feature: Save tracks from live playback to personal playlists
- Real-time Reactions: LIKE, DISLIKE, GRAB with live aggregation
- Motion Animations: Broadcast reaction motions to all users
- Reaction History: Track all user reactions for analytics
- OAuth2 Integration: Google and Twitter social login
- JWT-based Authentication: Secure cookie-based token management
- Role-based Access Control: Method-level security with Spring Security
- WebSocket Security: JWT validation in handshake
- Java 17 - Modern Java LTS version
- Spring Boot 3.2.3 - Application framework
- Gradle - Build automation
- Spring Boot Web - RESTful API
- Spring Security - Authentication & authorization
- Spring Data JPA - Data persistence
- Spring Data Redis - Caching & pub/sub
- Spring WebSocket - Real-time communication
- Spring WebFlux - Reactive HTTP clients
- Spring OAuth2 - Social login integration
- Spring Cache - Caching abstraction
- MySQL 8.0.30 - Primary relational database
- Redis - Caching, pub/sub messaging, distributed locks
- JPA/Hibernate - ORM framework
- QueryDSL 5.0.0 - Type-safe queries
- P6Spy - SQL logging and monitoring
- JWT (JJWT 0.12.3) - JSON Web Token
- Spring Security - Security framework
- OAuth2 Client & Resource Server - Social authentication
- Passay - Password validation
- SpringDoc OpenAPI 3 - API documentation (Swagger UI)
- Lombok - Boilerplate reduction
- Google YouTube API v3 - Music search and metadata
- Google OAuth2 - Social login
- Twitter OAuth2 - Social login
- PyTube Service - Custom YouTube streaming service
- Spring Boot DevTools - Hot reload
- Docker - Containerization
- GitHub Actions - CI/CD
The project follows Hexagonal Architecture (Ports & Adapters) with DDD principles:
┌─────────────────────────────────────────────────────┐
│ Inbound Adapters │
│ adapter/in/web/ (REST Controllers) │
│ adapter/in/listener/ (Redis Topic Listeners) │
│ adapter/in/stomp/ (WebSocket STOMP Controllers) │
└──────────────────────┬──────────────────────────────┘
↓
┌──────────────────────┴──────────────────────────────┐
│ Application Layer │
│ application/service/ (Use Case Orchestration) │
│ application/port/out/ (Outbound Port Interfaces) │
└──────────────────────┬──────────────────────────────┘
↓
┌──────────────────────┴──────────────────────────────┐
│ Domain Layer │
│ domain/entity/data/ (JPA Entities + Business Logic)│
│ domain/service/ (Domain Services) │
│ domain/value/ (Value Objects) │
└──────────────────────┬──────────────────────────────┘
↑
┌──────────────────────┴──────────────────────────────┐
│ Outbound Adapters │
│ adapter/out/persistence/ (JPA Repositories, QueryDSL)│
│ adapter/out/external/ (Cross-domain Port Adapters)│
└─────────────────────────────────────────────────────┘
1. Hexagonal Architecture (Ports & Adapters)
- Clear domain boundaries (Party, User, Playlist, etc.)
- Unified data entities (
*Data.java) with business logic - Value objects for type safety (UserId, PartyroomId)
- Cross-domain dependencies resolved via Port/Adapter interfaces
2. Event-Driven Architecture
- Redis pub/sub for cross-instance communication
- 15+ event types for different domain events
- Decoupled components via message passing
- Scalable distributed architecture
3. Gradle Multi-Module (5 modules)
common— Shared Kernel + infrastructure config (JPA, Redis, JWT, Security)realtime— WebSocket infrastructure (zero domain imports, port interfaces only)playlist— Playlist domain (depends:common)user— User domain (depends:common)app— Auth, Party, Admin, Bootstrap (depends: all modules)- Dependency direction:
app → user → common → realtime,app → playlist → common
4. Distributed Systems
- Redis-based distributed locks
- Session management across instances
- Pub/sub for cross-instance events
- Stateless API design
- Java 17 or higher
- Docker & Docker Compose (Install link)
- Git
- Clone the repository
git clone https://github.com/pfplay/pfplay-backend.git
cd pfplay-backend-java- Start infrastructure services (MySQL, Redis)
docker-compose up -d- Configure application properties
Create app/src/main/resources/application-local.yml:
spring:
datasource:
url: jdbc:mysql://localhost:3306/pfplay
username: your_username
password: your_password
data:
redis:
host: localhost
port: 6379
oauth:
google:
client-id: your_google_client_id
client-secret: your_google_client_secret
twitter:
client-id: your_twitter_client_id
client-secret: your_twitter_client_secret
jwt:
issuer: pfplay
access-token-secret: your_access_token_secret
refresh-token-secret: your_refresh_token_secret- Build and run the application
./gradlew :app:build
./gradlew :app:bootRun --args='--spring.profiles.active=local'The application will start on http://localhost:8080
Stop services
docker-compose downView logs
docker-compose logs -fCheck health endpoint
curl http://localhost:8080/actuator/healthAccess API documentation
http://localhost:8080/spec/api
When running the application, access the Swagger UI at:
http://localhost:8080/spec/api
POST /api/v1/auth/oauth/url- Generate OAuth authorization URLPOST /api/v1/auth/oauth/callback- Handle OAuth callbackPOST /api/v1/auth/logout- User logout
GET /api/v1/users/members/sign- Member OAuth sign-inGET /api/v1/users/guests/sign/**- Guest sign-inGET /api/v1/users/{userId}- Get user infoPUT /api/v1/users/{userId}/profile- Update profilePUT /api/v1/users/{userId}/avatar- Update avatar
POST /api/v1/partyrooms- Create party roomGET /api/v1/partyrooms/{partyroomId}- Get party room infoPUT /api/v1/partyrooms/{partyroomId}- Update party roomDELETE /api/v1/partyrooms/{partyroomId}- Delete party roomPOST /api/v1/partyrooms/{partyroomId}/access- Join party roomPOST /api/v1/partyrooms/{partyroomId}/exit- Leave party roomPUT /api/v1/partyrooms/{partyroomId}/notice- Update noticePOST /api/v1/partyrooms/{partyroomId}/playback/start- Start playbackPOST /api/v1/partyrooms/{partyroomId}/playback/skip- Skip trackPOST /api/v1/partyrooms/{partyroomId}/playback/reaction- Send reaction
POST /api/v1/partyrooms/{partyroomId}/dj-queue/enqueue- Join DJ queuePOST /api/v1/partyrooms/{partyroomId}/dj-queue/dequeue- Leave DJ queuePUT /api/v1/partyrooms/{partyroomId}/dj-queue- Update DJ queue status
GET /api/v1/crews/{crewId}- Get crew infoPUT /api/v1/partyrooms/{partyroomId}/crews/{crewId}/grade- Update crew gradePOST /api/v1/partyrooms/{partyroomId}/crews/{crewId}/penalty- Apply penaltyPOST /api/v1/partyrooms/{partyroomId}/crews/{crewId}/block- Block crew
POST /api/v1/playlists- Create playlist (ROLE_MEMBER required)GET /api/v1/playlists- Query playlistsPATCH /api/v1/playlists/{playlistId}- Rename playlistDELETE /api/v1/playlists- Delete playlistsPOST /api/v1/playlists/{playlistId}/tracks- Add trackDELETE /api/v1/playlists/{playlistId}/tracks- Remove tracks
GET /api/v1/music-search- Search music via YouTube
All protected endpoints require JWT authentication via cookies:
access_token- Access token (24h expiration)refresh_token- Refresh token (7 days expiration)
Cookies are automatically set after successful OAuth login.
Endpoint: ws://localhost:8080/ws
Authentication: Include JWT in handshake headers
/pub/groups/{chatroomId}/send- Send group message/pub/heartbeat- Send heartbeat
Subscribe to receive real-time events:
/sub/groups/{chatroomId}- Group chat messages/sub/events/{partyroomId}/chat-message- Chat messages/sub/events/{partyroomId}/partyroom-access- User join/leave events/sub/events/{partyroomId}/playback-start- Playback start events/sub/events/{partyroomId}/playback-skip- Playback skip events/sub/events/{partyroomId}/playback-reaction- Reaction events/sub/events/{partyroomId}/playback-reaction-motion- Reaction animations/sub/events/{partyroomId}/crew-grade- Crew grade changes/sub/events/{partyroomId}/crew-penalty- Crew penalties/sub/events/{partyroomId}/profile-update- Profile updates/sub/events/{partyroomId}/notice-update- Notice updates/sub/events/{partyroomId}/partyroom-deactivation- Room deactivation
stompClient.send('/pub/groups/' + chatroomId + '/send', {},
JSON.stringify({ content: 'Hello, world!' })
);stompClient.subscribe('/sub/events/' + partyroomId + '/playback-start',
(message) => {
const event = JSON.parse(message.body);
console.log('Playback started:', event);
}
);pfplay-backend-java/
├── common/ # Shared Kernel + infrastructure config
│ └── src/main/java/com/pfplaybackend/api/common/
│ ├── config/ # Configuration (Security, Redis, JPA, Cache, Swagger)
│ ├── domain/value/ # Shared Value Objects (UserId, Duration)
│ ├── entity/ # Base entities
│ ├── enums/ # Common enums
│ └── exception/ # Exception handling (GlobalExceptionHandler)
├── realtime/ # WebSocket infrastructure (zero domain imports)
│ └── src/main/java/com/pfplaybackend/realtime/
│ ├── config/ # WebSocket/STOMP configuration
│ ├── controller/ # Heartbeat controller
│ ├── event/ # Connection/Subscription event listeners
│ ├── interceptor/ # WebSocket handshake interceptor
│ ├── port/ # Port interfaces (WebSocketAuthPort, SessionCachePort)
│ └── sender/ # SimpMessageSender
├── playlist/ # Playlist domain
│ └── src/main/java/com/pfplaybackend/api/playlist/
│ ├── adapter/ # REST controllers, JPA repositories
│ ├── application/ # Use cases, DTOs
│ └── domain/ # Entities, services, value objects
├── user/ # User domain (member, guest, profile, avatar)
│ └── src/main/java/com/pfplaybackend/api/user/
│ ├── adapter/ # REST controllers, JPA repositories
│ ├── application/ # Use cases, ports, DTOs
│ └── domain/ # Entities, services, value objects
├── app/ # Auth, Party, Admin, Bootstrap (composition root)
│ └── src/main/java/com/pfplaybackend/api/
│ ├── auth/ # Authentication & OAuth
│ ├── party/ # Party room domain (rooms, crew, DJ, playback, chat)
│ ├── admin/ # Administrative tools
│ └── bootstrap/ # Cross-module adapters
├── build.gradle # Root Gradle config
├── settings.gradle # Multi-module settings (5 modules)
├── docs/ # Design documents (ADRs, context map, naming)
├── Dockerfile # Container image
└── README.md # This file
Each domain module follows a hexagonal architecture:
{domain}/
├── adapter/
│ ├── in/
│ │ ├── web/ # REST Controllers + payload/
│ │ ├── listener/ # Redis Topic Listeners
│ │ └── stomp/ # WebSocket STOMP Controllers
│ └── out/
│ ├── persistence/ # JPA Repositories + QueryDSL (custom/, impl/)
│ └── external/ # Cross-domain Port Adapters
├── application/
│ ├── service/ # Application Services (use cases)
│ ├── port/out/ # Outbound Port Interfaces
│ └── dto/ # Application DTOs
└── domain/
├── entity/data/ # JPA Data Entities (*Data.java)
├── service/ # Domain Services
├── enums/ # Domain Enums
├── value/ # Value Objects
└── exception/ # Domain Exceptions
The application supports multiple profiles:
local- Local developmentdev- Development environmentprod- Production environment
Activate a profile:
./gradlew :app:bootRun --args='--spring.profiles.active=local'Key configuration properties:
Database
SPRING_DATASOURCE_URL- MySQL connection URLSPRING_DATASOURCE_USERNAME- Database usernameSPRING_DATASOURCE_PASSWORD- Database password
Redis
SPRING_DATA_REDIS_HOST- Redis hostSPRING_DATA_REDIS_PORT- Redis port
OAuth
OAUTH_GOOGLE_CLIENT_ID- Google OAuth client IDOAUTH_GOOGLE_CLIENT_SECRET- Google OAuth secretOAUTH_TWITTER_CLIENT_ID- Twitter OAuth client IDOAUTH_TWITTER_CLIENT_SECRET- Twitter OAuth secret
JWT
JWT_ISSUER- JWT issuerJWT_ACCESS_TOKEN_SECRET- Access token secretJWT_REFRESH_TOKEN_SECRET- Refresh token secret
YouTube API
YOUTUBE_API_KEY- YouTube Data API key
- Notion Page: PFPlay Overview
- KanBan Board: Project Management
- Research Board: Backend Research
- Architecture Decision Records — 5 ADRs on key architectural choices
- Context Map — Bounded context boundaries and relationships
- Naming Convention — Project naming rules
- DDD Maturity Assessment — Domain-driven design maturity evaluation
- Refactoring Roadmap — Future improvement plans
Copyright (c) PFPlay. All rights reserved.
Built with Spring Boot 3.2.3 | Java 17 | MySQL 8.0.30 | Redis