Skip to content

subramanians29/tracker

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Parcel Tracker

A supply-chain parcel tracking REST API built with Java 17, Spring Boot 3, MongoDB, RabbitMQ, and JWT authentication. Designed to demonstrate production-grade backend architecture: async messaging, secure APIs, containerised deployment, and a clean layered codebase.


Architecture

Client
  │
  ▼
ParcelController  ──►  ParcelService  ──►  ParcelRepository (MongoDB)
                             │
                             ▼
                    StatusUpdatePublisher
                             │
                             ▼
                    RabbitMQ Exchange ( or Kafka )
                             │
                             ▼
                    StatusEventConsumer
                             │
                             ▼
                    StatusEventRepository (MongoDB)

Flow: When a parcel's status is updated, the service persists the new status to MongoDB and publishes a StatusUpdateEvent to RabbitMQ. The consumer picks up the event asynchronously and writes a full audit trail to the status_events collection. This decouples the write path from the audit/notification path — the same pattern used in warehouse and logistics systems.


Tech Stack

Layer Technology
Language Java 17
Framework Spring Boot 4.0, Spring MVC, Spring Data, Spring Security
Database MongoDB
Messaging RabbitMQ (AMQP), Kafka
Auth JWT (jjwt 0.12.3), BCrypt
Build Maven
Infra Docker, Docker Compose

Running Locally (Option 1 — Docker Compose, recommended)

Prerequisites: Docker Desktop installed and running.

# Clone the repo
git clone URI
cd tracker

# Start MongoDB + RabbitMQ + app
docker-compose up --build

The API is live at http://localhost:8080. RabbitMQ management UI: http://localhost:15672 (guest / guest).


API Reference

Authentication

All parcel endpoints require a JWT bearer token. Register a user first, then use the token.

Register

POST /api/auth/register
Content-Type: application/json

{
  "username": "warehouse_ops",
  "password": "securepass123"
}

Response:

{
  "token": "eyJhbGciOiJIUzI1NiJ9...",
  "username": "warehouse_ops"
}

Login

POST /api/auth/login
Content-Type: application/json

{
  "username": "warehouse_ops",
  "password": "securepass123"
}

Parcels

All requests below require the header:

Authorization: Bearer <your_token>

Create Parcel

POST /api/parcels
{
  "description": "Electronics batch — order #4821",
  "origin": "Mumbai Warehouse",
  "destination": "Distribution Centre"
}

Response: 201 Created

{
  "trackingNumber": "PT-A1B2-C3D4",
  "description": "Electronics batch — order #4821",
  "origin": "Mumbai Warehouse",
  "destination": "Amsterdam Distribution Centre",
  "status": "CREATED",
  "createdAt": "2024-05-10T09:00:00",
  "updatedAt": "2024-05-10T09:00:00"
}

Update Status

PUT /api/parcels/{trackingNumber}/status ( Kafka Variant - /api/kafka/parcels/{trackingNumber}/status )
{
  "status": "IN_TRANSIT",
  "message": "Departed Mumbai hub — en route to Dubai"
}

Valid statuses (in order):

CREATED → IN_TRANSIT → OUT_FOR_DELIVERY → DELIVERED
                    └──────────────────→ FAILED

Get Status History

GET /api/parcels/{trackingNumber}/history ( Kafka Variant - /api/kafka/parcels/{trackingNumber}/history )

Returns the full audit trail of every status change, persisted via RabbitMQ consumer.


Project Structure

src/main/java/com/subramanians/parceltracker/
├── config/
│   ├── RabbitMQConfig.java      # Exchange, queue, binding, JSON converter
│   └── SecurityConfig.java      # JWT filter chain, BCrypt, stateless sessions
│   └── KafkaConfig.java         
├── controller/
│   ├── AuthController.java           # /api/auth/register, /api/auth/login
│   └── ParcelController.java         # /api/parcels CRUD + status update
│   └── KafkaParcelController.java    # /api/parcels CRUD + status update
├── dto/                              # Request/response records (Java 17 records)
├── exception/
│   ├── ParcelNotFoundException.java
│   └── GlobalExceptionHandler.java  # Unified JSON error responses
├── messaging/
│   ├── KafkaStatusUpdatePublisher.java   # Publishes events to Kafka topics
│   ├── StatusUpdateEvent.java            # The message payload
│   └── StatusUpdatePublisher.java        # Publishes events to the Rabbit MQ exchange
├── model/
│   ├── Parcel.java              # @Document — parcels collection
│   ├── ParcelStatus.java        # Enum: CREATED, IN_TRANSIT, etc.
│   ├── StatusEvent.java         # @Document — status_events collection
│   └── User.java                # @Document — users collection
├── repository/                  # MongoRepository interfaces
├── security/
│   ├── JwtUtil.java             # Token generation and validation (jjwt 0.12)
│   ├── JwtAuthFilter.java       # OncePerRequestFilter — validates bearer token
│   └── UserDetailsServiceImpl.java
└── service/
    ├── AuthService.java               # Register + login logic
    ├── KafkaParcelService.java        # Kafka Publisher and Persists to MongoDB
    ├── KafkaStatusEventConsumer.java  # Kafka Listener - Persists to MongoDB        
    ├── ParcelService.java             # Core business logic, publishes RabbitMQ events
    └── StatusEventConsumer.java       # @RabbitListener - persists events to MongoDB

Design Decisions

Why both RabbitMQ and Kafka? The two brokers serve different architectural patterns. RabbitMQ is push-based with smart routing (exchanges, bindings, dead-letter queues) — ideal for task distribution and targeted delivery. Kafka is pull-based with durable, ordered, replayable logs — ideal for event sourcing, audit trails, and high-throughput pipelines. This project exposes both as parallel flows so the messaging characteristics of each can be observed directly.

Why a shared StatusUpdateEvent payload? Both brokers carry the same event structure — tracking number, previous status, new status, message, timestamp. This makes the two flows directly comparable and reflects real systems where the same domain event is published to multiple brokers for different consumer use cases.

Why KRaft mode for Kafka? KRaft removes the Zookeeper dependency, reducing the number of moving parts in the stack. It has been production-ready since Kafka 3.3 and is the default from Kafka 4.0 onwards.

Why MongoDB? Parcel documents have flexible schemas (future fields like dimensions, weight, carrier metadata) and the status history is a natural document collection. MongoDB's flexibility suits logistics data better than a rigid relational schema.

Why JWT over sessions? The service is stateless by design — critical for horizontal scaling behind a load balancer (e.g. Kubernetes). No shared session store is needed.


Environment Variables (Docker Compose overrides)

Variable Local default Docker value Description
SPRING_DATA_MONGODB_URI mongodb://localhost:27017/parcel_tracker mongodb://mongo:27017/parcel_tracker MongoDB connection
SPRING_RABBITMQ_HOST localhost rabbitmq RabbitMQ host
SPRING_KAFKA_BOOTSTRAP_SERVERS localhost:9092 kafka:9092 Kafka broker address
APP_JWT_SECRET (see application.yml) Base64-encoded 256-bit signing key

Author

Subramanian S — Java Backend Engineer
linkedin.com/in/subramanian-s-782a4a204

About

Logistics parcel tracking API — RabbitMQ + Kafka async messaging, JWT auth, MongoDB, Docker Compose

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors