Skip to content
IRC-CodingIRC-Coding
Software ArchitectureMonolith MicroservicesAPI GatewayService MeshContainer Orchestration

Monolith vs Microservices: Architecture Guide

Compare monolith and microservices architectures. Learn about API Gateway, Service Mesh, containers, and orchestration best practices.

S

schutzgeist

16 min read
Monolith vs Microservices: Architecture Guide

Software Architecture: Monolith vs Microservices with API Gateway & Service Mesh

This guide is a comprehensive introduction to software architecture – covering monolith versus microservices with API gateway, service mesh, containers, and orchestration, complete with practical examples.

In a Nutshell

A monolithic architecture is a single, cohesive codebase, while microservices are small, independent services. An API gateway coordinates communication between clients and services, and a service mesh manages how microservices interact with each other.

Core Concepts

Software architecture is the foundational structure of a software system, comprising its components, their relationships, and the guiding principles for design and evolution.

Architectural Patterns:

Monolithic Architecture

  • Concept: Single application built from one codebase
  • Deployment: The entire application deploys as a single unit
  • Communication: Direct method calls within the application
  • Advantages: Simple development, straightforward deployment and debugging
  • Disadvantages: Scaling challenges, technology constraints

Microservice Architecture

  • Concept: Small, autonomous services, each with a specific responsibility
  • Deployment: Each service deploys independently
  • Communication: Network-based API calls between services
  • Advantages: Scalability, technology flexibility, resilience
  • Disadvantages: Increased complexity, network latency, distributed system challenges

API Gateway

  • Concept: Single entry point for all client requests
  • Functions: Routing, authentication, rate limiting, load balancing
  • Advantages: Centralized management, security enforcement, monitoring
  • Implementation: Kong, NGINX, AWS API Gateway

Service Mesh

  • Concept: Infrastructure layer for microservice-to-microservice communication
  • Functions: Service discovery, load balancing, circuit breaking
  • Implementation: Istio, Linkerd, Consul Connect
  • Advantages: Visibility, observability, security policies

Key Takeaways

  • Software architecture: The fundamental structure of software systems
  • Monolith: Unified application with shared codebase
  • Microservices: Small, independent services with their own lifecycle
  • API gateway: Central entry point for client communication
  • Service mesh: Infrastructure for microservice interactions
  • Containers: Operating system-level virtualization
  • Orchestration: Automated management and coordination of containers
  • Modern design: Understanding architectural tradeoffs and patterns

Core Components

  1. Architectural decision: Choosing between monolith and microservices
  2. API management: Gateway configuration, routing, and security
  3. Service discovery: Automatic service location and registration
  4. Load balancing: Distributing requests across instances
  5. Circuit breaking: Preventing cascading failures
  6. Containerization: Docker, Podman
  7. Orchestration: Kubernetes, Docker Swarm
  8. Observability: Logging, metrics, distributed tracing

Practical Examples

1. Monolithic Architecture with Java Spring Boot

package com.example.monolith;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;
import org.springframework.stereotype.Service;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.beans.factory.annotation.Autowired;
import javax.persistence.*;
import java.util.List;
import java.util.Optional;

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

// User Entity
@Entity
@Table(name = "users")
class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, unique = true)
    private String username;
    
    @Column(nullable = false)
    private String email;
    
    @Column(nullable = false)
    private String password;
    
    // Getters and Setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
}

// Order Entity
@Entity
@Table(name = "orders")
class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private User user;
    
    @Column(nullable = false)
    private String product;
    
    @Column(nullable = false)
    private Double amount;
    
    @Column(nullable = false)
    private String status;
    
    // Getters and Setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public User getUser() { return user; }
    public void setUser(User user) { this.user = user; }
    
    public String getProduct() { return product; }
    public void setProduct(String product) { this.product = product; }
    
    public Double getAmount() { return amount; }
    public void setAmount(Double amount) { this.amount = amount; }
    
    public String getStatus() { return status; }
    public void setStatus(String status) { this.status = status; }
}

// Repository Interfaces
interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
    boolean existsByUsername(String username);
}

interface OrderRepository extends JpaRepository<Order, Long> {
    List<Order> findByUserId(Long userId);
    List<Order> findByStatus(String status);
}

// Business Logic Services
@Service
class UserService {
    @Autowired
    private UserRepository userRepository;
    
    public User createUser(User user) {
        if (userRepository.existsByUsername(user.getUsername())) {
            throw new IllegalArgumentException("Username already exists");
        }
        return userRepository.save(user);
    }
    
    public Optional<User> getUserById(Long id) {
        return userRepository.findById(id);
    }
    
    public Optional<User> getUserByUsername(String username) {
        return userRepository.findByUsername(username);
    }
    
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
    
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
}

@Service
class OrderService {
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private UserService userService;
    
    public Order createOrder(Long userId, String product, Double amount) {
        Optional<User> userOpt = userService.getUserById(userId);
        if (!userOpt.isPresent()) {
            throw new IllegalArgumentException("User not found");
        }
        
        Order order = new Order();
        order.setUser(userOpt.get());
        order.setProduct(product);
        order.setAmount(amount);
        order.setStatus("PENDING");
        
        return orderRepository.save(order);
    }
    
    public Optional<Order> getOrderById(Long id) {
        return orderRepository.findById(id);
    }
    
    public List<Order> getOrdersByUserId(Long userId) {
        return orderRepository.findByUserId(userId);
    }
    
    public Order updateOrderStatus(Long id, String status) {
        Optional<Order> orderOpt = orderRepository.findById(id);
        if (!orderOpt.isPresent()) {
            throw new IllegalArgumentException("Order not found");
        }
        
        Order order = orderOpt.get();
        order.setStatus(status);
        return orderRepository.save(order);
    }
    
    public void deleteOrder(Long id) {
        orderRepository.deleteById(id);
    }
}

// REST Controllers
@RestController
@RequestMapping("/api/users")
class UserController {
    @Autowired
    private UserService userService;
    
    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.createUser(user);
    }
    
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.getUserById(id)
            .orElseThrow(() -> new IllegalArgumentException("User not found"));
    }
    
    @GetMapping
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }
    
    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
    }
}

@RestController
@RequestMapping("/api/orders")
class OrderController {
    @Autowired
    private OrderService orderService;
    
    @PostMapping
    public Order createOrder(@RequestBody CreateOrderRequest request) {
        return orderService.createOrder(request.getUserId(), request.getProduct(), request.getAmount());
    }
    
    @GetMapping("/{id}")
    public Order getOrder(@PathVariable Long id) {
        return orderService.getOrderById(id)
            .orElseThrow(() -> new IllegalArgumentException("Order not found"));
    }
    
    @GetMapping("/user/{userId}")
    public List<Order> getOrdersByUser(@PathVariable Long userId) {
        return orderService.getOrdersByUserId(userId);
    }
    
    @PutMapping("/{id}/status")
    public Order updateOrderStatus(@PathVariable Long id, @RequestBody String status) {
        return orderService.updateOrderStatus(id, status);
    }
    
    @DeleteMapping("/{id}")
    public void deleteOrder(@PathVariable Long id) {
        orderService.deleteOrder(id);
    }
}

// DTOs
class CreateOrderRequest {
    private Long userId;
    private String product;
    private Double amount;
    
    // Getters and Setters
    public Long getUserId() { return userId; }
    public void setUserId(Long userId) { this.userId = userId; }
    
    public String getProduct() { return product; }
    public void setProduct(String product) { this.product = product; }
    
    public Double getAmount() { return amount; }
    public void setAmount(Double amount) { this.amount = amount; }
}

2. Microservice Architecture with Spring Cloud

// User Microservice
package com.example.userservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.*;
import org.springframework.stereotype.Service;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.beans.factory.annotation.Autowired;
import javax.persistence.*;
import java.util.List;
import java.util.Optional;

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

// User Entity (simplified)
@Entity
class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    private String email;
    
    // Getters and Setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
}

@Service
class UserService {
    @Autowired
    private UserRepository userRepository;
    
    public User createUser(User user) {
        return userRepository.save(user);
    }
    
    public Optional<User> getUserById(Long id) {
        return userRepository.findById(id);
    }
    
    public Optional<User> getUserByUsername(String username) {
        return userRepository.findByUsername(username);
    }
    
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
}

@RestController
@RequestMapping("/api/users")
class UserController {
    @Autowired
    private UserService userService;
    
    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.createUser(user);
    }
    
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.getUserById(id)
            .orElseThrow(() -> new RuntimeException("User not found"));
    }
    
    @GetMapping
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }
}

// Order Microservice
package com.example.orderservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import org.springframework.stereotype.Service;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.beans.factory.annotation.Autowired;
import javax.persistence.*;
import java.util.List;
import java.util.Optional;

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

// Feign Client for User Service
@FeignClient(name = "user-service")
interface UserServiceClient {
    @GetMapping("/api/users/{id}")
    User getUser(@PathVariable("id") Long id);
}

// Order Entity
@Entity
class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private Long userId;
    private String product;
    private Double amount;
    private String status;
    
    // Getters and Setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public Long getUserId() { return userId; }
    public void setUserId(Long userId) { this.userId = userId; }
    
    public String getProduct() { return product; }
    public void setProduct(String product) { this.product = product; }
    
    public Double getAmount() { return amount; }
    public void setAmount(Double amount) { this.amount = amount; }
    
    public String getStatus() { return status; }
    public void setStatus(String status) { this.status = status; }
}

interface OrderRepository extends JpaRepository<Order, Long> {
    List<Order> findByUserId(Long userId);
}

@Service
class OrderService {
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private UserServiceClient userServiceClient;
    
    public Order createOrder(Order order) {
        // User validation via microservice call
        try {
            User user = userServiceClient.getUser(order.getUserId());
            if (user == null) {
                throw new RuntimeException("User not found");
            }
        } catch (Exception e) {
            throw new RuntimeException("User service unavailable");
        }
        
        order.setStatus("PENDING");
        return orderRepository.save(order);
    }
    
    public Optional<Order> getOrderById(Long id) {
        return orderRepository.findById(id);
    }
    
    public List<Order> getOrdersByUserId(Long userId) {
        return orderRepository.findByUserId(userId);
    }
    
    public Order updateOrderStatus(Long id, String status) {
        Optional<Order> orderOpt = orderRepository.findById(id);
        if (!orderOpt.isPresent()) {
            throw new RuntimeException("Order not found");
        }
        
        Order order = orderOpt.get();
        order.setStatus(status);
        return orderRepository.save(order);
    }
}

@RestController
@RequestMapping("/api/orders")
class OrderController {
    @Autowired
    private OrderService orderService;
    
    @PostMapping
    public Order createOrder(@RequestBody Order order) {
        return orderService.createOrder(order);
    }
    
    @GetMapping("/{id}")
    public Order getOrder(@PathVariable Long id) {
        return orderService.getOrderById(id)
            .orElseThrow(() -> new RuntimeException("Order not found"));
    }
    
    @GetMapping("/user/{userId}")
    public List<Order> getOrdersByUser(@PathVariable Long userId) {
        return orderService.getOrdersByUserId(userId);
    }
    
    @PutMapping("/{id}/status")
    public Order updateOrderStatus(@PathVariable Long id, @RequestBody String status) {
        return orderService.updateOrderStatus(id, status);
    }
}

3. API Gateway with Spring Cloud Gateway

package com.example.apigateway;

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;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@SpringBootApplication
public class ApiGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }
    
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            // User Service Routes
            .route("user-service", r -> r.path("/api/users/**")
                .uri("lb://user-service"))
            
            // Order Service Routes
            .route("order-service", r -> r.path("/api/orders/**")
                .uri("lb://order-service"))
            
            // Product Service Routes
            .route("product-service", r -> r.path("/api/products/**")
                .uri("lb://product-service"))
            
            // Authentication Service Routes
            .route("auth-service", r -> r.path("/api/auth/**")
                .uri("lb://auth-service"))
            
            .build();
    }
}

// Authentication Filter
@Component
class AuthenticationFilter implements GlobalFilter, Ordered {
    
    @Override
    public Mono<Void> filter(ServerHttpRequest request, GatewayFilterChain chain) {
        String path = request.getURI().getPath();
        
        // Public endpoints without authentication
        if (path.startsWith("/api/auth/login") || 
            path.startsWith("/api/auth/register") ||
            path.startsWith("/actuator")) {
            return chain.filter(request);
        }
        
        // Extract token from header
        String authHeader = request.getHeaders().getFirst("Authorization");
        
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            return Mono.error(new RuntimeException("Missing or invalid token"));
        }
        
        String token = authHeader.substring(7);
        
        // Validate token (simplified)
        if (!isValidToken(token)) {
            return Mono.error(new RuntimeException("Invalid token"));
        }
        
        // Add user information to request
        ServerHttpRequest modifiedRequest = request.mutate()
            .header("X-User-ID", getUserIdFromToken(token))
            .header("X-User-Role", getUserRoleFromToken(token))
            .build();
        
        return chain.filter(modifiedRequest);
    }
    
    private boolean isValidToken(String token) {
        // Token validation (simplified)
        return token != null && !token.isEmpty();
    }
    
    private String getUserIdFromToken(String token) {
        // Extract user ID from token
        return "123"; // Example
    }
    
    private String getUserRoleFromToken(String token) {
        // Extract user role from token
        return "USER"; // Example
    }
    
    @Override
    public int getOrder() {
        return -100; // High priority
    }
}

// Rate Limiting Filter
@Component
class RateLimitingFilter implements GlobalFilter, Ordered {
    
    private final Map<String, Map<String, Integer>> rateLimitMap = new ConcurrentHashMap<>();
    private static final int RATE_LIMIT = 100; // 100 requests per minute
    private static final int TIME_WINDOW = 60; // seconds
    
    @Override
    public Mono<Void> filter(ServerHttpRequest request, GatewayFilterChain chain) {
        String clientId = getClientId(request);
        String currentTimeWindow = getCurrentTimeWindow();
        
        rateLimitMap.putIfAbsent(clientId, new ConcurrentHashMap<>());
        Map<String, Integer> clientRequests = rateLimitMap.get(clientId);
        
        // Remove old time windows
        clientRequests.entrySet().removeIf(entry -> 
            !entry.getKey().equals(currentTimeWindow));
        
        // Increment current counter
        int currentCount = clientRequests.getOrDefault(currentTimeWindow, 0);
        
        if (currentCount >= RATE_LIMIT) {
            return Mono.error(new RuntimeException("Rate limit exceeded"));
        }
        
        clientRequests.put(currentTimeWindow, currentCount + 1);
        return chain.filter(request);
    }
    
    private String getClientId(ServerHttpRequest request) {
        // Extract client ID from IP or token
        return request.getRemoteAddress() != null ? 
            request.getRemoteAddress().getAddress().getHostAddress() : "unknown";
    }
    
    private String getCurrentTimeWindow() {
        return String.valueOf(System.currentTimeMillis() / (TIME_WINDOW * 1000));
    }
    
    @Override
    public int getOrder() {
        return -99; // After authentication
    }
}

// Load Balancing Configuration
@Component
class LoadBalancerConfig {
    
    @Bean
    public ReactorLoadBalancer<ServiceInstance> userServiceLoadBalancer(
            Environment environment,
            LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RoundRobinLoadBalancer(
            loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
            name);
    }
}

// Circuit Breaker Configuration
@Component
class CircuitBreakerConfig {
    
    @Bean
    public CircuitBreaker circuitBreaker() {
        CircuitBreakerConfig config = CircuitBreakerConfig.custom()
            .failureRateThreshold(50)
            .waitDurationInOpenState(Duration.ofMillis(1000))
            .slidingWindowSize(20)
            .minimumNumberOfCalls(10)
            .build();
        
        return CircuitBreaker.of("myCircuitBreaker", config);
    }
}

// API Gateway Controller for health checks
@RestController
@RequestMapping("/gateway")
class GatewayController {
    
    @GetMapping("/health")
    public Map<String, String> health() {
        Map<String, String> status = new HashMap<>();
        status.put("status", "UP");
        status.put("service", "api-gateway");
        status.put("timestamp", Instant.now().toString());
        return status;
    }
    
    @GetMapping("/routes")
    public Map<String, Object> getRoutes() {
        Map<String, Object> routes = new HashMap<>();
        routes.put("user-service", "/api/users/**");
        routes.put("order-service", "/api/orders/**");
        routes.put("product-service", "/api/products/**");
        routes.put("auth-service", "/api/auth/**");
        return routes;
    }
}

4. Docker Containers for Microservices

# Dockerfile für User Service
FROM openjdk:11-jre-slim

# Working Directory
WORKDIR /app

# Application JAR kopieren
COPY target/user-service-0.0.1-SNAPSHOT.jar app.jar

# Expose Port
EXPOSE 8081

# Environment Variables
ENV JAVA_OPTS="-Xmx512m -Xms256m"
ENV SPRING_PROFILES_ACTIVE=docker

# Health Check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8081/actuator/health || exit 1

# Application starten
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
# Dockerfile für Order Service
FROM openjdk:11-jre-slim

WORKDIR /app

COPY target/order-service-0.0.1-SNAPSHOT.jar app.jar

EXPOSE 8082

ENV JAVA_OPTS="-Xmx512m -Xms256m"
ENV SPRING_PROFILES_ACTIVE=docker

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8082/actuator/health || exit 1

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
# Dockerfile für API Gateway
FROM openjdk:11-jre-slim

WORKDIR /app

COPY target/api-gateway-0.0.1-SNAPSHOT.jar app.jar

EXPOSE 8080

ENV JAVA_OPTS="-Xmx512m -Xms256m"
ENV SPRING_PROFILES_ACTIVE=docker

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8080/gateway/health || exit 1

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

5. Kubernetes Orchestration

# Namespace
apiVersion: v1
kind: Namespace
metadata:
  name: microservices

---
# User Service Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
  namespace: microservices
spec:
  replicas: 3
  selector:
    matchLabels:
      app: user-service
  template:
    metadata:
      labels:
        app: user-service
    spec:
      containers:
      - name: user-service
        image: user-service:latest
        ports:
        - containerPort: 8081
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "kubernetes"
        - name: SPRING_DATASOURCE_URL
          value: "jdbc:postgresql://postgres-service:5432/userdb"
        - name: SPRING_DATASOURCE_USERNAME
          value: "postgres"
        - name: SPRING_DATASOURCE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: password
        livenessProbe:
          httpGet:
            path: /actuator/health
            port: 8081
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /actuator/health
            port: 8081
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

---
# User Service Service
apiVersion: v1
kind: Service
metadata:
  name: user-service
  namespace: microservices
spec:
  selector:
    app: user-service
  ports:
  - protocol: TCP
    port: 8081
    targetPort: 8081
  type: ClusterIP

---
# Order Service Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: microservices
spec:
  replicas: 2
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
      - name: order-service
        image: order-service:latest
        ports:
        - containerPort: 8082
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "kubernetes"
        - name: USER_SERVICE_URL
          value: "http://user-service:8081"
        livenessProbe:
          httpGet:
            path: /actuator/health
            port: 8082
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /actuator/health
            port: 8082
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

---
# Order Service Service
apiVersion: v1
kind: Service
metadata:
  name: order-service
  namespace: microservices
spec:
  selector:
    app: order-service
  ports:
  - protocol: TCP
    port: 8082
    targetPort: 8082
  type: ClusterIP

---
# API Gateway Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-gateway
  namespace: microservices
spec:
  replicas: 2
  selector:
    matchLabels:
      app: api-gateway
  template:
    metadata:
      labels:
        app: api-gateway
    spec:
      containers:
      - name: api-gateway
        image: api-gateway:latest
        ports:
        - containerPort: 8080
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "kubernetes"
        livenessProbe:
          httpGet:
            path: /gateway/health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /gateway/health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

---
# API Gateway Service
apiVersion: v1
kind: Service
metadata:
  name: api-gateway
  namespace: microservices
spec:
  selector:
    app: api-gateway
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 8080
  type: LoadBalancer

---
# Horizontal Pod Autoscaler for User Service
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: user-service-hpa
  namespace: microservices
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: user-service
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80

---
# ConfigMap for Configuration
apiVersion: v1
kind: ConfigMap
metadata:
  name: microservices-config
  namespace: microservices
data:
  application.yml: |
    spring:
      cloud:
        kubernetes:
          discovery:
            enabled: true
          config:
            enabled: true
    management:
      endpoints:
        web:
          exposure:
            include: health,info,metrics,prometheus
      endpoint:
        health:
          show-details: always

---
# Secret for Sensitive Data
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
  namespace: microservices
type: Opaque
data:
  password: cGFzc3dvcmQxMjM=  # Base64 encoded "password123"
  username: cG9zdGdyZXM=      # Base64 encoded "postgres"

6. Service Mesh with Istio

# Istio Gateway
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: microservices-gateway
  namespace: microservices
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"

---
# Virtual Service for API Gateway
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: api-gateway-vs
  namespace: microservices
spec:
  hosts:
  - "*"
  gateways:
  - microservices-gateway
  http:
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: api-gateway
        port:
          number: 8080

---
# Destination Rules for Load Balancing
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: user-service-dr
  namespace: microservices
spec:
  host: user-service
  trafficPolicy:
    loadBalancer:
      simple: ROUND_ROBIN
    connectionPool:
      tcp:
        maxConnections: 100
      http:
        http1MaxPendingRequests: 50
        maxRequestsPerConnection: 10
    circuitBreaker:
      consecutiveErrors: 3
      interval: 30s
      baseEjectionTime: 30s

---
# Service Entry for External Services
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
  name: external-api
  namespace: microservices
spec:
  hosts:
  - external-api.example.com
  ports:
  - number: 443
    name: https
    protocol: HTTPS
  resolution: DNS

---
# Authorization Policy
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: allow-api-access
  namespace: microservices
spec:
  selector:
    matchLabels:
      app: api-gateway
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"]
  - to:
    - operation:
        methods: ["GET", "POST", "PUT", "DELETE"]

---
# Request Authentication
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
  name: jwt-auth
  namespace: microservices
spec:
  selector:
    matchLabels:
      app: api-gateway
  jwtRules:
  - issuer: "https://auth.example.com"
    jwksUri: "https://auth.example.com/.well-known/jwks.json"
    forwardOriginalToken: true

Architecture Comparison

Monolith vs Microservices

CriterionMonolithMicroservices
ComplexityLowHigh
DeploymentSimpleComplex
ScalingVerticalHorizontal
TechnologyUnifiedFlexible
Fault IsolationPoorGood
Team StructureCentralizedDistributed
DevelopmentFastSlower

Deployment Strategies

StrategyDescriptionAdvantagesDisadvantages
Big BangAll at onceSimpleHigh risk
Blue-GreenParallel systemsZero-downtimeResource overhead
CanaryGradual rolloutSafeComplex
RollingIncrementalEfficientSlow

Container Technologies

Docker vs Podman

FeatureDockerPodman
DaemonYesNo
RootlessLimitedFull
PodsNoYes
DockerfileYesYes
ComposeYesLimited

