System Overview¶
Viana is a single Spring Boot monolith. All modules — auth, rides, location, ratings, vehicles, documents — live in the same process. The external dependencies are PostgreSQL (persistent data), Redis (ephemeral location data), and S3 (document file storage).
High-level diagram¶
graph TB
subgraph Clients
C[Customer App]
D[Driver App]
A[Admin]
end
subgraph Viana Monolith [:8080]
AUTH[Auth Module<br/>JWT · Refresh Tokens]
RIDES[Rides Module<br/>Matching · Lifecycle]
LOC[Location Module<br/>Geo · Fares]
VEH[Vehicles Module<br/>Vehicles · Documents]
RATE[Ratings Module]
WS[WebSocket<br/>STOMP / SockJS]
ASYNC[Async Executor<br/>10–50 threads]
end
subgraph Data
PG[(PostgreSQL + PostGIS<br/>:5432)]
RD[(Redis Geo<br/>:6379)]
S3[(AWS S3<br/>Documents)]
end
C -->|REST /api| AUTH
D -->|REST /api| AUTH
A -->|REST /api/admin| AUTH
C -->|STOMP /ws| WS
D -->|STOMP /ws| WS
AUTH --> PG
RIDES --> PG
RIDES --> ASYNC
LOC --> PG
LOC --> RD
VEH --> PG
VEH --> S3
RATE --> PG
WS --> RIDES
WS --> LOC
Module breakdown¶
| Module | Package | Responsibility |
|---|---|---|
auth |
modules/auth |
User registration, login, JWT generation, token lifecycle, refresh |
users |
modules/users |
Driver and Customer profiles |
rides |
modules/rides |
Ride creation, matching, lifecycle state machine |
location |
modules/location |
Driver location updates (Redis + PostGIS), fare estimation, nearby search |
vehicles |
modules/vehicles |
Vehicle CRUD, document upload and admin review |
ratings |
modules/ratings |
Post-ride ratings for drivers and customers |
notifications |
modules/notifications |
WebSocket push to drivers (ride offers) and customers (location, status) |
configuration |
configuration |
Security, WebSocket, Redis, Async, S3, OpenAPI |
Request flow¶
Every inbound HTTP request passes through:
Request
→ CorsFilter
→ JwtAuthenticationFilter (validates Bearer token, sets SecurityContext)
→ UsernamePasswordAuthenticationFilter
→ DispatcherServlet
→ @PreAuthorize role check
→ Controller → Service → Repository
→ Response
Every WebSocket handshake passes through:
STOMP CONNECT
→ JwtHandshakeInterceptor (reads Authorization: Bearer header, validates JWT)
→ JwtHandshakeHandler (sets Principal = userId)
→ MessageBroker
Technology decisions¶
Why a monolith? At this stage, a monolith is the right choice. Ride matching, location updates, and WebSocket notifications all need to share in-process state (the async timer map, the messaging template). A service mesh would add latency and coordination overhead with no benefit at this scale.
Why Redis for location?
Driver positions update every few seconds. PostgreSQL with PostGIS is not designed for that write frequency. Redis Geo commands (GEOADD, GEORADIUS) are O(N+log M) on writes and reads, and the entire dataset fits in RAM. PostGIS is used as the fallback path for persistent spatial queries.
Why Reactor Mono for the acceptance timer?
The 30-second acceptance window must not block a thread. Thread.sleep inside @Async would waste a thread for each open ride. Mono.delay schedules the cancellation on Reactor's scheduler without holding a JVM thread.