Microservices Architecture - Câu hỏi phỏng vấn Senior Engineer

1. Microservices Design Patterns

Câu hỏi:

"Hãy giải thích các design patterns chính trong microservices architecture và implement Service Discovery pattern."

Câu trả lời:

Core Microservices Patterns: - Service Discovery: Tự động tìm và register services - Circuit Breaker: Prevent cascading failures - API Gateway: Single entry point - Saga Pattern: Distributed transactions - CQRS: Command Query Responsibility Segregation

Ví dụ Service Discovery Implementation:

// 1. Service Registry Interface
public interface ServiceRegistry {
    void register(ServiceInstance instance);
    void deregister(String serviceId, String instanceId);
    List<ServiceInstance> getInstances(String serviceId);
    ServiceInstance getInstance(String serviceId);
}

// 2. Service Instance Model
@Data
@Builder
public class ServiceInstance {
    private String serviceId;
    private String instanceId;
    private String host;
    private int port;
    private boolean secure;
    private Map<String, String> metadata;
    private HealthStatus healthStatus;
    private LocalDateTime registrationTime;
    private LocalDateTime lastHeartbeat;

    public String getUri() {
        return (secure ? "https" : "http") + "://" + host + ":" + port;
    }
}

// 3. In-Memory Service Registry Implementation
@Component
public class InMemoryServiceRegistry implements ServiceRegistry {

    private final Map<String, List<ServiceInstance>> serviceInstances = new ConcurrentHashMap<>();
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);

    @PostConstruct
    public void init() {
        // Health check scheduler
        scheduler.scheduleAtFixedRate(this::performHealthChecks, 30, 30, TimeUnit.SECONDS);

        // Cleanup scheduler
        scheduler.scheduleAtFixedRate(this::cleanupStaleInstances, 60, 60, TimeUnit.SECONDS);
    }

    @Override
    public void register(ServiceInstance instance) {
        serviceInstances.computeIfAbsent(instance.getServiceId(), k -> new ArrayList<>())
                .add(instance);

        log.info("Registered service instance: {}", instance);
    }

    @Override
    public void deregister(String serviceId, String instanceId) {
        List<ServiceInstance> instances = serviceInstances.get(serviceId);
        if (instances != null) {
            instances.removeIf(instance -> instance.getInstanceId().equals(instanceId));
            log.info("Deregistered service instance: {}/{}", serviceId, instanceId);
        }
    }

    @Override
    public List<ServiceInstance> getInstances(String serviceId) {
        return serviceInstances.getOrDefault(serviceId, Collections.emptyList())
                .stream()
                .filter(instance -> instance.getHealthStatus() == HealthStatus.UP)
                .collect(Collectors.toList());
    }

    @Override
    public ServiceInstance getInstance(String serviceId) {
        List<ServiceInstance> instances = getInstances(serviceId);
        if (instances.isEmpty()) {
            throw new ServiceNotFoundException("No healthy instances found for service: " + serviceId);
        }

        // Simple round-robin load balancing
        return instances.get(ThreadLocalRandom.current().nextInt(instances.size()));
    }

    private void performHealthChecks() {
        serviceInstances.values().stream()
                .flatMap(List::stream)
                .forEach(this::checkInstanceHealth);
    }

    private void checkInstanceHealth(ServiceInstance instance) {
        try {
            RestTemplate restTemplate = new RestTemplate();
            restTemplate.getForObject(instance.getUri() + "/actuator/health", String.class);

            instance.setHealthStatus(HealthStatus.UP);
            instance.setLastHeartbeat(LocalDateTime.now());

        } catch (Exception ex) {
            instance.setHealthStatus(HealthStatus.DOWN);
            log.warn("Health check failed for {}: {}", instance, ex.getMessage());
        }
    }

    private void cleanupStaleInstances() {
        LocalDateTime cutoff = LocalDateTime.now().minusMinutes(5);

        serviceInstances.values().forEach(instances -> 
                instances.removeIf(instance -> 
                        instance.getLastHeartbeat().isBefore(cutoff)));
    }
}

// 4. Service Discovery Client
@Component
public class ServiceDiscoveryClient {

    private final ServiceRegistry serviceRegistry;
    private final RestTemplate restTemplate;
    private final LoadBalancer loadBalancer;

    public ServiceDiscoveryClient(ServiceRegistry serviceRegistry, 
                                 RestTemplate restTemplate,
                                 LoadBalancer loadBalancer) {
        this.serviceRegistry = serviceRegistry;
        this.restTemplate = restTemplate;
        this.loadBalancer = loadBalancer;
    }

