📖 Java Spring Boot Toàn tập - Microservices với Spring Cloud
90 phút

Microservices với Spring Cloud

Microservices Architecture

Ưu điểm

  • Independent deployment: Mỗi service deploy riêng
  • Technology diversity: Có thể dùng tech stack khác nhau
  • Scalability: Scale từng service độc lập
  • Fault isolation: Lỗi 1 service không ảnh hưởng toàn hệ thống

Nhược điểm

  • Complexity: Phức tạp hơn monolith
  • Network latency: Giao tiếp qua network
  • Distributed transactions: Khó đảm bảo consistency
  • Testing: Khó test integration

Service Discovery với Eureka

Eureka Server

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

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false

Eureka Client

@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}
spring:
  application:
    name: user-service

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka

API Gateway

Spring Cloud Gateway

@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}
server:
  port: 8080

spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1

        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**
          filters:
            - StripPrefix=1

Custom Filter

@Component
public class AuthGatewayFilter implements GlobalFilter, Ordered {

    private final JwtService jwtService;

    public AuthGatewayFilter(JwtService jwtService) {
        this.jwtService = jwtService;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getPath().value();

        if (path.startsWith("/api/auth/")) {
            return chain.filter(exchange);
        }

        String authHeader = exchange.getRequest().getHeaders()
            .getFirst(HttpHeaders.AUTHORIZATION);

        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }

        try {
            String token = authHeader.substring(7);
            String username = jwtService.extractUsername(token);
            exchange.getRequest().mutate()
                .header("X-Auth-User", username)
                .build();
        } catch (Exception e) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }

        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

Inter-Service Communication

OpenFeign Client

@FeignClient(name = "user-service", path = "/api/users")
public interface UserClient {

    @GetMapping("/{id}")
    UserResponse getUser(@PathVariable Long id);

    @PostMapping
    UserResponse createUser(@RequestBody CreateUserRequest request);
}

Feign với fallback

@FeignClient(
    name = "user-service",
    fallbackFactory = UserClientFallback.class
)
public interface UserClient {
    @GetMapping("/{id}")
    UserResponse getUser(@PathVariable Long id);
}

@Component
public class UserClientFallback implements FallbackFactory<UserClient> {
    @Override
    public UserClient create(Throwable cause) {
        return id -> {
            log.warn("Fallback for getUser({}): {}", id, cause.getMessage());
            return new UserResponse(id, "Unknown", null, null, null);
        };
    }
}

Circuit Breaker với Resilience4j

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
</dependency>
@Service
@RequiredArgsConstructor
public class OrderService {

    private final UserClient userClient;

    @CircuitBreaker(name = "userService", fallbackMethod = "fallback")
    @Retry(name = "userService")
    @TimeLimiter(name = "userService")
    public CompletableFuture<OrderResponse> createOrder(Long userId, OrderRequest req) {
        return CompletableFuture.supplyAsync(() -> {
            UserResponse user = userClient.getUser(userId);
            // process order
            return new OrderResponse(user.id(), req);
        });
    }

    private CompletableFuture<OrderResponse> fallback(
        Long userId, OrderRequest req, Throwable t
    ) {
        log.error("Circuit breaker fallback", t);
        return CompletableFuture.failedFuture(
            new BusinessException("User service unavailable"));
    }
}

Configuration

resilience4j:
  circuitbreaker:
    instances:
      userService:
        registerHealthIndicator: true
        slidingWindowSize: 10
        minimumNumberOfCalls: 5
        permittedNumberOfCallsInHalfOpenState: 3
        automaticTransitionFromOpenToHalfOpenEnabled: true
        waitDurationInOpenState: 10s
        failureRateThreshold: 50
  retry:
    instances:
      userService:
        maxAttempts: 3
        waitDuration: 1s
  timelimiter:
    instances:
      userService:
        timeoutDuration: 3s

Distributed Tracing

Micrometer Tracing + Zipkin

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-reporter-brave</artifactId>
</dependency>
management:
  tracing:
    sampling:
      probability: 1.0
  zipkin:
    tracing:
      endpoint: http://localhost:9411/api/v2/spans

Configuration Server

Config Server

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}
server:
  port: 8888

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/myorg/config-repo
          default-label: main

Message Queue với Kafka

Producer

@Service
@RequiredArgsConstructor
public class OrderEventPublisher {

    private final KafkaTemplate<String, OrderEvent> kafkaTemplate;

    public void publishOrderCreated(OrderEvent event) {
        kafkaTemplate.send("order-events", event.orderId(), event)
            .whenComplete((result, ex) -> {
                if (ex != null) {
                    log.error("Failed to publish event", ex);
                }
            });
    }
}

Consumer

@Service
@Slf4j
public class OrderEventConsumer {

    @KafkaListener(topics = "order-events", groupId = "notification-service")
    public void handleOrderCreated(OrderEvent event) {
        log.info("Received order event: {}", event);
        // send notification
    }
}

Docker Compose cho microservices

version: '3.8'
services:
  eureka:
    build: ./eureka-server
    ports:
      - "8761:8761"

  config-server:
    build: ./config-server
    ports:
      - "8888:8888"
    depends_on:
      - eureka

  gateway:
    build: ./gateway
    ports:
      - "8080:8080"
    depends_on:
      - eureka

  user-service:
    build: ./user-service
    depends_on:
      - eureka
      - postgres

  order-service:
    build: ./order-service
    depends_on:
      - eureka
      - postgres
      - kafka

  postgres:
    image: postgres:15
    environment:
      POSTGRES_DB: microservices
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres

  kafka:
    image: confluentinc/cp-kafka:latest
    environment:
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181

  zipkin:
    image: openzipkin/zipkin:latest
    ports:
      - "9411:9411"

Bài tập thực hành

Hãy xây dựng microservices architecture hoàn chỉnh!

📝 Bài tập (1)

  1. Thiết kế microservices cho e-commerce