DEV Community

Khairul Basar
Khairul Basar

Posted on

Monolith vs Modular Monolith vs Multi-Module vs Microservices in Spring Boot

πŸ— Monolith vs Modular Monolith vs Multi-Module vs Microservices in Spring Boot
A Complete Beginner-Friendly Guide with Real Hospital System Example
πŸ“š Introduction
Building a backend system with Spring Boot is exciting, but every developer faces the same crucial question:

"Which architecture style should I choose for my project?"

The answer isn't always straightforward. Should you build everything in one place? Split into modules? Go fully distributed with microservices?

In this comprehensive guide, we'll explore four different architecture styles using a real-world example:

πŸ₯ Hospital OPD Appointment & Billing System
Our system will handle:

Identity Module - Login, registration, user management

Doctor Management - Doctor profiles, schedules, specializations

Appointment Booking - Slot booking, availability checking

Billing - Invoice generation, payment processing

Notification - SMS/Email alerts for appointments

We'll implement this same system in four different ways and understand:

How each architecture works

How modules communicate

Step-by-step implementation details

Pros and cons of each approach

When to use which architecture

🎯 Understanding the Problem Domain
Before diving into architectures, let's understand our hospital system requirements:

java
// Core business requirements:

  • Patients can register and login
  • Patients can view available doctors
  • Patients can book appointments
  • System sends confirmation notifications
  • Billing generates invoice after appointment
  • Doctors can manage their schedules Each module has specific responsibilities, and they need to work together seamlessly.

1️⃣ MONOLITH ARCHITECTURE
The Traditional Approach
πŸ” What is a Monolith?
A monolithic architecture means everything lives inside ONE Spring Boot application. Think of it as a single, unified codebase where all modules - Identity, Doctor, Appointment, Billing, and Notification - coexist in the same project.

🏒 Real-Life Analogy
Imagine a single hospital building where:

Reception desk (Identity)

Doctor's chambers (Doctor Management)

Appointment desk (Booking)

Billing counter (Billing)

Communication center (Notification)

All departments are in the same building. Staff can walk between departments easily. That's your monolith!

πŸ“¦ Detailed Project Structure
text
opd-system/
β”œβ”€β”€ src/
β”‚ β”œβ”€β”€ main/
β”‚ β”‚ β”œβ”€β”€ java/
β”‚ β”‚ β”‚ └── com/
β”‚ β”‚ β”‚ └── hospital/
β”‚ β”‚ β”‚ └── opd/
β”‚ β”‚ β”‚ β”œβ”€β”€ OpdApplication.java
β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”œβ”€β”€ controller/
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ AuthController.java
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ DoctorController.java
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ AppointmentController.java
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ BillingController.java
β”‚ β”‚ β”‚ β”‚ └── NotificationController.java
β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”œβ”€β”€ service/
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ UserService.java
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ DoctorService.java
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ AppointmentService.java
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ BillingService.java
β”‚ β”‚ β”‚ β”‚ └── NotificationService.java
β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”œβ”€β”€ repository/
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ UserRepository.java
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ DoctorRepository.java
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ AppointmentRepository.java
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ BillingRepository.java
β”‚ β”‚ β”‚ β”‚ └── NotificationRepository.java
β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”œβ”€β”€ entity/
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ User.java
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Doctor.java
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Appointment.java
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Bill.java
β”‚ β”‚ β”‚ β”‚ └── Notification.java
β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ └── config/
β”‚ β”‚ β”‚ β”œβ”€β”€ SecurityConfig.java
β”‚ β”‚ β”‚ └── SwaggerConfig.java
β”‚ β”‚ β”‚
β”‚ β”‚ └── resources/
β”‚ β”‚ β”œβ”€β”€ application.properties
β”‚ β”‚ └── db/
β”‚ β”‚ └── migration/
β”‚ β”‚
β”‚ └── test/
β”‚ └── java/
β”‚ └── com/
β”‚ └── hospital/
β”‚ └── opd/
β”‚ β”œβ”€β”€ service/
β”‚ └── controller/
πŸ”„ How Modules Communicate in Monolith
In a monolith, communication is direct and simple - just Java method calls!

Example: Booking an Appointment

java
@Service
public class AppointmentService {

@Autowired
private DoctorService doctorService;

@Autowired
private UserService userService;

@Autowired
private NotificationService notificationService;

@Autowired
private BillingService billingService;

@Transactional
public Appointment bookAppointment(AppointmentRequest request) {
    // 1. Verify doctor availability (direct method call)
    Doctor doctor = doctorService.findAvailableDoctor(
        request.getDoctorId(), 
        request.getDateTime()
    );

    // 2. Get patient details (direct method call)
    User patient = userService.findById(request.getPatientId());

    // 3. Create appointment
    Appointment appointment = new Appointment();
    appointment.setDoctor(doctor);
    appointment.setPatient(patient);
    appointment.setDateTime(request.getDateTime());
    appointment.setStatus(Status.CONFIRMED);

    appointment = appointmentRepository.save(appointment);

    // 4. Generate bill (direct method call)
    Bill bill = billingService.createBill(appointment);

    // 5. Send notification (direct method call)
    notificationService.sendAppointmentConfirmation(
        patient.getEmail(), 
        appointment
    );

    return appointment;
}
Enter fullscreen mode Exit fullscreen mode

}
Notice: No HTTP calls, no message queues, no network communication. Just plain Java method calls within the same JVM.

🧩 Architecture Diagram
text
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ OPD MONOLITH APP β”‚
β”‚ (Single JVM) β”‚
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ CONTROLLER LAYER β”‚ β”‚
β”‚ β”‚ Auth Doctor Appt Bill Notifβ”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ SERVICE LAYER β”‚ β”‚
β”‚ β”‚ Auth Doctor Appt Bill Notifβ”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ REPOSITORY LAYER β”‚ β”‚
β”‚ β”‚ Auth Doctor Appt Bill Notifβ”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ ENTITY LAYER β”‚ β”‚
β”‚ β”‚ User Doctor Appt Bill Notifβ”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ PostgreSQL DB β”‚
β”‚ (Single Schema) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
πŸ’» Code Walkthrough: Monolith Implementation
Application Properties:

properties

application.properties

spring.datasource.url=jdbc:postgresql://localhost:5432/opd_db
spring.datasource.username=admin
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
Entity Example:

java
@entity
@Table(name = "appointments")
public class Appointment {
@id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne
@JoinColumn(name = "doctor_id")
private Doctor doctor;

@ManyToOne
@JoinColumn(name = "patient_id")
private User patient;

private LocalDateTime appointmentDateTime;
private String status;

@OneToOne(mappedBy = "appointment")
private Bill bill;

// getters, setters, constructors
Enter fullscreen mode Exit fullscreen mode

}
βœ… Pros of Monolith
Simple to Develop

One codebase to manage

Easy to understand for beginners

Quick setup and configuration

Easy Debugging

Single log file

No network issues to debug

Clear stack traces

Fast Communication

Method calls are instant

No network latency

No serialization overhead

Simple Deployment

One JAR/WAR file

One server to configure

Easy to deploy anywhere

Perfect for MVP

Build fast

Validate ideas quickly

Iterate rapidly

❌ Cons of Monolith
Scaling Limitations

java
// Can't scale just the appointment module
// Must scale entire application
Code Organization Challenges

java
// As code grows, dependencies become messy
// Service classes might accidentally use each other's internals
Team Development Bottlenecks

Multiple teams working on same codebase

Merge conflicts common

Feature coordination overhead

Technology Lock-in

One technology stack for everything

Can't use different databases for different modules

One Failure Affects All

If billing has memory leak, appointment booking fails too

No fault isolation

🎯 When to Use Monolith
βœ… Perfect for:

Startups building MVP

Small teams (2-5 developers)

Simple applications with low complexity

Learning projects

Internal tools

Projects with tight deadlines

❌ Avoid when:

Multiple teams are working independently

You need to scale specific features

System has high complexity

You need technology diversity

2️⃣ MODULAR MONOLITH (Spring Modulith)
Structured Single Application
πŸ” What is a Modular Monolith?
A Modular Monolith (or Modulith) is still ONE application, but internally divided into strict modules with clear boundaries. Each module has its own public API and hides its internal implementation.

🏒 Real-Life Analogy
Think of a modern hospital with secured departments:

Each department (Identity, Doctor, Appointment) has its own entrance

Departments communicate through formal channels

Staff can't just walk into any department without proper authorization

Each department has its own internal operations that others can't see

πŸ“¦ Detailed Project Structure with Spring Modulith
text
opd-modulith/
β”œβ”€β”€ src/
β”‚ └── main/
β”‚ └── java/
β”‚ └── com/
β”‚ └── hospital/
β”‚ └── opd/
β”‚ β”œβ”€β”€ OpdApplication.java
β”‚ β”‚
β”‚ β”œβ”€β”€ identity/
β”‚ β”‚ β”œβ”€β”€ api/
β”‚ β”‚ β”‚ β”œβ”€β”€ IdentityService.java (public interface)
β”‚ β”‚ β”‚ └── dto/
β”‚ β”‚ β”‚ β”œβ”€β”€ UserDTO.java
β”‚ β”‚ β”‚ └── LoginRequest.java
β”‚ β”‚ β”œβ”€β”€ internal/
β”‚ β”‚ β”‚ β”œβ”€β”€ model/
β”‚ β”‚ β”‚ β”‚ └── User.java (internal entity)
β”‚ β”‚ β”‚ β”œβ”€β”€ repository/
β”‚ β”‚ β”‚ β”‚ └── UserRepository.java
β”‚ β”‚ β”‚ └── service/
β”‚ β”‚ β”‚ └── IdentityServiceImpl.java
β”‚ β”‚ └── events/
β”‚ β”‚ └── UserRegisteredEvent.java
β”‚ β”‚
β”‚ β”œβ”€β”€ doctor/
β”‚ β”‚ β”œβ”€β”€ api/
β”‚ β”‚ β”‚ β”œβ”€β”€ DoctorService.java
β”‚ β”‚ β”‚ └── dto/
β”‚ β”‚ β”‚ β”œβ”€β”€ DoctorDTO.java
β”‚ β”‚ β”‚ └── AvailabilityDTO.java
β”‚ β”‚ β”œβ”€β”€ internal/
β”‚ β”‚ β”‚ β”œβ”€β”€ model/
β”‚ β”‚ β”‚ β”‚ └── Doctor.java
β”‚ β”‚ β”‚ β”œβ”€β”€ repository/
β”‚ β”‚ β”‚ β”‚ └── DoctorRepository.java
β”‚ β”‚ β”‚ └── service/
β”‚ β”‚ β”‚ └── DoctorServiceImpl.java
β”‚ β”‚ └── events/
β”‚ β”‚ └── DoctorAvailabilityChangedEvent.java
β”‚ β”‚
β”‚ β”œβ”€β”€ appointment/
β”‚ β”‚ β”œβ”€β”€ api/
β”‚ β”‚ β”‚ β”œβ”€β”€ AppointmentService.java
β”‚ β”‚ β”‚ └── dto/
β”‚ β”‚ β”‚ β”œβ”€β”€ AppointmentRequest.java
β”‚ β”‚ β”‚ └── AppointmentDTO.java
β”‚ β”‚ β”œβ”€β”€ internal/
β”‚ β”‚ β”‚ β”œβ”€β”€ model/
β”‚ β”‚ β”‚ β”‚ └── Appointment.java
β”‚ β”‚ β”‚ β”œβ”€β”€ repository/
β”‚ β”‚ β”‚ β”‚ └── AppointmentRepository.java
β”‚ β”‚ β”‚ └── service/
β”‚ β”‚ β”‚ └── AppointmentServiceImpl.java
β”‚ β”‚ └── events/
β”‚ β”‚ └── AppointmentBookedEvent.java
β”‚ β”‚
β”‚ β”œβ”€β”€ billing/
β”‚ β”‚ β”œβ”€β”€ api/
β”‚ β”‚ β”‚ β”œβ”€β”€ BillingService.java
β”‚ β”‚ β”‚ └── dto/
β”‚ β”‚ β”‚ └── InvoiceDTO.java
β”‚ β”‚ β”œβ”€β”€ internal/
β”‚ β”‚ β”‚ β”œβ”€β”€ model/
β”‚ β”‚ β”‚ β”‚ └── Bill.java
β”‚ β”‚ β”‚ β”œβ”€β”€ repository/
β”‚ β”‚ β”‚ β”‚ └── BillingRepository.java
β”‚ β”‚ β”‚ └── service/
β”‚ β”‚ β”‚ └── BillingServiceImpl.java
β”‚ β”‚ └── events/
β”‚ β”‚ └── BillGeneratedEvent.java
β”‚ β”‚
β”‚ └── notification/
β”‚ β”œβ”€β”€ api/
β”‚ β”‚ β”œβ”€β”€ NotificationService.java
β”‚ β”‚ └── dto/
β”‚ β”‚ └── NotificationRequest.java
β”‚ β”œβ”€β”€ internal/
β”‚ β”‚ β”œβ”€β”€ model/
β”‚ β”‚ β”‚ └── Notification.java
β”‚ β”‚ β”œβ”€β”€ repository/
β”‚ β”‚ β”‚ └── NotificationRepository.java
β”‚ β”‚ └── service/
β”‚ β”‚ └── NotificationServiceImpl.java
β”‚ └── events/
β”‚ └── NotificationSentEvent.java
πŸ”„ Communication in Modular Monolith
Spring Modulith supports two communication patterns:

Pattern 1: Controlled Direct Calls (via Public APIs)
java
// Identity Module - Public API
package com.hospital.opd.identity.api;

public interface IdentityService {
UserDTO findUserById(Long id);
UserDTO registerUser(RegisterRequest request);
boolean validateUserCredentials(String email, String password);
}

// Identity Module - Internal Implementation
package com.hospital.opd.identity.internal.service;

@Service
@Modulithic(module = "identity")
class IdentityServiceImpl implements IdentityService {
@Autowired
private UserRepository userRepository;

@Override
public UserDTO findUserById(Long id) {
    // Internal implementation hidden from other modules
    User user = userRepository.findById(id)
        .orElseThrow(() -> new UserNotFoundException(id));
    return mapToDTO(user);
}

private UserDTO mapToDTO(User user) {
    // Internal mapping logic
    return new UserDTO(user.getId(), user.getName(), user.getEmail());
}
Enter fullscreen mode Exit fullscreen mode

}