    public <T> T call(String serviceId, String path, Class<T> responseType) {
        ServiceInstance instance = loadBalancer.choose(serviceId);
        String url = instance.getUri() + path;

        try {
            return restTemplate.getForObject(url, responseType);
        } catch (Exception ex) {
            log.error("Failed to call service {}: {}", serviceId, ex.getMessage());
            throw new ServiceCallException("Service call failed", ex);
        }
    }

    public <T> ResponseEntity<T> callForEntity(String serviceId, String path, 
                                              Class<T> responseType) {
        ServiceInstance instance = loadBalancer.choose(serviceId);
        String url = instance.getUri() + path;

        return restTemplate.getForEntity(url, responseType);
    }

    public <T, R> R post(String serviceId, String path, T request, Class<R> responseType) {
        ServiceInstance instance = loadBalancer.choose(serviceId);
        String url = instance.getUri() + path;

        return restTemplate.postForObject(url, request, responseType);
    }
}

// 5. Load Balancer Interface và Implementation
public interface LoadBalancer {
    ServiceInstance choose(String serviceId);
}

@Component
public class RoundRobinLoadBalancer implements LoadBalancer {

    private final ServiceRegistry serviceRegistry;
    private final Map<String, AtomicInteger> counters = new ConcurrentHashMap<>();

    public RoundRobinLoadBalancer(ServiceRegistry serviceRegistry) {
        this.serviceRegistry = serviceRegistry;
    }

    @Override
    public ServiceInstance choose(String serviceId) {
        List<ServiceInstance> instances = serviceRegistry.getInstances(serviceId);
        if (instances.isEmpty()) {
            throw new ServiceNotFoundException("No instances available for service: " + serviceId);
        }

        AtomicInteger counter = counters.computeIfAbsent(serviceId, k -> new AtomicInteger(0));
        int index = counter.getAndIncrement() % instances.size();

        return instances.get(index);
    }
}

// 6. Auto-registration for Spring Boot services
@Component
public class ServiceAutoRegistration {

    private final ServiceRegistry serviceRegistry;

    @Value("${spring.application.name}")
    private String serviceName;

    @Value("${server.port}")
    private int port;

    @Value("${management.server.port:${server.port}}")
    private int managementPort;

    private ServiceInstance currentInstance;

    public ServiceAutoRegistration(ServiceRegistry serviceRegistry) {
        this.serviceRegistry = serviceRegistry;
    }

    @EventListener
    public void onApplicationReady(ApplicationReadyEvent event) {
        registerSelf();
    }

    @PreDestroy
    public void deregisterSelf() {
        if (currentInstance != null) {
            serviceRegistry.deregister(currentInstance.getServiceId(), 
                                     currentInstance.getInstanceId());
        }
    }

    private void registerSelf() {
        try {
            String hostAddress = InetAddress.getLocalHost().getHostAddress();

            currentInstance = ServiceInstance.builder()
                    .serviceId(serviceName)
                    .instanceId(serviceName + "-" + hostAddress + "-" + port)
                    .host(hostAddress)
                    .port(port)
                    .secure(false)
                    .healthStatus(HealthStatus.UP)
                    .registrationTime(LocalDateTime.now())
                    .lastHeartbeat(LocalDateTime.now())
                    .metadata(Map.of(
                            "management.port", String.valueOf(managementPort),
                            "version", getClass().getPackage().getImplementationVersion(),
                            "zone", "default"
                    ))
                    .build();

            serviceRegistry.register(currentInstance);

        } catch (Exception ex) {
            log.error("Failed to register service instance", ex);
        }
    }
}

2. Circuit Breaker Pattern

Câu hỏi:

"Implement Circuit Breaker pattern để prevent cascading failures trong microservices."

Câu trả lời:

Ví dụ Circuit Breaker Implementation:

// 1. Circuit Breaker States
public enum CircuitBreakerState {
    CLOSED,    // Normal operation
    OPEN,      // Failing, reject calls
    HALF_OPEN  // Testing if service recovered
}

// 2. Circuit Breaker Configuration
@Data
@Builder
public class CircuitBreakerConfig {
    @Builder.Default
    private int failureThreshold = 5;           // Failures to open circuit

    @Builder.Default
    private int successThreshold = 3;           // Successes to close circuit

    @Builder.Default
    private Duration timeout = Duration.ofSeconds(60);  // How long to stay open

    @Builder.Default
    private Duration callTimeout = Duration.ofSeconds(5); // Individual call timeout

    @Builder.Default
    private Class<? extends Exception>[] failureExceptions = new Class[]{Exception.class};

    @Builder.Default
    private Class<? extends Exception>[] ignoreExceptions = new Class[]{IllegalArgumentException.class};
}

// 3. Circuit Breaker Implementation
public class CircuitBreaker {

