π 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;
}
}
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
}
β
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());
}
}
// 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);
}
}
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
}
// 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);
}
}
// 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));
}
}
// 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()
);
}
}
π§© 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!");
};
}
}
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>
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>
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>
π 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);
}
}
π§© 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
- 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
-
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
);
}
// 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);
}
}
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
}
// 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);
}
}
// 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);
}
}
// 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()
);
}
}
π§© 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)