// Appointment Module using Identity Module
package com.hospital.opd.appointment.api;

@Service
@Modulithic(module = "appointment")
class AppointmentServiceImpl implements AppointmentService {

// Using public API, not internal implementation
@Autowired
private IdentityService identityService;

@Override
public AppointmentDTO bookAppointment(AppointmentRequest request) {
    // Can only access public methods
    UserDTO patient = identityService.findUserById(request.getPatientId());

    // Cannot access internal Identity classes!
    // identityService.userRepository.findById() ❌ Not accessible

    // Rest of the logic
    Appointment appointment = createAppointment(patient, request);
    return mapToDTO(appointment);
}
Enter fullscreen mode Exit fullscreen mode

}
Pattern 2: Event-Based Communication (Recommended)
java
// 1. Define Event in Appointment Module
package com.hospital.opd.appointment.events;

public class AppointmentBookedEvent {
private final Long appointmentId;
private final Long patientId;
private final Long doctorId;
private final LocalDateTime appointmentTime;
private final BigDecimal consultationFee;

// constructor, getters
Enter fullscreen mode Exit fullscreen mode

}

// 2. Publish Event in Appointment Module
package com.hospital.opd.appointment.internal.service;

@Service
@Modulithic(module = "appointment")
class AppointmentServiceImpl implements AppointmentService {

@Autowired
private ApplicationEventPublisher eventPublisher;

@Override
@Transactional
public AppointmentDTO bookAppointment(AppointmentRequest request) {
    // 1. Create appointment
    Appointment appointment = createAppointment(request);

    // 2. Save to database
    appointment = appointmentRepository.save(appointment);

    // 3. Publish event - other modules will react
    eventPublisher.publishEvent(new AppointmentBookedEvent(
        appointment.getId(),
        appointment.getPatientId(),
        appointment.getDoctorId(),
        appointment.getDateTime(),
        appointment.getConsultationFee()
    ));

    return mapToDTO(appointment);
}
Enter fullscreen mode Exit fullscreen mode

}

// 3. Billing Module Listens to Event
package com.hospital.opd.billing.internal.service;

@Service
@Modulithic(module = "billing")
class BillingEventListener {

@Autowired
private BillingService billingService;

@EventListener
@Transactional
public void handleAppointmentBooked(AppointmentBookedEvent event) {
    // Billing module reacts independently
    InvoiceDTO invoice = billingService.createInvoice(
        event.getAppointmentId(),
        event.getPatientId(),
        event.getConsultationFee()
    );

    // Can publish its own events
    eventPublisher.publishEvent(new BillGeneratedEvent(invoice));
}
Enter fullscreen mode Exit fullscreen mode

}

// 4. Notification Module Listens to Multiple Events
package com.hospital.opd.notification.internal.service;

@Service
@Modulithic(module = "notification")
class NotificationEventListener {

@Autowired
private NotificationService notificationService;

@EventListener
public void handleAppointmentBooked(AppointmentBookedEvent event) {
    notificationService.sendAppointmentConfirmation(
        event.getPatientId(),
        event.getAppointmentId()
    );
}

@EventListener
public void handleBillGenerated(BillGeneratedEvent event) {
    notificationService.sendInvoiceNotification(
        event.getPatientId(),
        event.getInvoiceNumber()
    );
}
Enter fullscreen mode Exit fullscreen mode

}
🧩 Spring Modulith Architecture Diagram
text
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ MODULAR MONOLITH β”‚
β”‚ (Single Application) β”‚
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ API GATEWAY LAYER β”‚ β”‚
β”‚ β”‚ (All REST Controllers) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ MODULE BOUNDARIES β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ IDENTITY β”‚ β”‚ DOCTOR β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ MODULE β”‚ β”‚ MODULE β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ PUBLIC β”‚ β”‚ β”‚ β”‚ PUBLIC β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ API β”‚ β”‚ β”‚ β”‚ API β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β” β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚INTERNAL β”‚ β”‚ β”‚ β”‚INTERNAL β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ LOGIC β”‚ β”‚ β”‚ β”‚ LOGIC β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ APPOINTMENT β”‚ β”‚ BILLING β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ MODULE β”‚ β”‚ MODULE β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ PUBLIC β”‚ β”‚ β”‚ β”‚ PUBLIC β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ API β”‚ β”‚ β”‚ β”‚ API β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β” β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚INTERNAL β”‚ β”‚ β”‚ β”‚INTERNAL β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ LOGIC β”‚ β”‚ β”‚ β”‚ LOGIC β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ NOTIFICATION β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ MODULE β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ PUBLIC β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ API β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ INTERNAL β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ LOGIC β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ EVENT BUS β”‚ β”‚
β”‚ β”‚ (In-Memory) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ PostgreSQL DB β”‚
β”‚ (Single Schema) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
πŸ› οΈ Spring Modulith Configuration
Add Modulith Dependency:

xml

org.springframework.modulith
spring-modulith-starter-core
1.1.0

Module Validation:

java
@SpringBootApplication
public class OpdApplication {
public static void main(String[] args) {
SpringApplication.run(OpdApplication.class, args);
}

// Verify module boundaries at startup
@Bean
ApplicationModuleInitializer moduleInitializer() {
    return () -> {
        var modules = ApplicationModules.of(OpdApplication.class);
        modules.verify();
        System.out.println("βœ… Module boundaries verified!");
    };
}
Enter fullscreen mode Exit fullscreen mode

}
Module Documentation:

java
@org.springframework.modulith.ApplicationModule(
allowedDependencies = {"identity", "doctor"}
)
package com.hospital.opd.appointment;

// This module can only depend on identity and doctor modules
βœ… Pros of Modular Monolith
Clean Architecture

Clear separation of concerns

Enforced module boundaries

No accidental dependencies

Better Code Organization

java
// Each module owns its domain
// Internal implementation can change without affecting others
Easier Testing

java
@ModuleTest
class AppointmentModuleTest {
// Test module in isolation
// Mock external module interactions
}
Future-Proof

Easy to extract modules into microservices later

Already has clear boundaries

No Network Overhead

Still fast in-JVM communication

Events are lightweight

❌ Cons of Modular Monolith
Single Deployment Unit

Still deploy everything together

Can't scale modules independently

Single Database

All modules share one database

Database becomes bottleneck

Learning Curve

Developers must understand module boundaries

Need discipline to maintain separation

🎯 When to Use Modular Monolith
βœ… Perfect for:

Growing products with increasing complexity

Medium-sized teams (5-15 developers)

Systems preparing for microservices migration

Projects needing clear architecture

Most real-world business applications

❌ Avoid when:

You need independent scaling

Each module requires different databases

Teams are fully independent geographically

πŸ‘‰ This is the sweet spot for most applications!

3️⃣ MULTI-MODULE ARCHITECTURE
Build-Time Modularization
πŸ” What is Multi-Module Architecture?
Multi-module architecture uses Maven or Gradle to split code into separate modules at build time. Each module is a separate Maven/Gradle subproject, but they all combine into ONE final application.

🏒 Real-Life Analogy
Think of a hospital built from prefabricated sections:

Each department (Identity, Doctor, Appointment) is built separately

They're designed to fit together perfectly

Once assembled, they form one complete hospital

You can't move a department after construction

πŸ“¦ Detailed Multi-Module Project Structure
text
opd-multi-module/
β”œβ”€β”€ pom.xml (parent pom)
β”œβ”€β”€ identity-module/
β”‚ β”œβ”€β”€ pom.xml
β”‚ └── src/
β”‚ └── main/
β”‚ └── java/
β”‚ └── com/
β”‚ └── hospital/
β”‚ └── identity/
β”‚ β”œβ”€β”€ IdentityApplication.java
β”‚ β”œβ”€β”€ controller/
β”‚ β”œβ”€β”€ service/
β”‚ β”œβ”€β”€ repository/
β”‚ β”œβ”€β”€ model/
β”‚ └── config/
β”œβ”€β”€ doctor-module/
β”‚ β”œβ”€β”€ pom.xml
β”‚ └── src/
β”‚ └── main/
β”‚ └── java/
β”‚ └── com/
β”‚ └── hospital/
β”‚ └── doctor/
β”‚ β”œβ”€β”€ DoctorApplication.java
β”‚ β”œβ”€β”€ controller/
β”‚ β”œβ”€β”€ service/
β”‚ β”œβ”€β”€ repository/
β”‚ β”œβ”€β”€ model/
β”‚ └── config/
β”œβ”€β”€ appointment-module/
β”‚ β”œβ”€β”€ pom.xml
β”‚ └── src/
β”‚ └── main/
β”‚ └── java/
β”‚ └── com/
β”‚ └── hospital/
β”‚ └── appointment/
β”‚ β”œβ”€β”€ AppointmentApplication.java
β”‚ β”œβ”€β”€ controller/
β”‚ β”œβ”€β”€ service/
β”‚ β”œβ”€β”€ repository/
β”‚ β”œβ”€β”€ model/
β”‚ └── config/
β”œβ”€β”€ billing-module/
β”‚ β”œβ”€β”€ pom.xml
β”‚ └── src/
β”‚ └── main/
β”‚ └── java/
β”‚ └── com/
β”‚ └── hospital/
β”‚ └── billing/
β”‚ β”œβ”€β”€ BillingApplication.java
β”‚ β”œβ”€β”€ controller/
β”‚ β”œβ”€β”€ service/
β”‚ β”œβ”€β”€ repository/
β”‚ β”œβ”€β”€ model/
β”‚ └── config/
β”œβ”€β”€ notification-module/
β”‚ β”œβ”€β”€ pom.xml
β”‚ └── src/
β”‚ └── main/
β”‚ └── java/
β”‚ └── com/
β”‚ └── hospital/
β”‚ └── notification/
β”‚ β”œβ”€β”€ NotificationApplication.java
β”‚ β”œβ”€β”€ controller/
β”‚ β”œβ”€β”€ service/
β”‚ β”œβ”€β”€ repository/
β”‚ β”œβ”€β”€ model/
β”‚ └── config/
└── application/
β”œβ”€β”€ pom.xml
└── src/
└── main/
└── java/
└── com/
└── hospital/
└── app/
β”œβ”€β”€ HospitalApplication.java
└── config/
└── ModuleConfiguration.java
πŸ“„ Maven Configuration Files
Parent pom.xml:

xml
<?xml version="1.0" encoding="UTF-8"?>

4.0.0

<groupId>com.hospital</groupId>
<artifactId>opd-multi-module</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>

<modules>
    <module>identity-module</module>
    <module>doctor-module</module>
    <module>appointment-module</module>
    <module>billing-module</module>
    <module>notification-module</module>
    <module>application</module>
</modules>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.1.5</version>
</parent>

<properties>
    <java.version>17</java.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.hospital</groupId>
            <artifactId>identity-module</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>com.hospital</groupId>
            <artifactId>doctor-module</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!-- Other module dependencies -->
    </dependencies>
</dependencyManagement>
Enter fullscreen mode Exit fullscreen mode


Module pom.xml (appointment-module):

xml
<?xml version="1.0" encoding="UTF-8"?>

4.0.0

<parent>
    <groupId>com.hospital</groupId>
    <artifactId>opd-multi-module</artifactId>
    <version>1.0.0</version>
</parent>

<artifactId>appointment-module</artifactId>
<packaging>jar</packaging>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- Dependencies on other modules -->
    <dependency>
        <groupId>com.hospital</groupId>
        <artifactId>doctor-module</artifactId>
    </dependency>
    <dependency>
        <groupId>com.hospital</groupId>
        <artifactId>identity-module</artifactId>
    </dependency>
</dependencies>
Enter fullscreen mode Exit fullscreen mode


Application Module pom.xml:

xml
<?xml version="1.0" encoding="UTF-8"?>

4.0.0

<parent>
    <groupId>com.hospital</groupId>
    <artifactId>opd-multi-module</artifactId>
    <version>1.0.0</version>
</parent>

<artifactId>application</artifactId>
<packaging>jar</packaging>

<dependencies>
    <!-- Include all modules -->
    <dependency>
        <groupId>com.hospital</groupId>
        <artifactId>identity-module</artifactId>
    </dependency>
    <dependency>
        <groupId>com.hospital</groupId>
        <artifactId>doctor-module</artifactId>
    </dependency>
    <dependency>
        <groupId>com.hospital</groupId>
        <artifactId>appointment-module</artifactId>
    </dependency>
    <dependency>
        <groupId>com.hospital</groupId>
        <artifactId>billing-module</artifactId>
    </dependency>
    <dependency>
        <groupId>com.hospital</groupId>
        <artifactId>notification-module</artifactId>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <mainClass>com.hospital.app.HospitalApplication</mainClass>
            </configuration>
        </plugin>
    </plugins>
</build>
Enter fullscreen mode Exit fullscreen mode


πŸ”„ How Modules Communicate in Multi-Module
Communication is through compile-time dependencies and direct method calls.

java
// Appointment Module Service
package com.hospital.appointment.service;

@Service
public class AppointmentService {

// Direct dependency on other modules
private final DoctorService doctorService;
private final IdentityService identityService;
private final BillingService billingService;

public AppointmentService(
        DoctorService doctorService,
        IdentityService identityService,
        BillingService billingService) {
    this.doctorService = doctorService;
    this.identityService = identityService;
    this.billingService = billingService;
}

@Transactional
public AppointmentDTO bookAppointment(AppointmentRequest request) {
    // Direct calls to other modules' services
    DoctorDTO doctor = doctorService.findAvailableDoctor(
        request.getDoctorId(), 
        request.getDateTime()
    );

    UserDTO patient = identityService.findById(request.getPatientId());

    Appointment appointment = new Appointment();
    appointment.setDoctorId(doctor.getId());
    appointment.setPatientId(patient.getId());
    appointment.setDateTime(request.getDateTime());
    appointment.setStatus("BOOKED");

    appointment = appointmentRepository.save(appointment);

    // Call billing module directly
    InvoiceDTO invoice = billingService.createInvoice(
        appointment.getId(),
        patient.getId(),
        doctor.getConsultationFee()
    );

    return mapToDTO(appointment);
}
Enter fullscreen mode Exit fullscreen mode

}
🧩 Multi-Module Architecture Diagram
text
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ MULTI-MODULE PROJECT β”‚
β”‚ (Maven/Gradle Parent) β”‚
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ COMPILE TIME β”‚ β”‚
β”‚ β”‚ DEPENDENCY TREE β”‚ β”‚
β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ identity-module.jar β”‚ β”‚
β”‚ β”‚ β–² β”‚ β”‚
β”‚ β”‚ β”‚ depends β”‚ β”‚
β”‚ β”‚ doctor-module.jar β”‚ β”‚
β”‚ β”‚ β–² β”‚ β”‚
β”‚ β”‚ β”‚ depends β”‚ β”‚
β”‚ β”‚ appointment-module.jar ◄──┐ β”‚ β”‚
β”‚ β”‚ β–² β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ depends β”‚ β”‚ β”‚
β”‚ β”‚ billing-module.jar β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β”‚ β–² β”‚ β”‚
β”‚ β”‚ β”‚ depends β”‚ β”‚
β”‚ β”‚ notification-module.jar β”‚ β”‚
β”‚ β”‚ β–² β”‚ β”‚
β”‚ β”‚ β”‚ depends β”‚ β”‚
β”‚ β”‚ application.jar β”‚ β”‚
β”‚ β”‚ (Final Assembly) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ RUNTIME (Single JVM) β”‚ β”‚
β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ ONE SPRING BOOT APP β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ with all modules β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ loaded together β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ PostgreSQL DB β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ (Single Schema) β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
πŸ’‘ Key Differences from Modular Monolith
java
// In Multi-Module: Compile-time dependencies
// appointment-module/pom.xml explicitly declares:

com.hospital
doctor-module

// In Modular Monolith: Runtime module boundaries
// No explicit dependency declaration needed in pom.xml
// Boundaries are enforced by package structure and Spring Modulith
βœ… Pros of Multi-Module
Clear Build Dependencies

xml


com.hospital
billing-module

Parallel Development

Teams can work on different modules

Clear ownership

Independent versioning

Reusable Modules

xml


com.hospital
notification-module
1.0.0

Faster Builds

Maven/Gradle incremental builds

Only rebuild changed modules

Better IDE Support

IntelliJ/Eclipse understand module structure

Easy navigation between modules

❌ Cons of Multi-Module
Tight Coupling at Runtime

java
// Even though modules are separate at build time,
// at runtime they're all in one JVM with no boundaries
No Runtime Isolation

One module's memory leak affects all

Can't restart just one module

Complex Refactoring

Moving code between modules requires dependency changes

Circular dependencies possible

🎯 When to Use Multi-Module
βœ… Perfect for:

Large codebases needing organization

Multiple teams sharing code

Projects with reusable components

Enterprise internal systems

Libraries and frameworks

❌ Avoid when:

You need runtime isolation

Modules need independent scaling

You want strict runtime boundaries

4️⃣ MICROSERVICES ARCHITECTURE
Fully Distributed System
πŸ” What are Microservices?
Microservices architecture splits the application into completely independent services. Each service:

Is its own Spring Boot application

Has its own database

Can be deployed independently

Communicates over network

🏒 Real-Life Analogy
Think of a hospital campus with separate buildings:

Identity building (separate structure)

Doctor's office building

Appointment center

Billing department building

Notification center

Each building has:

Its own staff

Its own resources

Its own phone system

Communication happens via phones/couriers (network)

πŸ“¦ Microservices Project Structure
text
hospital-system/
β”œβ”€β”€ identity-service/
β”‚ β”œβ”€β”€ pom.xml
β”‚ β”œβ”€β”€ src/
β”‚ β”‚ └── main/
β”‚ β”‚ β”œβ”€β”€ java/com/hospital/identity/
β”‚ β”‚ β”‚ β”œβ”€β”€ IdentityServiceApplication.java
β”‚ β”‚ β”‚ β”œβ”€β”€ controller/
β”‚ β”‚ β”‚ β”œβ”€β”€ service/
β”‚ β”‚ β”‚ β”œβ”€β”€ repository/
β”‚ β”‚ β”‚ β”œβ”€β”€ model/
β”‚ β”‚ β”‚ └── config/
β”‚ β”‚ └── resources/
β”‚ β”‚ β”œβ”€β”€ application.properties
β”‚ β”‚ └── db/migration/
β”‚ └── Dockerfile
β”‚
β”œβ”€β”€ doctor-service/
β”‚ β”œβ”€β”€ pom.xml
β”‚ β”œβ”€β”€ src/
β”‚ β”‚ └── main/
β”‚ β”‚ β”œβ”€β”€ java/com/hospital/doctor/
β”‚ β”‚ β”‚ β”œβ”€β”€ DoctorServiceApplication.java
β”‚ β”‚ β”‚ β”œβ”€β”€ controller/
β”‚ β”‚ β”‚ β”œβ”€β”€ service/
β”‚ β”‚ β”‚ β”œβ”€β”€ repository/
β”‚ β”‚ β”‚ β”œβ”€β”€ model/
β”‚ β”‚ β”‚ └── client/
β”‚ β”‚ β”‚ β”œβ”€β”€ IdentityServiceClient.java
β”‚ β”‚ β”‚ └── AppointmentServiceClient.java
β”‚ β”‚ └── resources/
β”‚ β”‚ β”œβ”€β”€ application.properties
β”‚ β”‚ └── db/migration/
β”‚ └── Dockerfile
β”‚
β”œβ”€β”€ appointment-service/
β”‚ β”œβ”€β”€ pom.xml
β”‚ β”œβ”€β”€ src/
β”‚ β”‚ └── main/
β”‚ β”‚ β”œβ”€β”€ java/com/hospital/appointment/
β”‚ β”‚ β”‚ β”œβ”€β”€ AppointmentServiceApplication.java
β”‚ β”‚ β”‚ β”œβ”€β”€ controller/
β”‚ β”‚ β”‚ β”œβ”€β”€ service/
β”‚ β”‚ β”‚ β”œβ”€β”€ repository/
β”‚ β”‚ β”‚ β”œβ”€β”€ model/
β”‚ β”‚ β”‚ └── client/
β”‚ β”‚ β”‚ β”œβ”€β”€ DoctorServiceClient.java
β”‚ β”‚ β”‚ └── IdentityServiceClient.java
β”‚ β”‚ └── resources/
β”‚ β”‚ β”œβ”€β”€ application.properties
β”‚ β”‚ └── db/migration/
β”‚ └── Dockerfile
β”‚
β”œβ”€β”€ billing-service/
β”‚ β”œβ”€β”€ pom.xml
β”‚ β”œβ”€β”€ src/
β”‚ β”‚ └── main/
β”‚ β”‚ β”œβ”€β”€ java/com/hospital/billing/
β”‚ β”‚ β”‚ β”œβ”€β”€ BillingServiceApplication.java
β”‚ β”‚ β”‚ β”œβ”€β”€ controller/
β”‚ β”‚ β”‚ β”œβ”€β”€ service/
β”‚ β”‚ β”‚ β”œβ”€β”€ repository/
β”‚ β”‚ β”‚ β”œβ”€β”€ model/
β”‚ β”‚ β”‚ └── consumer/
β”‚ β”‚ β”‚ └── AppointmentEventConsumer.java
β”‚ β”‚ └── resources/
β”‚ β”‚ β”œβ”€β”€ application.properties
β”‚ β”‚ └── db/migration/
β”‚ └── Dockerfile
β”‚
β”œβ”€β”€ notification-service/
β”‚ β”œβ”€β”€ pom.xml
β”‚ β”œβ”€β”€ src/
β”‚ β”‚ └── main/
β”‚ β”‚ β”œβ”€β”€ java/com/hospital/notification/
β”‚ β”‚ β”‚ β”œβ”€β”€ NotificationServiceApplication.java
β”‚ β”‚ β”‚ β”œβ”€β”€ controller/
β”‚ β”‚ β”‚ β”œβ”€β”€ service/
β”‚ β”‚ β”‚ β”œβ”€β”€ repository/
β”‚ β”‚ β”‚ β”œβ”€β”€ model/
β”‚ β”‚ β”‚ └── consumer/
β”‚ β”‚ β”‚ β”œβ”€β”€ AppointmentEventConsumer.java
β”‚ β”‚ β”‚ └── BillingEventConsumer.java
β”‚ β”‚ └── resources/
β”‚ β”‚ β”œβ”€β”€ application.properties
β”‚ β”‚ └── db/migration/
β”‚ └── Dockerfile
β”‚
β”œβ”€β”€ api-gateway/
β”‚ β”œβ”€β”€ pom.xml
β”‚ β”œβ”€β”€ src/
β”‚ β”‚ └── main/
β”‚ β”‚ β”œβ”€β”€ java/com/hospital/gateway/
β”‚ β”‚ β”‚ β”œβ”€β”€ GatewayApplication.java
β”‚ β”‚ β”‚ └── config/
β”‚ β”‚ β”‚ β”œβ”€β”€ RouteConfig.java
β”‚ β”‚ β”‚ └── SecurityConfig.java
β”‚ β”‚ └── resources/
β”‚ β”‚ └── application.properties
β”‚ └── Dockerfile
β”‚
β”œβ”€β”€ service-registry/
β”‚ β”œβ”€β”€ pom.xml
β”‚ β”œβ”€β”€ src/
β”‚ β”‚ └── main/
β”‚ β”‚ β”œβ”€β”€ java/com/hospital/registry/
β”‚ β”‚ β”‚ β”œβ”€β”€ RegistryApplication.java
β”‚ β”‚ β”‚ └── config/
β”‚ β”‚ β”‚ └── SecurityConfig.java
β”‚ β”‚ └── resources/
β”‚ β”‚ └── application.properties
β”‚ └── Dockerfile
β”‚
β”œβ”€β”€ config-server/
β”‚ β”œβ”€β”€ pom.xml
β”‚ β”œβ”€β”€ src/
β”‚ β”‚ └── main/
β”‚ β”‚ β”œβ”€β”€ java/com/hospital/config/
β”‚ β”‚ β”‚ └── ConfigServerApplication.java
β”‚ β”‚ └── resources/
β”‚ β”‚ └── application.properties
β”‚ └── Dockerfile
β”‚
β”œβ”€β”€ docker-compose.yml
β”œβ”€β”€ kubernetes/
β”‚ β”œβ”€β”€ deployments/
β”‚ └── services/
└── README.md
πŸ”§ Infrastructure Components

  1. Service Registry (Eureka) java // service-registry/src/main/java/com/hospital/registry/RegistryApplication.java @SpringBootApplication @EnableEurekaServer public class RegistryApplication { public static void main(String[] args) { SpringApplication.run(RegistryApplication.class, args); } } properties # service-registry/src/main/resources/application.properties spring.application.name=service-registry server.port=8761 eureka.client.register-with-eureka=false eureka.client.fetch-registry=false
  2. API Gateway (Spring Cloud Gateway)
    java
    // api-gateway/src/main/java/com/hospital/gateway/config/RouteConfig.java
    @Configuration
    public class RouteConfig {

    @bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
    .route("identity-service", r -> r
    .path("/api/auth/", "/api/users/")
    .uri("lb://IDENTITY-SERVICE"))
    .route("doctor-service", r -> r
    .path("/api/doctors/")
    .uri("lb://DOCTOR-SERVICE"))
    .route("appointment-service", r -> r
    .path("/api/appointments/
    ")
    .uri("lb://APPOINTMENT-SERVICE"))
    .route("billing-service", r -> r
    .path("/api/bills/", "/api/invoices/")
    .uri("lb://BILLING-SERVICE"))
    .build();
    }

    @bean
    public GlobalFilter customGlobalFilter() {
    return (exchange, chain) -> {
    // Add correlation ID for tracing
    String correlationId = UUID.randomUUID().toString();
    exchange = exchange.mutate()
    .request(r -> r.header("X-Correlation-ID", correlationId))
    .build();
    return chain.filter(exchange);
    };
    }
    }
    πŸ”„ Service Communication Patterns
    Pattern 1: Synchronous REST Communication
    java
    // doctor-service/src/main/java/com/hospital/doctor/client/IdentityServiceClient.java
    @FeignClient(name = "identity-service", url = "${identity.service.url}")
    public interface IdentityServiceClient {

    @GetMapping("/api/users/{userId}")
    UserDTO getUserById(@PathVariable("userId") Long userId);

    @GetMapping("/api/users/validate")
    Boolean validateUser(@RequestParam("email") String email,
    @RequestParam("password") String password);
    }

// appointment-service/src/main/java/com/hospital/appointment/client/DoctorServiceClient.java
@FeignClient(name = "doctor-service")
public interface DoctorServiceClient {

@GetMapping("/api/doctors/{doctorId}/availability")
AvailabilityDTO checkAvailability(
    @PathVariable("doctorId") Long doctorId,
    @RequestParam("dateTime") String dateTime
);

@PostMapping("/api/doctors/{doctorId}/book-slot")
void bookDoctorSlot(
    @PathVariable("doctorId") Long doctorId,
    @RequestBody SlotBookingRequest request
);
Enter fullscreen mode Exit fullscreen mode

}

