The application belongs to the REST service type with WebSocket (STOMP) support for real-time message delivery. At its core are key data models — UserAccount, Profile, Swipe/Match, Message, ProfilePhoto — each serviced by a dedicated domain module/service and possessing its own API.
The project uses non-distributed modular deployment with the possibility of further service extraction and isolation of responsibility, as well as an emphasis on environment isolation and ease of local launch.
-
System Components:
- Service Level: Domain modules/services
Auth,Profiles,Matching(swipes + matches),Messaging(messages + readAt),Media(photos). - Data Level: Separate schemas/databases for services (migration to Database per Service), where users/profiles/swipes/matches/messages are logically separated.
- Caching Level: A unified caching strategy (e.g., Redis) for "hot" reads (recommendations/profiles) and invalidation control.
- Asynchronous Level: A message broker (e.g., RabbitMQ/Kafka) for events like
match_created,message_sent, and for scaling the real-time component.
- Service Level: Domain modules/services
-
Containerization: Each service is packaged into an isolated Docker image;
docker composeis used for local building and launching. -
Local Launch: One-click launch of the stack with a single command without manual configuration of the infrastructure (database, cache, broker).
| Technology | Purpose | Justification |
|---|---|---|
| Java 17 & Spring Boot 3.x | Main development stack | Matches the current application and allows rapid support for REST and STOMP WebSockets. |
| PostgreSQL | Main relational DBMS | Reliable support for transactions and relationships between domain entities. |
| JWT + Spring Security | Authentication and authorization | Stateless security, convenient integration with @PreAuthorize and resource access control. |
| Redis Cache | Data caching | Stable TTL and unified invalidation to accelerate the retrieval of recommended profiles/lists. |
Docker + docker compose |
Deployment | Standardizes environments and simplifies problem reproduction/testing. |
- Scalability: Separating domain logic into modules/services allows scaling the most loaded components (typically
MessagingandMatching) independently of each other. - Performance: Multi-tier caching reduces the load on the database during frequent requests for reference data.
- Supportability: The codebase is organized according to a clear layered architecture: controllers (handling HTTP requests) → services (business logic) → repositories (data access). The use of DTOs (Data Transfer Objects) separates the internal representation of entities from the data returned to the client, simplifying API changes.
- Reliability: Validation of incoming data at the DTO level using standard annotations (
@NotNull,@Size, etc.) prevents incorrect data from entering the database and business logic. - Testability: Due to the loose coupling of components (controllers depend on service interfaces, services depend on repository interfaces), unit testing of each layer in isolation is ensured.
- Exception Handling: Centralized management of errors through
@RestControllerAdvice(GlobalExceptionHandler), returning unified responses with clear error messages and appropriate HTTP statuses. - Validation: Application of the Jakarta Bean Validation specification at the DTO level (
@NotNull,@Size,@Email). This ensures automatic verification of input data correctness before passing it to the service layer. - Entity Mapping: Conversion between database entities and DTOs is executed using Lombok and manual mapping in services, ensuring a clear separation between the internal data representation and API contracts.
- Caching: Implemented at two levels: using Spring Cache annotations (
@Cacheable,@CacheEvict) for frequently requested, rarely changed data, and a cache in the service layer to optimize resource-intensive calculations.
flowchart LR
client["Client (Web/Mobile)"] --> gateway["API entry"]
client --> ws["WebSocket endpoint"]
gateway --> auth["Auth service"]
gateway --> profiles["Profiles service"]
gateway --> matching["Matching service"]
gateway --> messages["Messaging service"]
gateway --> media["Media service"]
auth --> users[("Users DB")]
profiles --> prof[("Profiles DB")]
matching --> matchdb[("Swipes+Matches DB")]
messages --> msgdb[("Messages DB")]
media --> storage[("Photos storage")]
matching --> cache[("Redis cache")]
profiles --> cache
messages --> broker[("Message broker")]
Diagram Description:
- Functional Blocks: The system is divided into five domain microservices (
Auth,Profiles,Matching,Messaging,Media), each responsible for a specific boundary of the business scope. - Functional Layers: Within the services, the API/Controller, Business Logic (Service), and DAO/Repository layers are defined.
- Connections:
- Synchronous (HTTP/REST): All client requests pass through a single entry point — the API Gateway, which routes them to the appropriate services based on the request path.
- Data Access: Direct interaction of services with their own databases and caches, along with asynchronous event handling through the broker.
The current state of meet_app is a highly optimized monolithic delivery. The backend handles the core business logic, database persistence, and websocket routing, while the frontend is served statically on the same origin (port 9090), solving CORS completely.
classDiagram
direction LR
class UserAccount {
+Long id
+String username
+String email
+String passwordHash
+LocalDate dateOfBirth
+Gender gender
+Gender lookingForGender
+Boolean enabled
+Set roles
}
class Profile {
+Long id
+String displayName
+String bio
+String city
+Boolean active
}
class ProfilePhoto {
+Long id
+String url
+Boolean mainPhoto
}
class Swipe {
+Long id
+SwipeDirection direction
}
class Match {
+Long id
}
class Message {
+Long id
+String content
+OffsetDateTime createdAt
+OffsetDateTime readAt
}
class SwipeDirection {
<<enumeration>>
LIKE
DISLIKE
}
UserAccount "1" o-- "many" Profile : owner
Profile "1" o-- "many" ProfilePhoto : profile
UserAccount "1" o-- "many" Swipe : fromUser
UserAccount "1" o-- "many" Swipe : toUser
UserAccount "1" o-- "many" Match : user1
UserAccount "1" o-- "many" Match : user2
Match "1" o-- "many" Message : match
UserAccount "1" o-- "many" Message : sender
This diagram demonstrates how messages sent via the REST API POST are captured, written to the database, and immediately broadcasted to the WebSocket topic /topic/chat.{matchId} in real-time.
sequenceDiagram
participant Client as React Client (UI)
participant Sec as JwtAuthenticationFilter
participant RestCtrl as MessageController
participant Service as MessageService
participant MsgRepo as MessageRepository
participant STOMP as SimpMessagingTemplate
participant WSClient as Subscriber (/topic/chat.matchId)
Client->>Sec: POST /api/messages (HTTP Request + Bearer Token)
Sec->>RestCtrl: Forward Authenticated Request (Principal Set)
RestCtrl->>Service: sendMessage(senderId, request)
Service->>MsgRepo: save(Message Entity)
MsgRepo-->>Service: Persisted Message
Service->>STOMP: convertAndSend(/topic/chat.matchId, MessageResponse)
STOMP-->>WSClient: Broadcast Real-Time Message
Service-->>RestCtrl: MessageResponse
RestCtrl-->>Client: 201 Created (JSON Response)
This diagram illustrates how React's component hierarchy, context state providers (AuthContext, ThemeContext), routing guards, page views, and API layers are organized and interact within the single-page application.
flowchart TD
App[App.tsx Router] --> ThemeProvider[ThemeProvider / ThemeContext]
ThemeProvider --> AuthProvider[AuthProvider / AuthContext]
AuthProvider --> RequireAuth{RequireAuth Guard}
RequireAuth -->|Authenticated| PrivatePages[Private Pages]
RequireAuth -->|Anonymous| PublicPages[Public Pages]
PublicPages --> LoginPage[LoginPage]
PublicPages --> RegisterPage[RegisterPage]
PublicPages --> VerifyPage[VerifyEmailPage]
PrivatePages --> SwipePage[SwipePage]
PrivatePages --> MatchesPage[MatchesPage]
PrivatePages --> ChatPage[ChatPage]
PrivatePages --> ProfilePage[ProfilePage]
SwipePage --> SwipeCard[SwipeCard Component]
SwipePage --> API_Profile[API: getRecommendations]
SwipeCard -->|Swipe Action| API_Swipe[API: swipe]
ChatPage --> API_Messages[API: getMessages / sendMessage]
ChatPage --> STOMP[STOMP.js Over WS Client]
ProfilePage --> API_Profile_Me[API: getMyProfiles / updateProfile]
ProfilePage --> API_Photos[API: uploadPhoto]
API_Profile & API_Swipe & API_Messages & API_Profile_Me & API_Photos --> HTTP[http.ts Fetch Client]
HTTP -->|Authorization: Bearer JWT| Server[(Spring Boot Backend)]
This diagram traces how mouse or touch gestures are captured in the user interface, animated via state transitions, and subsequently communicated to the backend API services.
sequenceDiagram
participant User as User (Touch/Mouse)
participant Card as SwipeCard (Component)
participant Page as SwipePage (Parent)
participant API as swipes.ts (API Client)
User->>Card: Pointer Down (pointerStartX = ClientX)
Card->>Card: setDragging(true)
loop Pointer Move (dragX = ClientX - pointerStartX)
User->>Card: Drag Card
Card->>Card: setDragX(dragX) & Apply CSS Rotation
end
User->>Card: Pointer Up (Release)
alt dragX >= Threshold (+120px)
Card->>Card: triggerLike (Fly right animation)
Card->>Page: onLike() callback
Page->>API: swipe(targetUserId, like = true)
else dragX <= -Threshold (-120px)
Card->>Card: triggerDislike (Fly left animation)
Card->>Page: onDislike() callback
Page->>API: swipe(targetUserId, like = false)
else Inside Threshold bounds
Card->>Card: reset (Animate back to center)
end
- Standard Layered Architecture: The project clearly separates the levels of representation (controllers), business logic (services), and data access (repositories), which simplifies code maintenance.
- DTOs as API Contracts: Request/response payloads are formed through DTOs (
AuthDtos,ProfileDtos,SwipeDtos,MessageDtos), separating the database model from the public API. - Security (JWT + Spring Security): Authentication is implemented via
JwtAuthenticationFilter, and access to resources is restricted using the@PreAuthorize("hasRole('USER')")annotation in controllers. Accounts that are not verified (enabled = false) are explicitly blocked by the JWT filter. - Rate Limiting: Request frequency is restricted by the
@RateLimitannotation and theRateLimitAspectimplementation, which uses a sliding-window queue over a thread-safeConcurrentHashMapwith anip:pathkey. - Real-time via WebSocket/STOMP: Messages are dispatched in real-time through the STOMP protocol. A custom
ChannelInterceptorinWebSocketConfig.javaintercepts the STOMPCONNECTframe to extract and validate the JWT token, safely authenticating the WebSocket session. - File Storage (Photos):
PhotoServicesaves files directly to the absolute operating system directory retrieved fromPhotoStorageProperties, whileWebConfigprovides public resource mapping via/photos/**. - Monolithic Database: All entities are stored in a single database within a single Spring Boot application. It runs on an in-memory
H2database by default, with configuration pathways prepared for a seamless production switch to PostgreSQL. - Centralized Error Handling:
GlobalExceptionHandlerreturns a unifiedErrorResponseformat for domain, validation, and JSON parsing errors.
- Component-Driven SPA Architecture: Built on React 18 and TypeScript. The interface is composed of independent, highly reusable components and pages (
LoginPage,RegisterPage,SwipePage,MatchesPage,ChatPage), managed client-side via React Router DOM. - Vite Build Tooling: Next-generation build tool featuring native ES modules for instantaneous development server start and Hot Module Replacement (HMR). The proxy configuration in
vite.config.tsautomatically forwards relative/api,/photos, and/wsrequests to the port9090backend. - Reactive State Management: Global states such as authentication tokens (JWT) and user themes (Light/Dark mode) are managed natively via the React Context API (
AuthContext,ThemeContext) and persisted insidelocalStorage. - Custom Drag Gesture Physics (
SwipeCard.tsx): Card-swiping mechanics are implemented natively using browser pointer events (onPointerDown,onPointerMove,onPointerUp). Mathematical transformation coordinates mapping translationXand rotation are applied dynamically to the CSS. A uniquekey={top.id}forces React to unmount/mount card instances, resetting coordinate state on every swipe. - Optimistic UI Chat Updates: When a message is sent, the frontend immediately appends it to the local UI state without waiting for the WebSocket or REST server confirmation, achieving zero-latency interaction.
- Hook-Based Internationalization (i18n): Features a lightweight, dependency-free translation pipeline (
i18n.ts) driven by custom state hooks, enabling complete UI localization swap (RU/EN) in real-time.
| Criteria | "To Be" Target Design (Plan) | "As Is" Current Monolith (Reality) |
|---|---|---|
| Coupling | Weak (Independent services with clear REST API contracts) | Moderate (A single Spring Boot monolith sharing a single database) |
| Orchestration | API Gateway acting as a single entry point for routing | None (Vite index.html and REST routes served directly on port 9090) |
| Layers | Layered architecture preserving DTO contracts for APIs | Clear Controller/Service/Repository layered separation within the monolith |
| Fault Tolerance | Microservice circuit breakers, retry patterns, and fallbacks | None (Exceptions are captured globally by the Global Exception Handler) |
| Caching | Unified Redis cache cluster with TTL policies | Local in-memory LruCache on recommendation queries |
| Authentication | Unified SSO Gateway / OAuth2 | JWT filter verifying signatures locally on every request |
| Database Isolation | Database per Service (separate schemas / instances) | Shared database (H2 in-memory, logical separation of JPA tables) |
- Absence of API Gateway: In the current "As Is" setup, the client communicates directly with monolithic controllers.
Reason: Simplicity of development and debugging at the initial stage. Eliminating an extra infrastructure layer accelerates local launches. However, this duplicates cross-cutting concerns (security, logging, rate limiting) within the application. - Shared Monolithic Database: All entities share a single database, with direct JPA-model relationships.
Reason: Simplification of transactional and referential integrity (foreign keys between tables). This is typical for the early stages of a project. However, it prevents independent service scaling. - In-Memory Rate Limiting and Caching: The rate limiter uses a JVM-local sliding window, and the cache uses a custom
LruCacheclass.
Reason: Minimizing external infrastructure dependencies for local setups. If the application is scaled horizontally across multiple servers, a centralized system (such as Redis) is required. - Local File Storage: Uploaded photos are stored on the local filesystem.
Reason: Ease of implementation. In a production environment, this must be migrated to a shared object storage solution (such as S3 or MinIO).
- Shared Library Implementation: Extract repeating DTO models, CORS configurations, validation constraints, and utility classes into a standalone shared library module to reduce duplicate definitions.
- API Gateway Integration: Introduce Spring Cloud Gateway as a single entry point to centralize routing, rate limiting, and edge security protocols.
- Database per Service Migration: Decouple the database. Move domain tables into isolated schemas or separate database instances, replacing direct JPA relationships with event-driven message queues.
- Distributed Caching & Rate Limiting: Transition the local
LruCacheandRateLimitAspectmaps to a Redis cluster to enable distributed state sharing across multiple backend nodes. - Object Storage Migration: Refactor
PhotoServiceto upload assets directly to an S3-compatible object storage provider, saving only the public URLs in the database.
The current "As Is" architecture is a highly optimized, fully functional monolithic client-server system. It implements crucial production-grade features: state-of-the-art JWT Security, custom sliding-window Rate Limiting, real-time STOMP messaging, and a bundled single-port static delivery. The existing constraints are typical for monolithic designs, and the codebase has been structured with clear domain boundaries, making it fully prepared for a seamless transition to the "To Be" microservices target architecture.
- Backend Foundation: This project includes modified backend logic originally developed by @kirirut under the Meet App repository.
- Frontend & Modern Features: The React SPA client, TypeScript migration, dynamic rate limiter aspect, WebSocket token interceptors, and production single-port serving pipeline were designed and implemented by @vSAQv.