Skip to content

Backend Architecture

Big Picture

FoodApp is organized as a modular Spring Boot monolith. That is a good fit for the current stage: one deployable backend, clear domain packages, relational consistency, and enough Redis/WebSocket support to scale operational features later.

The application is not a toy CRUD backend. It already has real delivery-app concerns:

  • Separate roles and ownership checks.
  • Branch-specific menus and pricing.
  • Location-aware restaurant discovery.
  • Payment webhooks and payment cleanup.
  • Realtime order updates.
  • External route providers.
  • Redis caches and geospatial indexes.
  • Driver live location ingestion.

Backend Module Diagram

Module Diagram

Layering

Most packages follow this structure:

text
controller -> service -> repository -> entity
              |
              -> mapper / dto / specification / validator

Controllers should stay thin. They parse HTTP inputs and delegate to services.

Services contain business rules: ownership, status transitions, pricing, payment initiation, cache decisions, and validation.

Repositories own database queries and specifications.

DTOs are used for responses and request bodies. Entity classes should not be returned directly from controllers.

Main Packages

PackagePurpose
auth_usersRegistration, login, user profile, user filtering, account activation/deactivation, password changes.
roleRole CRUD and role entities.
securityJWT generation/validation, request filter, user details, CORS, Spring Security chain.
restaurantRestaurants, branches, opening hours, reviews, branch menu items, branch option overrides, restaurant discovery.
categoryMenu categories and restaurant discovery categories.
menuMaster menu items, options, variants, branch manager menu views, cached branch menus.
cartCustomer basket, checkout preview, totals, tips, delivery note, payment method selection.
orderOrder creation, order status updates, assignment, order history, WebSocket event publishing.
paymentPayment methods, payment listing/filtering, PokPay integration, refunds, webhooks.
deliverToCustomer saved delivery locations and live driver coordinate submission.
locationRoute providers and distance matrix routing manager.
analyticsRevenue, customer, popular item, order, and delivery earnings metrics.
awsS3 upload service.
schedulerInternal job endpoints and startup import helpers.
configRedis, WebSocket, Google Maps, ModelMapper, JTS, application beans.
exceptionsGlobal error handling and domain exceptions.
responseShared API response wrapper and serializable page wrapper.

Shared Response Shape

Most endpoints return:

json
{
  "statusCode": 200,
  "message": "Human readable message",
  "data": {},
  "meta": {}
}

meta is optional. data can be an object, array, page, or omitted.

Authentication

REST authentication uses:

http
Authorization: Bearer <jwt>

AuthFilter extracts the token, validates it, loads the user, checks token version, checks active account status, and blocks users who must change their password except for:

http
PATCH /api/users/change-password

Important security details:

  • Stateless sessions are used.
  • CSRF is disabled for the API.
  • CORS currently allows http://localhost:5173 and http://localhost:8080.
  • @PreAuthorize method annotations are the real role boundary for many endpoints.
  • Some URL patterns are public in the HTTP filter but still protected by method annotations.

JWT Token Versioning

User.tokenVersion is stored in the database and included in the JWT. If the database value differs from the token value, the backend returns:

json
{ "error": "Session expired. Please log in again." }

This supports forced logout after password changes or security events.

Account Deactivation

User.isActive controls whether a user can keep using the system. If inactive, the auth filter returns:

json
{ "error": "User account is deactivated." }

Required Password Change

Manager-created users can be forced to change password through User.requirePasswordChange. If set, all routes except password change are blocked with:

json
{
  "error": "Password change required.",
  "code": "PASSWORD_CHANGE_REQUIRED"
}

WebSocket Architecture

WebSocket endpoint:

text
/ws

The backend uses STOMP with a broker relay:

text
/topic
/queue

RabbitMQ STOMP relay is configured with:

text
host = spring.rabbitmq.host
port = 61613
login = spring.rabbitmq.username
passcode = spring.rabbitmq.password

Clients pass the JWT in the STOMP CONNECT native header:

text
Authorization: Bearer <jwt>

WebSocketConfig extracts the user email from the token and sets the WebSocket principal. This is important because personal driver updates use:

java
convertAndSendToUser(driverEmail, "/queue/updates", order)

WebSocket Topics

TopicAudiencePurpose
/topic/branch.{branchId}.managerBranch dashboardOrder updates for a branch.
/topic/delivery.global.unassignedDelivery driversGlobal pool of unassigned orders. Uses wsAction as ADD or REMOVE.
/user/queue/updatesIndividual delivery driverPersonal assigned-order updates.

Important: the existing older delivery doc used slash-style topics. The current code uses dot-style topic names for RabbitMQ topic exchange compatibility.

Redis Responsibilities

Redis is used for three different purposes:

PurposeKey/cacheNotes
Restaurant branch GEO indexrestaurant:locationsStores active branch IDs by coordinates for nearby discovery.
Dashboard restaurant cachedashboardRestaurantsFive-minute cache in non-dev profile.
Branch menu cachebranchMenusTwo-hour cache in non-dev profile.
Distance matrix cachedistanceMatrixKey includes branch, rounded user location, and provider.
User profile cacheuserProfileCacheFive-second micro-cache.
Live driver locationdriver_loc:{driverId}JSON payload with one-hour TTL.

Location and Routing Strategy

Restaurant discovery uses a two-stage design:

  1. Redis GEO narrows candidate branches within the system maximum delivery radius of 8 km.
  2. Database queries and route calculations decide which restaurants/branches are actually deliverable and how they should be sorted.

Route providers:

Provider enumImplementationUse
HIGH_ACCURACYGoogle Distance MatrixPrecise single route calculations, especially checkout.
STANDARDOpenRouteService matrixCheaper/on-premise bulk route calculations.

The pricing service calculates:

  • deliverability,
  • delivery fee,
  • delivery time range,
  • road distance,
  • route duration.

Image Storage

The backend uploads images to AWS S3 and stores URLs in PostgreSQL. It does not store raw image bytes in the database.

CloudFront is configured through:

properties
aws.cloudfront.domain=...

Thumbnailator is included and should be used where images need resizing/compression before upload.

Internal Jobs

Internal job endpoints are protected by:

http
X-Internal-Secret: <app.internal-job-secret>

The current internal jobs:

  • Daily reset for order counts.
  • Payment cleanup for expired pending payments.

Use an external scheduler, cron, cloud job, or Kubernetes CronJob to call these endpoints.