    private final String name;
    private final CircuitBreakerConfig config;
    private final AtomicReference<CircuitBreakerState> state = new AtomicReference<>(CircuitBreakerState.CLOSED);
    private final AtomicInteger failureCount = new AtomicInteger(0);
    private final AtomicInteger successCount = new AtomicInteger(0);
    private final AtomicLong lastFailureTime = new AtomicLong(0);
    private final MeterRegistry meterRegistry;

    // Metrics
    private final Counter successCounter;
    private final Counter failureCounter;
    private final Counter circuitOpenCounter;
    private final Timer callTimer;

    public CircuitBreaker(String name, CircuitBreakerConfig config, MeterRegistry meterRegistry) {
        this.name = name;
        this.config = config;
        this.meterRegistry = meterRegistry;

        // Initialize metrics
        this.successCounter = Counter.builder("circuit_breaker_success")
                .tag("name", name)
                .register(meterRegistry);
        this.failureCounter = Counter.builder("circuit_breaker_failure")
                .tag("name", name)
                .register(meterRegistry);
        this.circuitOpenCounter = Counter.builder("circuit_breaker_open")
                .tag("name", name)
                .register(meterRegistry);
        this.callTimer = Timer.builder("circuit_breaker_call_duration")
                .tag("name", name)
                .register(meterRegistry);
    }

    public <T> T execute(Supplier<T> operation) throws Exception {
        return execute(operation, () -> {
            throw new CircuitBreakerOpenException("Circuit breaker is open for: " + name);
        });
    }

    public <T> T execute(Supplier<T> operation, Supplier<T> fallback) throws Exception {
        if (state.get() == CircuitBreakerState.OPEN) {
            if (shouldAttemptReset()) {
                state.set(CircuitBreakerState.HALF_OPEN);
                log.info("Circuit breaker {} transitioned to HALF_OPEN", name);
            } else {
                circuitOpenCounter.increment();
                return fallback.get();
            }
        }

        return callTimer.recordCallable(() -> {
            try {
                T result = executeWithTimeout(operation);
                onSuccess();
                return result;

            } catch (Exception ex) {
                onFailure(ex);
                throw ex;
            }
        });
    }

    private <T> T executeWithTimeout(Supplier<T> operation) throws Exception {
        CompletableFuture<T> future = CompletableFuture.supplyAsync(operation);

        try {
            return future.get(config.getCallTimeout().toMillis(), TimeUnit.MILLISECONDS);
        } catch (TimeoutException ex) {
            future.cancel(true);
            throw new CallTimeoutException("Operation timed out after " + config.getCallTimeout());
        } catch (ExecutionException ex) {
            Throwable cause = ex.getCause();
            if (cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            } else if (cause instanceof Exception) {
                throw (Exception) cause;
            } else {
                throw new RuntimeException(cause);
            }
        }
    }

    private void onSuccess() {
        successCounter.increment();

        if (state.get() == CircuitBreakerState.HALF_OPEN) {
            int currentSuccessCount = successCount.incrementAndGet();
            if (currentSuccessCount >= config.getSuccessThreshold()) {
                reset();
            }
        } else {
            failureCount.set(0);
        }
    }

    private void onFailure(Exception ex) {
        if (shouldIgnoreException(ex)) {
            return;
        }

        failureCounter.increment();
        lastFailureTime.set(System.currentTimeMillis());

        int currentFailureCount = failureCount.incrementAndGet();

        if (state.get() == CircuitBreakerState.HALF_OPEN) {
            trip();
        } else if (currentFailureCount >= config.getFailureThreshold()) {
            trip();
        }
    }

    private boolean shouldIgnoreException(Exception ex) {
        for (Class<? extends Exception> ignoreException : config.getIgnoreExceptions()) {
            if (ignoreException.isAssignableFrom(ex.getClass())) {
                return true;
            }
        }
        return false;
    }

    private void trip() {
        state.set(CircuitBreakerState.OPEN);
        successCount.set(0);
        log.warn("Circuit breaker {} OPENED due to failures", name);
    }

    private void reset() {
        state.set(CircuitBreakerState.CLOSED);
        failureCount.set(0);
        successCount.set(0);
        log.info("Circuit breaker {} CLOSED - service recovered", name);
    }

    private boolean shouldAttemptReset() {
        return System.currentTimeMillis() - lastFailureTime.get() >= config.getTimeout().toMillis();
    }

    public CircuitBreakerState getState() {
        return state.get();
    }

    public CircuitBreakerMetrics getMetrics() {
        return CircuitBreakerMetrics.builder()
                .name(name)
                .state(state.get())
                .failureCount(failureCount.get())
                .successCount(successCount.get())
                .build();
    }
}