// appointment-service/src/main/java/com/hospital/appointment/service/AppointmentService.java
@Service
public class AppointmentService {

private final DoctorServiceClient doctorClient;
private final IdentityServiceClient identityClient;

public AppointmentDTO bookAppointment(AppointmentRequest request) {
    // 1. Validate patient with identity service (REST call)
    UserDTO patient = identityClient.getUserById(request.getPatientId());

    // 2. Check doctor availability (REST call)
    AvailabilityDTO availability = doctorClient.checkAvailability(
        request.getDoctorId(),
        request.getDateTime()
    );

    if (!availability.isAvailable()) {
        throw new AppointmentNotAvailableException();
    }

    // 3. Book doctor slot (REST call)
    doctorClient.bookDoctorSlot(
        request.getDoctorId(),
        new SlotBookingRequest(request.getDateTime(), request.getPatientId())
    );

    // 4. Create appointment locally
    Appointment appointment = createAppointment(request, patient);
    appointment = appointmentRepository.save(appointment);

    return mapToDTO(appointment);
}
Enter fullscreen mode Exit fullscreen mode

}
Pattern 2: Asynchronous Event Communication with Kafka
java
// 1. Define Common Event Classes (shared library)
// shared-events/src/main/java/com/hospital/events/AppointmentBookedEvent.java
public class AppointmentBookedEvent {
private Long appointmentId;
private Long patientId;
private Long doctorId;
private LocalDateTime appointmentTime;
private BigDecimal consultationFee;
private String patientEmail;
private String patientPhone;

// constructors, getters, setters
Enter fullscreen mode Exit fullscreen mode

}

// 2. Appointment Service - Event Publisher
// appointment-service/src/main/java/com/hospital/appointment/service/AppointmentService.java
@Service
public class AppointmentService {

@Autowired
private KafkaTemplate<String, AppointmentBookedEvent> kafkaTemplate;

@Transactional
public AppointmentDTO bookAppointment(AppointmentRequest request) {
    // Create appointment
    Appointment appointment = createAppointment(request);
    appointment = appointmentRepository.save(appointment);

    // Publish event to Kafka
    AppointmentBookedEvent event = new AppointmentBookedEvent(
        appointment.getId(),
        appointment.getPatientId(),
        appointment.getDoctorId(),
        appointment.getDateTime(),
        appointment.getConsultationFee(),
        appointment.getPatientEmail(),
        appointment.getPatientPhone()
    );

    kafkaTemplate.send("appointment-events", event);

    return mapToDTO(appointment);
}
Enter fullscreen mode Exit fullscreen mode

}

// 3. Billing Service - Event Consumer
// billing-service/src/main/java/com/hospital/billing/consumer/AppointmentEventConsumer.java
@Service
public class AppointmentEventConsumer {

@Autowired
private BillingService billingService;

@KafkaListener(topics = "appointment-events", groupId = "billing-group")
public void handleAppointmentBooked(AppointmentBookedEvent event) {
    log.info("Received appointment booked event: {}", event.getAppointmentId());

    // Generate bill
    Invoice invoice = billingService.generateInvoice(
        event.getAppointmentId(),
        event.getPatientId(),
        event.getConsultationFee()
    );

    // Publish billing event for notification service
    BillGeneratedEvent billEvent = new BillGeneratedEvent(
        invoice.getId(),
        event.getPatientId(),
        invoice.getAmount(),
        event.getPatientEmail(),
        event.getPatientPhone()
    );

    kafkaTemplate.send("billing-events", billEvent);
}
Enter fullscreen mode Exit fullscreen mode

}

// 4. Notification Service - Multiple Event Consumers
// notification-service/src/main/java/com/hospital/notification/consumer/EventConsumer.java
@Service
public class EventConsumer {

@Autowired
private NotificationService notificationService;

@KafkaListener(topics = "appointment-events", groupId = "notification-group")
public void handleAppointmentBooked(AppointmentBookedEvent event) {
    notificationService.sendAppointmentConfirmation(
        event.getPatientEmail(),
        event.getPatientPhone(),
        event.getAppointmentId(),
        event.getAppointmentTime()
    );
}

@KafkaListener(topics = "billing-events", groupId = "notification-group")
public void handleBillGenerated(BillGeneratedEvent event) {
    notificationService.sendInvoiceNotification(
        event.getPatientEmail(),
        event.getPatientPhone(),
        event.getInvoiceId(),
        event.getAmount()
    );
}
Enter fullscreen mode Exit fullscreen mode

}
🧩 Microservices Architecture Diagram
text
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ CLIENT APPLICATIONS β”‚
β”‚ (Web, Mobile, Third-party APIs) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ API GATEWAY β”‚
β”‚ (Spring Cloud Gateway) β”‚
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Routing β”‚ β”‚ Rate β”‚ β”‚ Security β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ Limiting β”‚ β”‚ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚
β–Ό β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ SERVICE REGISTRY β”‚ β”‚ CONFIG SERVER β”‚
β”‚ (Eureka Server) │◄────►│ (Spring Cloud Config)β”‚
β”‚ service discovery β”‚ β”‚ centralized config β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚

β”‚ Service Registration

β–Ό

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ MICROSERVICES β”‚
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ IDENTITY β”‚ β”‚ DOCTOR β”‚ β”‚ APPOINTMENT β”‚ β”‚
β”‚ β”‚ SERVICE β”‚ β”‚ SERVICE β”‚ β”‚ SERVICE β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚PostgreSQLβ”‚ β”‚ β”‚ β”‚PostgreSQLβ”‚ β”‚ β”‚ β”‚PostgreSQLβ”‚ β”‚ β”‚
β”‚ β”‚ β”‚ DB 1 β”‚ β”‚ β”‚ β”‚ DB 2 β”‚ β”‚ β”‚ β”‚ DB 3 β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ BILLING β”‚ β”‚ NOTIFICATION β”‚ β”‚
β”‚ β”‚ SERVICE β”‚ β”‚ SERVICE β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚PostgreSQLβ”‚ β”‚ β”‚ β”‚PostgreSQLβ”‚ β”‚ β”‚
β”‚ β”‚ β”‚ DB 4 β”‚ β”‚ β”‚ β”‚ DB 5 β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ MESSAGE BROKER β”‚
β”‚ (Apache Kafka) β”‚
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Topics: appointment-events, billing-events, notification-eventsβ”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ MONITORING STACK β”‚
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Prometheus β”‚ β”‚ Grafana β”‚ β”‚ Zipkin β”‚ β”‚
β”‚ β”‚ Metrics β”‚ β”‚ Dashboard β”‚ β”‚ Tracing β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
πŸ“ Service Configuration
application.yml for Appointment Service:

yaml
spring:
application:
name: appointment-service

datasource:
url: jdbc:postgresql://localhost:5432/appointment_db
username: app_user
password: ${DB_PASSWORD}

jpa:
hibernate:
ddl-auto: validate
show-sql: false

kafka:
bootstrap-servers: localhost:9092
producer:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
consumer:
group-id: appointment-group
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
properties:
spring.json.trusted.packages: "*"

eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
hostname: localhost
prefer-ip-address: true

server:
port: 8083

management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
🐳 Docker Compose Configuration
yaml

docker-compose.yml

version: '3.8'

services:
# Databases
postgres-identity:
image: postgres:15
environment:
POSTGRES_DB: identity_db
POSTGRES_USER: identity_user
POSTGRES_PASSWORD: ${IDENTITY_DB_PASSWORD}
volumes:
- identity-data:/var/lib/postgresql/data
ports:
- "5432:5432"

postgres-doctor:
image: postgres:15
environment:
POSTGRES_DB: doctor_db
POSTGRES_USER: doctor_user
POSTGRES_PASSWORD: ${DOCTOR_DB_PASSWORD}
volumes:
- doctor-data:/var/lib/postgresql/data
ports:
- "5433:5432"

