Skip to content

About

πŸ” A microservice-ready modular monolith backend for a food delivery app built with Kotlin & Ktor. Clean architecture with explicit extraction seams - event bus, query ports & per-module migrations - scale from one JAR to independent services without rewriting business logic. πŸš€

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Foodike Backend

A microservice-ready modular monolith backend for a food delivery app, built with Kotlin and Ktor. It is being developed as a reference architecture that can scale from a single deploy to independent microservices without rewriting business logic.

Why This Exists

Most food delivery backend tutorials are either too simple (single-file CRUD) or jump straight to microservices (overkill for 99% of projects). This project sits in the sweet spot: a production-grade modular monolith where every module is shaped like a future microservice, with explicit extraction seams documented and enforced by Gradle.

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   Single JVM Process                β”‚
β”‚                                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚              shared/                        β”‚    β”‚
β”‚  β”‚   common Β· events Β· auth Β· persistence      β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”‚
β”‚  β”‚  user    β”‚ β”‚restaurantβ”‚ β”‚  order   β”‚            β”‚
β”‚  β”‚ service  β”‚ β”‚ service  β”‚ β”‚ service  β”‚            β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”‚
β”‚  β”‚ payment  β”‚ β”‚ notific. β”‚ β”‚ tracking β”‚            β”‚
β”‚  β”‚ service  β”‚ β”‚ service  β”‚ β”‚ service  β”‚            β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β”‚
β”‚         β”‚            β”‚            β”‚                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚         EventBus (SharedFlow)               β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The target architecture is for each service module to contain its own domain/, infrastructure/, api/, di/, and migrations/. The current repository has a lighter scaffold in place and is moving toward that structure incrementally.

Current Status

Implemented today:

  • Multi-module Gradle build
  • Shared common, events, auth, and persistence modules
  • Service module boundaries for user, restaurant, order, payment, notification, and tracking
  • Ktor application shell in app/
  • Placeholder routes and minimal domain models
  • Basic integration test for app startup

Planned but not implemented yet:

  • PostgreSQL/PostGIS setup
  • Redis integration
  • Flyway migrations
  • Razorpay, Firebase, and S3 integrations
  • Swagger/OpenAPI docs
  • Micrometer/Prometheus metrics
  • Full domain services, repositories, DTOs, and production flows
  • Kotest, MockK, and Testcontainers-based test suite

Tech Stack

Layer Tool
Language Kotlin 2.x
Framework Ktor 3.x (Netty)
Serialization kotlinx.serialization
Database H2 today, PostgreSQL/PostGIS planned
ORM Exposed DSL
Migrations Planned
Cache Planned
DI Koin 4
Event Bus In-process SharedFlow (swappable to RabbitMQ)
Auth JWT (java-jwt)
Payments Planned
Push Notifications Planned
Storage Planned
Observability Logback today, broader observability planned
Testing Ktor testApplication today, broader test stack planned

Getting Started

Prerequisites

  • JDK 17+
  • No external infrastructure is required for the current scaffold

Run Locally

# Clone the repo
git clone https://github.com/gautam84/foodike-backend.git
cd foodike-backend

# Run the application
./gradlew :app:run

The server starts at http://localhost:8080.

Environment Variables

The current scaffold only uses application config from app/src/main/resources/application.yaml.

Planned environment variables for future infrastructure:

Variable Description Required
DB_URL Postgres JDBC URL Planned
DB_USER Postgres username Planned
DB_PASSWORD Postgres password Planned
REDIS_URL Redis connection URL Planned
JWT_SECRET Secret for signing JWTs Planned
JWT_ISSUER JWT issuer claim Planned
RAZORPAY_KEY Razorpay API key For payments
RAZORPAY_SECRET Razorpay API secret For payments
FIREBASE_CREDENTIALS Path to Firebase service account JSON For notifications
S3_BUCKET S3 bucket name for image uploads For storage
S3_REGION AWS region For storage

Run Tests

# Unit tests
./gradlew :tests:test --tests "com.example.foodike.unit.*"

# All tests
./gradlew :tests:test

Project Structure

foodike-backend/
β”œβ”€β”€ shared/
β”‚   β”œβ”€β”€ common/          # Value objects, exceptions, utilities
β”‚   β”œβ”€β”€ events/          # Event contracts + EventBus interface
β”‚   β”œβ”€β”€ auth/            # JWT validation, shared across all modules
β”‚   └── persistence/     # Database factory, query helpers
β”‚
β”œβ”€β”€ services/
β”‚   β”œβ”€β”€ user-service/          # Auth (OTP), profiles, addresses
β”‚   β”œβ”€β”€ restaurant-service/    # Restaurants, menus, search
β”‚   β”œβ”€β”€ order-service/         # Cart, orders, coupons
β”‚   β”œβ”€β”€ payment-service/       # Razorpay integration
β”‚   β”œβ”€β”€ notification-service/  # FCM, SMS, email
β”‚   └── tracking-service/      # WebSocket order tracking
β”‚
β”œβ”€β”€ app/                 # Ktor entry point, plugin config, Koin wiring
└── tests/               # Unit + integration tests

