Software Architecture: Monolith vs Microservices with API Gateway & Service Mesh
This article is a comprehensive introduction to Software Architecture – including monolith vs microservices with API Gateway, Service Mesh, containers and orchestration with practical examples.
In a Nutshell
Monolith architecture is a single, cohesive code block, while microservices are small, independent services. API Gateway coordinates communication, Service Mesh manages microservice interactions.
Compact Technical Description
Software Architecture is the fundamental structure of a software system, consisting of components, their relationships, and principles for design and evolution.
Architecture Patterns:
Monolith Architecture
- Concept: Unified application in a single codebase
- Deployment: Single deployment of the entire application
- Communication: Direct method calls within the application
- Advantages: Simple development, deployment, debugging
- Disadvantages: Scaling challenges, technology constraints
Microservice Architecture
- Concept: Small, autonomous services with their own responsibility
- Deployment: Independent deployment capability
- Communication: Network-based API calls
- Advantages: Scalability, technology flexibility, resilience
- Disadvantages: Complexity, network latency, distributed complexity
API Gateway
- Concept: Central entry point for client requests
- Functions: Routing, authentication, rate limiting, load balancing
- Advantages: Centralized management, security, monitoring
- Implementation: Kong, NGINX, AWS API Gateway
Service Mesh
- Concept: Infrastructure layer for microservice communication
- Functions: Service discovery, load balancing, circuit breaking
- Implementation: Istio, Linkerd, Consul Connect
- Advantages: Transparency, observability, security
Exam-Relevant Key Points
- Software Architecture: 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: Virtualization at operating system level
- Orchestration: Automated management of containers
- IHK-relevant: Modern architecture decisions and patterns
Core Components
- Architecture Decision: Monolith vs Microservices
- API Management: Gateway, routing, security
- Service Discovery: Automatic service discovery
- Load Balancing: Distribution of requests
- Circuit Breaking: Protection against cascading failures
- Containerization: Docker, Podman
- Orchestration: Kubernetes, Docker Swarm
- Monitoring: Logging, metrics, tracing
Practical Examples
1. Monolith 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-Architektur mit 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 (vereinfacht)
@Entity
class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
// Getters und 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 für 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 und 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 mit 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 für 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 Container 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
| Criterion | Monolith | Microservices |
|---|---|---|
| Complexity | Low | High |
| Deployment | Simple | Complex |
| Scaling | Vertical | Horizontal |
| Technology | Uniform | Flexible |
| Error Isolation | Poor | Good |
| Team Structure | Centralized | Distributed |
| Development | Fast | Slower |
Deployment Strategies
| Strategy | Description | Advantages | Disadvantages |
|---|---|---|---|
| Big Bang | Everything at once | Simple | High-risk |
| Blue-Green | Parallel systems | Zero-downtime | Resources |
| Canary | Gradual | Safe | Complex |
| Rolling | Gradual | Efficient | Slow |
Container Technologies
Docker vs Podman
| Feature | Docker | Podman |
|---|---|---|
| Daemon | Yes | No |
| Rootless | Limited | Full |
| Pods | No | Yes |
| Dockerfile | Yes | Yes |
| Compose | Yes | Limited |
Container Registry
| Registry | Features | Price |
|---|---|---|
| Docker Hub | Public/Private | Freemium |
| GitHub Packages | Integration | Free |
| AWS ECR | Cloud-native | Pay-per-use |
| Google GCR | Cloud-native | Pay-per-use |
Orchestration Platforms
Kubernetes vs Docker Swarm
| Feature | Kubernetes | Docker Swarm |
|---|---|---|
| Complexity | High | Low |
| Scaling | Auto | Manual |
| Service Mesh | Yes | No |
| Learning Curve | Steep | Flat |
| Community | Large | Medium |
Service Mesh Implementations
Istio vs Linkerd
| Feature | Istio | Linkerd |
|---|---|---|
| Complexity | High | Low |
| Features | Comprehensive | Focused |
| Performance | Good | Very good |
| Resources | High | Low |
| Integration | Comprehensive | Simple |
API Gateway Features
Core Functions
- Routing: Direct requests to correct services
- Authentication: JWT, OAuth2, API Keys
- Authorization: Role-based access
- Rate Limiting: Request limits per client
- Load Balancing: Distribute load
- Caching: Response caching
- Logging: Request/Response logging
Advanced Features
- Circuit Breaking: Protection from failures
- Retry Logic: Automatic retries
- Request/Response Transformation: Data transformation
- API Versioning: Version management
- Analytics: Usage statistics
Monitoring & Observability
Three Pillars of Observability
- Metrics: Quantitative data (Prometheus, Grafana)
- Logging: Event-based data (ELK Stack, Fluentd)
- Tracing: Request tracing (Jaeger, Zipkin)
Tools in Microservice 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: One service, one task
- Database per Service: Separate database per service
- Async Communication: Event-based communication
- Circuit Breaking: Protection from cascade failures
- Health Checks: Monitor service health
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: Independent scaling per service
- Flexibility: Different technologies possible
- Resilience: Failure of one service does not affect others
- Team Autonomy: Independent development teams
- Faster Deployment: Small, frequent deployments
Disadvantages of Microservices
- Complexity: Distributed systems are complex
- Network Latency: Communication over network
- Data Consistency: Distributed transactions
- Monitoring: More complex monitoring
- Debugging: Difficult troubleshooting
Common Exam Questions
-
What are the main differences between monolith and microservices? Monolith is a single unit, microservices are small, independent services with their own responsibility.
-
Explain the role of an API Gateway! API Gateway is the central entry point for client requests with routing, authentication, and rate limiting.
-
When do you use containers and when orchestration? Containers for isolation and portability, orchestration for automated management of many containers.
-
What is Service Mesh and why is it needed? Service Mesh is an infrastructure layer for microservice communication with service discovery and security.
Key Sources
- https://microservices.io/
- https://kubernetes.io/
- https://istio.io/
- https://spring.io/projects/spring-cloud