postgres-appointment:
image: postgres:15
environment:
POSTGRES_DB: appointment_db
POSTGRES_USER: appointment_user
POSTGRES_PASSWORD: ${APPOINTMENT_DB_PASSWORD}
volumes:
- appointment-data:/var/lib/postgresql/data
ports:
- "5434:5432"

# Message Broker
zookeeper:
image: confluentinc/cp-zookeeper:latest
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
ports:
- "2181:2181"

kafka:
image: confluentinc/cp-kafka:latest
depends_on:
- zookeeper
ports:
- "9092:9092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1

# Service Registry
service-registry:
build: ./service-registry
ports:
- "8761:8761"
environment:
- SPRING_PROFILES_ACTIVE=docker

# Config Server
config-server:
build: ./config-server
ports:
- "8888:8888"
environment:
- SPRING_PROFILES_ACTIVE=docker

# API Gateway
api-gateway:
build: ./api-gateway
ports:
- "8080:8080"
depends_on:
- service-registry
- config-server
environment:
- SPRING_PROFILES_ACTIVE=docker
- EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://service-registry:8761/eureka/

# Microservices
identity-service:
build: ./identity-service
depends_on:
- postgres-identity
- service-registry
- config-server
- kafka
environment:
- SPRING_PROFILES_ACTIVE=docker
- DB_PASSWORD=${IDENTITY_DB_PASSWORD}
- EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://service-registry:8761/eureka/
- SPRING_KAFKA_BOOTSTRAPSERVERS=kafka:9092

doctor-service:
build: ./doctor-service
depends_on:
- postgres-doctor
- service-registry
- config-server
- kafka
environment:
- SPRING_PROFILES_ACTIVE=docker
- DB_PASSWORD=${DOCTOR_DB_PASSWORD}
- EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://service-registry:8761/eureka/
- SPRING_KAFKA_BOOTSTRAPSERVERS=kafka:9092

# ... similar for other services

volumes:
identity-data:
doctor-data:
appointment-data:
billing-data:
notification-data:
βœ… Pros of Microservices
Independent Scaling

yaml

Scale only appointment service during peak hours

docker-compose up -d --scale appointment-service=5
Technology Diversity

java
// Appointment service can use PostgreSQL
// Notification service can use MongoDB for logs
// Billing service can use Oracle for compliance
Independent Deployment

Deploy billing service without touching others

Zero downtime deployments possible

A/B testing per service

Fault Isolation

java
// If billing service crashes, appointments still work
// Circuit breakers prevent cascading failures
@CircuitBreaker(name = "billing-service")
public Invoice createBill(Appointment appointment) {
return billingClient.createBill(appointment);
}
Team Autonomy

Each team owns their service

Independent release cycles

Choose their own tools

❌ Cons of Microservices
Distributed Systems Complexity

java
// Network calls can fail
// Need retry logic, circuit breakers
// Distributed tracing required
Data Consistency Challenges

java
// No ACID transactions across services
// Must implement Saga pattern
// Eventual consistency only
Network Latency

java
// Previously: method call (1ms)
// Now: REST call (50-100ms)
// Need to optimize API design
Debugging Difficulty

Logs spread across services

Need centralized logging

Correlation IDs essential

Operational Overhead

Need DevOps team

Monitoring required

Complex deployment pipelines

🎯 When to Use Microservices
βœ… Perfect for:

Large enterprises with many teams

Systems needing independent scaling

Companies with DevOps maturity

Applications with millions of users

When different modules need different tech stacks

❌ Avoid when:

Small team (< 10 developers)

Simple application

Limited DevOps resources

Startup/MVP phase

Strict data consistency requirements

πŸš€ Recommended Evolution Path
Most successful systems evolve naturally:

text
Phase 1: MONOLITH
↓
Phase 2: MODULAR MONOLITH
↓
Phase 3: EXTRACT HIGH-LOAD MODULES
↓
Phase 4: MICROSERVICES
Real-World Evolution Example
Year 1-2: Monolith

java
// Fast development, validate business model
// Hospital OPD system as single Spring Boot app
// Perfect for initial launch
Year 2-3: Modular Monolith

java
// Business growing, team expanding
// Introduce Spring Modulith
// Clear boundaries, event-driven communication
// Still one deployment
Year 3-4: Extract Billing Module

java
// Billing has high load, compliance requirements
// Extract as separate microservice
// Others remain modular monolith
// Hybrid architecture
Year 4+: Full Microservices

java
// Different teams for each domain
// Independent scaling needs
// Full microservices with Kubernetes
πŸ“Š Final Comparison Table
Feature Monolith Modular Monolith Multi-Module Microservices
Deployment Units 1 1 1 Multiple
Databases 1 shared 1 shared 1 shared Separate per service
Communication In-JVM method calls Method calls + Events Compile-time deps + method calls HTTP/REST + Message Queue
Scalability Scale entire app Scale entire app Scale entire app Scale per service
Development Speed (Initial) πŸš€ Fast πŸš€ Fast πŸš€ Fast 🐒 Slow
Development Speed (Long-term) 🐒 Slow πŸš€ Fast πŸš€ Fast πŸš€ Fast
Team Autonomy Low Medium Medium High
Testing Complexity Low Medium Medium High
Operational Complexity Low Low Low High
Fault Isolation None Limited None Excellent
Technology Diversity None None None Full
Learning Curve Easy Medium Medium Steep
Best For MVP, Startups Growing products Large codebases Enterprise scale
🧠 Final Advice for Junior Developers
πŸ“ Golden Rules
Don't Start with Microservices!

java
// Bad: Starting new project with 10 microservices
// Good: Start simple, evolve when needed
Build Clean Boundaries from Day 1

java
// Even in monolith, think in modules
// Separate concerns, avoid circular dependencies
Understand Your Domain First

java
// Domain-driven design helps
// Bounded contexts become service boundaries later
Choose Based on Team Size

text
1-5 developers β†’ Monolith or Modular Monolith
5-15 developers β†’ Modular Monolith
15+ developers β†’ Consider microservices
Remember: Amazon, Netflix Started as Monoliths

They evolved to microservices over years

They had the team size and expertise

They had clear business reasons

πŸŽ“ Career Advice
Learn Monolith first - Understand the basics

Master Modular Monolith - Most common in real jobs

Understand Multi-Module - For build management

Study Microservices - For enterprise roles

πŸ’‘ The Sweet Spot
For 80% of real-world applications, a Modular Monolith is the perfect choice:

Clear architecture

Fast development

Easy deployment

Future-proof

No distributed complexity

πŸ“š Summary
We've explored four architectures for the same Hospital OPD system:

Architecture One-Line Summary
Monolith Everything in one place, simple but messy
Modular Monolith One app with strict internal boundaries
Multi-Module Build-time separation, runtime together
Microservices Fully distributed, maximum flexibility
Your choice depends on:

Team size and expertise

Current stage of project

Scaling requirements

Organizational maturity

🎯 Final Thought
"The best architecture is not the most advanced one, but the one that solves your current problems without creating new ones."

Start simple, evolve gradually, and always keep your modules cleanly separated. Your future self will thank you!

Happy Coding! πŸš€

Found this guide helpful? Share it with fellow developers!

Top comments (0)