Target per-service structure:

services/xyz-service/
β”œβ”€β”€ domain/
β”‚   β”œβ”€β”€ model/           # Entities and value objects
β”‚   β”œβ”€β”€ repository/      # Interfaces (ports)
β”‚   β”œβ”€β”€ service/         # Business logic
β”‚   └── port/            # Cross-service query interfaces (if needed)
β”œβ”€β”€ infrastructure/
β”‚   β”œβ”€β”€ persistence/
β”‚   β”‚   β”œβ”€β”€ tables/      # Exposed table definitions
β”‚   β”‚   └── *Impl.kt    # Repository implementations
β”‚   β”œβ”€β”€ adapter/         # In-process adapters for query ports
β”‚   └── events/          # Event publishers and consumers
β”œβ”€β”€ api/
β”‚   β”œβ”€β”€ routes/          # Ktor route definitions
β”‚   β”œβ”€β”€ dto/             # Request/response objects
β”‚   └── mapper/          # Domain ↔ DTO mappers
β”œβ”€β”€ di/
β”‚   └── XyzModule.kt    # Koin module
└── migrations/
    └── db/migration/    # Flyway SQL files

Module Dependency Rules

shared/common        ← no dependencies
shared/events        ← common
shared/auth          ← common
shared/persistence   ← common

services/*           ← shared modules only (NEVER other services)

app                  ← all services (composition root)

The critical rule: service modules should never depend on each other. The project is structured around that rule, and module dependencies should continue to preserve it. If order-service needs data from restaurant-service, it should go through a query port interface rather than a direct import.

Three Extraction Seams

This architecture is intended so that extracting any module into an independent microservice can happen with minimal business-logic changes. These seams are part of the target design and are only partially implemented today:

1. Event Bus

Services communicate asynchronously through EventBus. Today the repo includes an in-process event bus. A broker-backed event bus is planned for extraction scenarios.

// Today (monolith)
single<EventBus> { InProcessEventBus() }

// Planned after extraction
single<EventBus> { RabbitMqEventBus(connectionFactory) }

2. Query Ports

When a service needs synchronous data from another service, it should call a port interface. The concrete in-process and HTTP adapters shown below are target examples and are not fully implemented yet.

// Target monolith form
single<MenuQueryPort> { InProcessMenuAdapter(get()) }

// Planned extraction form
single<MenuQueryPort> { HttpMenuClient(httpClient, env("RESTAURANT_SERVICE_URL")) }

3. Per-Module Migrations

Each service is intended to own its own migrations. Flyway integration is planned and not wired into the current scaffold yet.

Extraction Checklist

To extract any service into an independent microservice:

  1. Copy services/xyz-service/ into its own Gradle project
  2. Add its own Application.kt and Dockerfile
  3. Swap InProcessEventBus β†’ RabbitMqEventBus
  4. Swap InProcess*Adapter β†’ Http*Client for query ports
  5. Point Flyway at a dedicated database
  6. Add to API gateway route config
  7. Remove from monolith's settings.gradle.kts and AppModule.kt

API Overview

Auth

  • POST /auth/send-otp β€” send OTP to phone
  • POST /auth/verify-otp β€” verify and get JWT pair
  • POST /auth/refresh β€” refresh access token

Users

  • GET /users/me β€” current profile
  • PUT /users/me β€” update profile
  • GET /users/me/addresses β€” list addresses
  • POST /users/me/addresses β€” add address
  • DELETE /users/me/addresses/{id} β€” remove address

Restaurants

  • GET /restaurants β€” list (paginated, filterable)
  • GET /restaurants/nearby?lat=&lng=&radius= β€” geo query
  • GET /restaurants/{id} β€” detail
  • GET /restaurants/{id}/menu β€” grouped menu

Cart

  • GET /cart β€” current cart
  • POST /cart/items β€” add item
  • PUT /cart/items/{id} β€” update quantity
  • DELETE /cart/items/{id} β€” remove item
  • DELETE /cart β€” clear
  • POST /cart/coupon β€” apply coupon

Orders

  • POST /orders β€” place order
  • GET /orders β€” list (paginated)
  • GET /orders/{id} β€” detail
  • PATCH /orders/{id}/status β€” update status
  • WS /track/{orderId} β€” live tracking

Payments

  • POST /payments/initiate β€” create Razorpay order
  • POST /payments/verify β€” verify payment signature

Search

  • GET /search?q=&type= β€” full-text search

The endpoint list above reflects the target API surface. The current implementation only exposes a small subset of placeholder routes while the modules are being built out.

License

This project is licensed under the MIT License. See LICENSE.md for details.

Contributing

Contributions are welcome! Please read CONTRIBUTING.md before submitting a PR.

About

πŸ” A microservice-ready modular monolith backend for a food delivery app built with Kotlin & Ktor. Clean architecture with explicit extraction seams - event bus, query ports & per-module migrations - scale from one JAR to independent services without rewriting business logic. πŸš€

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages