<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400..900;1,400..900&family=Libre+Caslon+Text:ital,wght@0,400;0,700;1,400&family=Libre+Franklin:wght@400..800&family=JetBrains+Mono:wght@400;500;700&display=swap" />
Late edition Stop the presses

Suspect at large

A backend developer is fleeing the scene. The record is being set.

Setting the type Inking the plates Running the presses

Skip to the front page
Kathmandu, NepalThe Backend EditionEst. 2021

The personal record of a backend developer

Wednesday 5 August 2026Vol. ISelected works & notesPrice: one coffee
← Back to the dispatchesMicroservices · 15 min read

Microservices

Introduction to Microservices Architecture

Learn the fundamentals of microservices architecture and how to implement scalable, resilient systems using Spring Boot, Docker, and Kubernetes.

By · Backend Developer — Spring Boot & DevOps1 January 2024 · 15 min read

Microservices architecture is a powerful approach for building scalable, resilient, and maintainable applications. This comprehensive guide explores the fundamentals of microservices, their benefits and challenges, key design patterns, and practical implementation using Spring Boot, Docker, and Kubernetes. Whether you're transitioning from a monolithic architecture or building a new system, this tutorial provides actionable insights and code examples to help you succeed.

What are Microservices?

Microservices architecture structures an application as a collection of small, independent services, each focusing on a specific business capability. These services communicate through well-defined APIs or message queues and can be developed, deployed, and scaled independently.

Key Characteristics

  • Single Responsibility: Each microservice handles one specific function, such as user management or order processing.
  • Decentralized Data Management: Each service manages its own database, reducing dependencies.
  • Technology Agnostic: Services can use different programming languages, frameworks, or databases.
  • Independent Deployment: Services can be updated or deployed without affecting the entire system.
  • Fault Isolation: A failure in one service does not necessarily impact others.

Why Microservices?

Microservices are ideal for large-scale applications requiring flexibility, scalability, and team autonomy. They align well with modern DevOps practices and cloud-native environments, enabling faster development cycles and easier maintenance.

Benefits and Challenges

Benefits

  1. 01Scalability: Scale individual services based on demand, optimizing resource usage.
  2. 02Flexibility: Use the best technology stack for each service (e.g., Java for backend services, Python for data processing).
  3. 03Team Autonomy: Independent teams can work on different services, accelerating development.
  4. 04Resilience: Fault isolation ensures that a failure in one service doesn’t bring down the entire system.
  5. 05Faster Releases: Smaller codebases enable quicker updates and deployments.

Challenges

  1. 01Distributed System Complexity: Managing inter-service communication and data consistency is complex.
  2. 02Network Latency: Service-to-service calls over the network introduce latency.
  3. 03Data Management: Ensuring consistency across distributed databases requires careful design (e.g., eventual consistency).
  4. 04Testing Complexity: Integration and end-to-end testing are more challenging than in monolithic applications.
  5. 05Monitoring Overhead: Comprehensive observability is needed to track distributed services.

Real-World Use Case

Imagine an e-commerce platform with features like user management, product catalog, order processing, and payment handling. In a monolithic architecture, all features share a single codebase and database, making scaling and updates difficult. With microservices, each feature becomes a separate service, allowing independent scaling (e.g., scaling the order service during a flash sale) and enabling parallel development by multiple teams.

Design Patterns

Microservices architecture leverages several design patterns to address common challenges. Below are key patterns with practical implementations using Spring Boot.

Service Discovery with Eureka

Service discovery allows microservices to locate and communicate with each other dynamically. Spring Cloud Netflix Eureka is a popular service registry.

java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

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

Client Configuration (in a microservice):

java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

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

application.yml (for User Service):

yaml
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
spring:
  application:
    name: user-service
server:
  port: 8081

API Gateway with Spring Cloud Gateway

An API Gateway provides a single entry point for client requests, routing them to the appropriate microservice.

java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;

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

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("user-service", r -> r.path("/api/users/**")
                        .uri("lb://user-service"))
                .route("order-service", r -> r.path("/api/orders/**")
                        .uri("lb://order-service"))
                .build();
    }
}

application.yml (for API Gateway):

yaml
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 8080

Circuit Breaker with Resilience4j

Circuit breakers prevent cascading failures by providing fallback mechanisms when a service is unavailable.

java
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
public class OrderServiceClient {
    private final RestTemplate restTemplate;

    public OrderServiceClient(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @CircuitBreaker(name = "orderService", fallbackMethod = "getDefaultOrder")
    public Order getOrder(Long orderId) {
        return restTemplate.getForObject("http://order-service/api/orders/" + orderId, Order.class);
    }

    public Order getDefaultOrder(Long orderId, Throwable throwable) {
        return new Order(orderId, "Default Order", 0.0, "UNAVAILABLE");
    }
}

application.yml (for circuit breaker):

yaml
resilience4j.circuitbreaker:
  instances:
    orderService:
      slidingWindowSize: 10
      failureRateThreshold: 50
      waitDurationInOpenState: 10000
      permittedNumberOfCallsInHalfOpenState: 5

Event-Driven Communication with Kafka

Asynchronous communication reduces coupling between services. Apache Kafka is used for event-driven interactions.

java
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
public class OrderEventProducer {
    private final KafkaTemplate<String, Object> kafkaTemplate;

    public OrderEventProducer(KafkaTemplate<String, Object> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    public void publishOrderCreated(Order order) {
        OrderCreatedEvent event = new OrderCreatedEvent(
                order.getId(),
                order.getCustomerId(),
                order.getTotal(),
                Instant.now()
        );
        kafkaTemplate.send("order-events", order.getId().toString(), event);
    }
}
java
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

@Component
public class OrderEventConsumer {
    private static final Logger logger = LoggerFactory.getLogger(OrderEventConsumer.class);

    @KafkaListener(topics = "order-events", groupId = "notification-service")
    public void handleOrderCreated(OrderCreatedEvent event) {
        logger.info("Received order created event for order ID: {}", event.getOrderId());
        // Send notification to customer
        notificationService.sendOrderConfirmation(event.getCustomerId(), event.getOrderId());
    }
}

Implementation with Spring Boot

Below is an implementation of a simple e-commerce system with two microservices: User Service and Order Service.

User Service

Entity:

java
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "users")
public class User {
    @Id
    private Long id;

    private String username;
    private String email;

    // Constructors, getters, and setters
}

Repository:

java
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}

Controller:

java
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/users")
public class UserController {
    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User createdUser = userService.createUser(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
    }

    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        User user = userService.getUser(id);
        return ResponseEntity.ok(user);
    }
}

Service:

java
import org.springframework.stereotype.Service;

@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User createUser(User user) {
        if (userRepository.findByEmail(user.getEmail()).isPresent()) {
            throw new RuntimeException("Email already exists");
        }
        return userRepository.save(user);
    }

    public User getUser(Long id) {
        return userRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("User not found"));
    }
}

Order Service

Entity:

java
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "orders")
public class Order {
    @Id
    private Long id;

    private Long customerId;
    private Double total;
    private String status;

    // Constructors, getters, and setters
}

Repository:

java
import org.springframework.data.jpa.repository.JpaRepository;

public interface OrderRepository extends JpaRepository<Order, Long> {
    List<Order> findByCustomerId(Long customerId);
}

Controller:

java
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/orders")
public class OrderController {
    private final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @PostMapping
    public ResponseEntity<Order> createOrder(@RequestBody Order order) {
        Order createdOrder = orderService.createOrder(order);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdOrder);
    }

    @GetMapping("/{id}")
    public ResponseEntity<Order> getOrder(@PathVariable Long id) {
        Order order = orderService.getOrder(id);
        return ResponseEntity.ok(order);
    }
}

Service:

java
import org.springframework.stereotype.Service;

@Service
public class OrderService {
    private final OrderRepository orderRepository;
    private final OrderEventProducer eventProducer;

    public OrderService(OrderRepository orderRepository, OrderEventProducer eventProducer) {
        this.orderRepository = orderRepository;
        this.eventProducer = eventProducer;
    }

    public Order createOrder(Order order) {
        Order savedOrder = orderRepository.save(order);
        eventProducer.publishOrderCreated(savedOrder);
        return savedOrder;
    }

    public Order getOrder(Long id) {
        return orderRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("Order not found"));
    }
}

Configuration

application.yml (for User Service):

yaml
spring:
  application:
    name: user-service
  datasource:
    url: jdbc:mysql://localhost:3306/user_db
    username: root
    password: password
  jpa:
    hibernate:
      ddl-auto: update
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 8081

application.yml (for Order Service):

yaml
spring:
  application:
    name: order-service
  datasource:
    url: jdbc:mysql://localhost:3306/order_db
    username: root
    password: password
  jpa:
    hibernate:
      ddl-auto: update
  kafka:
    bootstrap-servers: localhost:9092
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 8082

Deployment Strategies

Deploying microservices requires orchestration and containerization for scalability and reliability.

Dockerizing Microservices

Dockerfile (for User Service):

dockerfile
# Build stage
FROM maven:3.8.4-openjdk-17 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests

# Runtime stage
FROM openjdk:17-jdk-slim
RUN addgroup --system spring && adduser --system spring --ingroup spring
WORKDIR /app
COPY --from=build /app/target/user-service-1.0.0.jar app.jar
RUN chown spring:spring app.jar
USER spring:spring
EXPOSE 8081
ENTRYPOINT ["java", "-jar", "app.jar"]

Docker Compose (for orchestrating services):

yaml
version: '3.8'
services:
  eureka-server:
    build: ./eureka-server
    ports:
      - "8761:8761"
  user-service:
    build: ./user-service
    ports:
      - "8081:8081"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://eureka-server:8761/eureka/
    depends_on:
      - eureka-server
      - user-db
  order-service:
    build: ./order-service
    ports:
      - "8082:8082"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://eureka-server:8761/eureka/
    depends_on:
      - eureka-server
      - order-db
      - kafka
  user-db:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=user_db
    ports:
      - "3307:3306"
  order-db:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=order_db
    ports:
      - "3308:3306"
  kafka:
    image: confluentinc/cp-kafka:7.3.0
    environment:
      - KAFKA_BROKER_ID=1
      - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181
      - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092
      - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1
    ports:
      - "9092:9092"
    depends_on:
      - zookeeper
  zookeeper:
    image: confluentinc/cp-zookeeper:7.3.0
    environment:
      - ZOOKEEPER_CLIENT_PORT=2181
      - ZOOKEEPER_TICK_TIME=2000
    ports:
      - "2181:2181"

Kubernetes Deployment

Deployment YAML (for User Service):

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: user-service
  template:
    metadata:
      labels:
        app: user-service
    spec:
      containers:
      - name: user-service
        image: myregistry/user-service:1.0.0
        ports:
        - containerPort: 8081
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "prod"
        - name: EUREKA_CLIENT_SERVICEURL_DEFAULTZONE
          value: "http://eureka-server:8761/eureka/"

Service YAML (for User Service):

yaml
apiVersion: v1
kind: Service
metadata:
  name: user-service
spec:
  selector:
    app: user-service
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8081
  type: ClusterIP

Monitoring and Logging

Effective monitoring and logging are essential for maintaining microservices.

Prometheus Configuration (in application.yml):

yaml
management:
  endpoints:
    web:
      exposure:
        include: prometheus, health, info

Prometheus Scrape Config (prometheus.yml):

yaml
scrape_configs:
  - job_name: 'user-service'
    metrics_path: '/actuator/prometheus'
    static_configs:
    - targets: ['user-service:8081']
  - job_name: 'order-service'
    metrics_path: '/actuator/prometheus'
    static_configs:
    - targets: ['order-service:8082']

Centralized Logging:

java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component
public class LogService {
    private static final Logger logger = LoggerFactory.getLogger(LogService.class);

    public void logAction(String action) {
        logger.info("Action performed: {}", action);
    }
}

Best Practices

  1. 01Domain-Driven Design:
  • Align services with business domains (e.g., user management, order processing).
  • Example: Separate services for inventory, payments, and shipping.
  1. 01Database per Service:
  • Use separate databases for each service to ensure loose coupling.
  • Example: Use Flyway for database migrations:
java
     @Component
     public class DatabaseMigrationService {
         @Autowired
         private Flyway flyway;

         @PostConstruct
         public void migrate() {
             flyway.migrate();
         }
     }
  1. 01API Versioning:
  • Version APIs to handle breaking changes (e.g., / api / v1 / users).
  • Example:
java
     @RestController
     @RequestMapping("/api/v1/users")
     public class UserController {
         // Controller logic
     }
  1. 01Resilience:
  • Use retries and timeouts for inter-service calls.
  • Example with Resilience4j retry:
java
     @Retry(name = "orderService", fallbackMethod = "retryFallback")
     public Order getOrder(Long orderId) {
         return restTemplate.getForObject("http://order-service/api/orders/" + orderId, Order.class);
     }

     public Order retryFallback(Long orderId, Throwable throwable) {
         return new Order(orderId, "Retry Failed", 0.0, "ERROR");
     }
  1. 01Security:
  • Secure APIs with JWT or OAuth2.
  • Implement rate limiting to prevent abuse:
java
     @Component
     public class RateLimiter {
         private final RateLimiterRegistry registry = RateLimiterRegistry.ofDefaults();

         @PostMapping("/api/users")
         @RateLimiter(name = "userCreation", fallbackMethod = "rateLimitFallback")
         public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
             User createdUser = userService.createUser(user);
             return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
         }

         public ResponseEntity<?> rateLimitFallback(User user, RateLimitException ex) {
             return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
                     .body(new ErrorResponse(HttpStatus.TOO_MANY_REQUESTS.value(), "Rate limit exceeded"));
         }
     }

Conclusion

Microservices architecture enables scalable, flexible, and resilient applications. By using Spring Boot for development, Docker for containerization, and Kubernetes for orchestration, you can build robust systems. Key takeaways:

  • Design services with single responsibilities and clear boundaries.
  • Use service discovery, API gateways, and circuit breakers for resilience.
  • Implement event-driven communication with Kafka for loose coupling.
  • Monitor services with Prometheus and centralized logging.
  • Follow best practices like domain-driven design and database-per-service patterns.

Call to Action: Start building your microservices-based application using the examples provided. Deploy locally with Docker Compose and scale to production with Kubernetes. Share your feedback or questions in the comments or on X to join the developer community!

Backend developer specializing in Java and Spring Boot. Building scalable, reliable systems that power modern applications. This broadsheet is hand-set in Caslon and Franklin.

The desk

Tech stack

  • Java & Spring Boot
  • MySQL & PostgreSQL
  • Docker & Microservices
  • JWT & OAuth2
Case closed

System up since 2021 · Building backend systems · Learning new technologies · Contributing to open source

© 2026 Utsab Dahal · All rights reserved · Printed in Kathmandu