Container Registry

RegistryFeaturesPricing
Docker HubPublic/PrivateFreemium
GitHub PackagesIntegrationFree
AWS ECRCloud-nativePay-per-use
Google GCRCloud-nativePay-per-use

Orchestration Platforms

Kubernetes vs Docker Swarm

FeatureKubernetesDocker Swarm
ComplexityHighLow
ScalingAutomaticManual
Service MeshYesNo
Learning CurveSteepShallow
CommunityLargeMedium

Service Mesh Implementations

Istio vs Linkerd

FeatureIstioLinkerd
ComplexityHighLow
FeaturesComprehensiveFocused
PerformanceGoodVery good
ResourcesHighLow
IntegrationComprehensiveSimple

API Gateway Features

Core Functions

  • Routing: Direct requests to the correct services
  • Authentication: JWT, OAuth2, API Keys
  • Authorization: Role-based access control
  • Rate Limiting: Request limits per client
  • Load Balancing: Distribute traffic across instances
  • Caching: Response caching
  • Logging: Request and response logging

Advanced Features

  • Circuit Breaking: Protect against cascading failures
  • Retry Logic: Automatic request retries
  • Request/Response Transformation: Transform data in flight
  • API Versioning: Manage multiple API versions
  • Analytics: Usage statistics and insights

Monitoring & Observability

Three Pillars of Observability

  1. Metrics: Quantitative measurements (Prometheus, Grafana)
  2. Logging: Event-based data (ELK Stack, Fluentd)
  3. Tracing: Request tracking across services (Jaeger, Zipkin)

Tools in the Microservices Context

# Prometheus Monitoring
apiVersion: v1
kind: ServiceMonitor
metadata:
  name: user-service-monitor
  namespace: microservices
spec:
  selector:
    matchLabels:
      app: user-service
  endpoints:
  - port: http
    path: /actuator/prometheus
    interval: 30s

Best Practices

Microservice Design

  • Single Responsibility: Each service handles one business capability
  • Database per Service: Separate data store for each service
  • Async Communication: Event-driven interaction between services
  • Circuit Breaking: Prevent cascading failures
  • Health Checks: Monitor service health continuously

Container Best Practices

# Best Practice Dockerfile
FROM alpine:latest

# Non-root User
RUN addgroup -g 1001 -S appgroup && \
    adduser -u 1001 -S appuser -G appgroup

USER appuser

# Minimal Image
COPY --from=builder /app/target/app.jar /app/app.jar

WORKDIR /app

EXPOSE 8080

# Health Check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1

ENTRYPOINT ["java", "-jar", "app.jar"]

Advantages and Disadvantages

Advantages of Microservices

  • Scalability: Scale individual services independently
  • Flexibility: Mix and match technologies per service
  • Resilience: One service failing doesn’t bring down the entire system
  • Team Autonomy: Teams develop and deploy independently
  • Faster Deployments: Small, frequent releases

Disadvantages of Microservices

  • Complexity: Distributed systems introduce operational challenges
  • Network Latency: Inter-service communication over the network
  • Data Consistency: Maintaining consistency across services
  • Monitoring: More difficult to trace issues across services
  • Debugging: Identifying root causes becomes harder

Common Exam Questions

  1. What are the main differences between monolith and microservices? A monolith is a single application, while microservices break functionality into small, independently deployable services.

  2. Explain the role of an API Gateway! The API Gateway serves as a single entry point for client requests, handling routing, authentication, and rate limiting.

  3. When do you use containers versus orchestration? Containers provide isolation and portability for individual applications, while orchestration automates management of many containers at scale.

  4. What is a Service Mesh and why is it needed? A Service Mesh is an infrastructure layer managing inter-service communication, providing service discovery, load balancing, and security.

Key References

  1. https://microservices.io/
  2. https://kubernetes.io/
  3. https://istio.io/
  4. https://spring.io/projects/spring-cloud
Back to Blog
Share:

Related Posts