// 4. Circuit Breaker Service
@Service
public class CircuitBreakerService {

    private final Map<String, CircuitBreaker> circuitBreakers = new ConcurrentHashMap<>();
    private final MeterRegistry meterRegistry;

    public CircuitBreakerService(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }

    public CircuitBreaker getCircuitBreaker(String name, CircuitBreakerConfig config) {
        return circuitBreakers.computeIfAbsent(name, 
                key -> new CircuitBreaker(key, config, meterRegistry));
    }

    public CircuitBreaker getCircuitBreaker(String name) {
        return getCircuitBreaker(name, CircuitBreakerConfig.builder().build());
    }

    public Map<String, CircuitBreakerMetrics> getAllMetrics() {
        return circuitBreakers.entrySet().stream()
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        entry -> entry.getValue().getMetrics()
                ));
    }
}

// 5. Service Client với Circuit Breaker
@Service
public class UserServiceClient {

    private final RestTemplate restTemplate;
    private final CircuitBreaker circuitBreaker;
    private final ServiceDiscoveryClient serviceDiscoveryClient;

    public UserServiceClient(RestTemplate restTemplate, 
                           CircuitBreakerService circuitBreakerService,
                           ServiceDiscoveryClient serviceDiscoveryClient) {
        this.restTemplate = restTemplate;
        this.serviceDiscoveryClient = serviceDiscoveryClient;

        CircuitBreakerConfig config = CircuitBreakerConfig.builder()
                .failureThreshold(3)
                .successThreshold(2)
                .timeout(Duration.ofSeconds(30))
                .callTimeout(Duration.ofSeconds(5))
                .build();

        this.circuitBreaker = circuitBreakerService.getCircuitBreaker("user-service", config);
    }

    public User getUserById(Long userId) {
        try {
            return circuitBreaker.execute(
                    () -> serviceDiscoveryClient.call("user-service", 
                            "/api/users/" + userId, User.class),
                    () -> {
                        // Fallback: return cached user or default
                        log.warn("Using fallback for user: {}", userId);
                        return getCachedUser(userId);
                    }
            );
        } catch (Exception ex) {
            log.error("Failed to get user {}: {}", userId, ex.getMessage());
            throw new UserServiceException("Failed to retrieve user", ex);
        }
    }

    public List<User> getAllUsers() {
        try {
            return circuitBreaker.execute(
                    () -> {
                        ResponseEntity<User[]> response = serviceDiscoveryClient
                                .callForEntity("user-service", "/api/users", User[].class);
                        return Arrays.asList(response.getBody());
                    },
                    () -> {
                        // Fallback: return empty list or cached data
                        log.warn("Using fallback for getAllUsers");
                        return getCachedUsers();
                    }
            );
        } catch (Exception ex) {
            log.error("Failed to get all users: {}", ex.getMessage());
            return Collections.emptyList();
        }
    }

    public User createUser(CreateUserRequest request) {
        try {
            return circuitBreaker.execute(
                    () -> serviceDiscoveryClient.post("user-service", 
                            "/api/users", request, User.class)
            );
        } catch (Exception ex) {
            log.error("Failed to create user: {}", ex.getMessage());
            throw new UserServiceException("Failed to create user", ex);
        }
    }

    private User getCachedUser(Long userId) {
        // Implementation của cache lookup
        return User.builder()
                .id(userId)
                .name("Cached User")
                .email("cached@example.com")
                .build();
    }

    private List<User> getCachedUsers() {
        // Implementation của cached users
        return Collections.emptyList();
    }
}

// 6. Health Indicator cho Circuit Breakers
@Component
public class CircuitBreakerHealthIndicator implements HealthIndicator {

    private final CircuitBreakerService circuitBreakerService;

    public CircuitBreakerHealthIndicator(CircuitBreakerService circuitBreakerService) {
        this.circuitBreakerService = circuitBreakerService;
    }

    @Override
    public Health health() {
        Map<String, CircuitBreakerMetrics> metrics = circuitBreakerService.getAllMetrics();

        boolean hasOpenCircuits = metrics.values().stream()
                .anyMatch(metric -> metric.getState() == CircuitBreakerState.OPEN);

        Health.Builder builder = hasOpenCircuits ? Health.down() : Health.up();

        metrics.forEach((name, metric) -> {
            builder.withDetail(name, Map.of(
                    "state", metric.getState(),
                    "failureCount", metric.getFailureCount(),
                    "successCount", metric.getSuccessCount()
            ));
        });

        return builder.build();
    }
}

3. API Gateway Pattern

Câu hỏi:

"Design và implement một API Gateway với authentication, rate limiting, và request routing."

Câu trả lời:

Ví dụ API Gateway Implementation sẽ được tiếp tục...