This project is a complete, microservices-based application for managing patient data. It demonstrates a modern, distributed architecture using Java, Spring Boot, Docker, gRPC, and a Zookeeper-less Kafka (Kraft) setup.
The application is composed of several independent services that communicate via REST APIs, gRPC, and asynchronous events. An API Gateway acts as the single entry point for all client requests, providing routing and centralized security.
- A client authenticates against the Auth Service via the API Gateway to get a JWT.
- The client then makes requests to other services (e.g., Patient Service) by passing the JWT in the
Authorizationheader. - The API Gateway intercepts these requests, validates the JWT with the Auth Service, and then routes the request to the appropriate downstream service.
- When a new patient is created, the Patient Management Service persists the data, makes a synchronous gRPC call to the Billing Service, and publishes an asynchronous event to Kafka.
- The Analytics Service, which constantly listens to the Kafka topic, consumes this event for processing. It is not called directly by any other service.
| Service | Port(s) | Primary Responsibility |
|---|---|---|
| API Gateway | 4004 |
Routes all incoming traffic, enforces security with JWT validation. |
| Authentication Service | 4005 |
Handles user login, issues and validates JSON Web Tokens (JWT). |
| Patient Management Service | 4000 |
Provides CRUD operations for patient records. |
| Billing Service | 9001 |
Exposes a gRPC endpoint to create patient billing accounts. |
| Analytics Service | 4002 |
Consumes patient events from Kafka for analytical purposes. |
- Backend: Java 21, Spring Boot 3.x
- Gateway: Spring Cloud Gateway
- Security: Spring Security, JSON Web Tokens (JWT)
- Database: PostgreSQL, Spring Data JPA
- Messaging: Apache Kafka (in KRaft mode, without Zookeeper)
- RPC: gRPC with Protocol Buffers
- Containerization: Docker & Docker Compose
- Build Tool: Maven
The entire application stack is orchestrated to run on a unified Docker network.
-
Clone the Repository
git clone <your-repository-url> cd <your-repository-name>
-
Create an Environment File Create a file named
.envin the root directory. This file will provide the configuration for all services.# Example .env content # JWT SECRET (Must be Base64 encoded) JWT_SECRET=TXlTdXBlclNlY3JldEtleUZvckp3dEVuY29kaW5nX0NoYW5nZVRoaXMxMjMh # Auth Service DB AUTH_DB_USER=admin_user AUTH_DB_PASSWORD=password AUTH_DB_NAME=db # Patient Service DB PATIENT_DB_USER=admin_user PATIENT_DB_PASSWORD=password PATIENT_DB_NAME=db # Kafka Cluster ID (Generate a new one with `kafka-storage.sh random-uuid`) KAFKA_CLUSTER_ID=l_AbCDeFgHiJkLmNoPqRsA
-
Build and Run with Docker Compose From the root directory, run the following command. This will build the Docker images for each service and start all containers on a shared internal network.
docker-compose up --build
To stop the application, press
Ctrl+Cand then rundocker-compose down.
This file orchestrates the entire application. All services are connected to a common internal network, allowing them to communicate using their service names.
version: '3.8'
services:
# --- Infrastructure ---
auth-service-db:
image: postgres:14
container_name: auth-service-db
environment:
POSTGRES_USER: ${AUTH_DB_USER}
POSTGRES_PASSWORD: ${AUTH_DB_PASSWORD}
POSTGRES_DB: ${AUTH_DB_NAME}
ports: [ "5433:5432" ]
networks: [ internal ]
patient-service-db:
image: postgres:14
container_name: patient-service-db
environment:
POSTGRES_USER: ${PATIENT_DB_USER}
POSTGRES_PASSWORD: ${PATIENT_DB_PASSWORD}
POSTGRES_DB: ${PATIENT_DB_NAME}
ports: [ "5432:5432" ]
networks: [ internal ]
kafka:
image: confluentinc/cp-kafka:latest
container_name: kafka
ports: [ "29092:29092" ]
environment:
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: 'broker,controller'
KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka:9093'
KAFKA_LISTENERS: 'INTERNAL://:9092,EXTERNAL://:29092,CONTROLLER://:9093'
KAFKA_ADVERTISED_LISTENERS: 'INTERNAL://kafka:9092,EXTERNAL://localhost:29092'
KAFKA_INTER_BROKER_LISTENER_NAME: 'INTERNAL'
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT'
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
CLUSTER_ID: ${KAFKA_CLUSTER_ID}
networks: [ internal ]
# --- Application Services ---
auth-service:
build: ./auth-service
container_name: auth-service
depends_on: [ auth-service-db ]
ports: [ "4005:4005" ]
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://auth-service-db:5432/db
- SPRING_DATASOURCE_USERNAME=${AUTH_DB_USER}
- SPRING_DATASOURCE_PASSWORD=${AUTH_DB_PASSWORD}
- JWT_SECRET=${JWT_SECRET}
networks: [ internal ]
billing-service:
build: ./billing-service
container_name: billing-service
ports: [ "4001:4001", "9001:9001" ]
networks: [ internal ]
patient-service:
build: ./patient-service
container_name: patient-service
depends_on: [ patient-service-db, kafka, billing-service ]
ports: [ "4000:4000" ]
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://patient-service-db:5432/db
- SPRING_DATASOURCE_USERNAME=${PATIENT_DB_USER}
- SPRING_DATASOURCE_PASSWORD=${PATIENT_DB_PASSWORD}
- SPRING_KAFKA_BOOTSTRAP_SERVERS=kafka:9092
- BILLING_SERVICE_ADDRESS=billing-service
- BILLING_SERVICE_GRPC_PORT=9001
networks: [ internal ]
analytics-service:
build: ./analytics-service
container_name: analytics-service
depends_on: [ kafka ]
environment:
- SPRING_KAFKA_BOOTSTRAP_SERVERS=kafka:9092
networks: [ internal ]
api-gateway:
build: ./api-gateway
container_name: api-gateway
depends_on: [ auth-service, patient-service ]
ports: [ "4004:4004" ]
environment:
- AUTH_SERVICE_URL=http://auth-service:4005
networks: [ internal ]
networks:
internal:
driver: bridgeAll Java-based services in this project share a consistent, multi-stage Dockerfile pattern for optimized and lean container images.
- Builder Stage: Uses a full Maven JDK image to build the application from source and download dependencies.
- Runner Stage: Uses a slim OpenJDK image and copies only the final
.jarfile from the builder stage, resulting in a smaller and more secure production image.
Example Dockerfile Template:
# Stage 1: Build the application
FROM maven:3.9.9-eclipse-temurin-21 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package
# Stage 2: Create the final runtime image
FROM openjdk:21-jdk-slim AS runner
WORKDIR /app
# The JAR name is the only part that changes between services
COPY --from=builder /app/target/your-service-name-0.0.1-SNAPSHOT.jar ./app.jar
EXPOSE <port>
ENTRYPOINT ["java","-jar","app.jar"]Note: This workflow requires a user to exist in the auth-service-db. You can connect to the database on localhost:5433 and manually insert a user with a BCrypt-hashed password.
Send a POST request to the API Gateway's authentication endpoint.
curl -X POST http://localhost:4004/auth/login \
-H "Content-Type: application/json" \
-d '{ "email": "[email protected]", "password": "your-password" }'The response will contain your JWT. Copy this token.
Send a POST request to the patient endpoint, including the JWT in the Authorization header.
# Replace <YOUR_JWT_TOKEN> with the token from Step 1
export TOKEN=<YOUR_JWT_TOKEN>
curl -X POST http://localhost:4004/api/patients \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "Jane Doe",
"email": "[email protected]",
"address": "456 Oak Ave, Othertown, USA",
"dateOfBirth": "1992-05-20"
}'This single API call triggers the full communication flow between the services as described in the architecture section.
For more detailed information about a specific service, including its API endpoints, configuration, and internal logic, please refer to the README.md file within each service's directory:
