Scalability Patterns trong Java

1. Horizontal Scaling

1.1 Load Balancing

@Configuration
public class LoadBalancerConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

@Service
public class ServiceDiscoveryClient {
    private final RestTemplate restTemplate;

    public Response callService(String serviceId, String path) {
        return restTemplate.getForObject(
            "http://" + serviceId + path,
            Response.class
        );
    }
}

1.2 Session Management

@Configuration
@EnableRedisHttpSession
public class SessionConfig {
    @Bean
    public LettuceConnectionFactory connectionFactory() {
        return new LettuceConnectionFactory();
    }
}

@RestController
@SessionAttributes("user")
public class UserController {
    @GetMapping("/api/session")
    public User getUserFromSession(
            @ModelAttribute("user") User user) {
        return user;
    }
}

2. Database Scaling

2.1 Read Replicas

@Configuration
public class DataSourceConfig {
    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource.write")
    public DataSource writeDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.read")
    public DataSource readDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public AbstractRoutingDataSource routingDataSource(
            @Qualifier("writeDataSource") DataSource writeDataSource,
            @Qualifier("readDataSource") DataSource readDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.WRITE, writeDataSource);
        targetDataSources.put(DataSourceType.READ, readDataSource);

        AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() {
            @Override
            protected Object determineCurrentLookupKey() {
                return TransactionSynchronizationManager.isCurrentTransactionReadOnly()
                    ? DataSourceType.READ
                    : DataSourceType.WRITE;
            }
        };
        routingDataSource.setTargetDataSources(targetDataSources);
        routingDataSource.setDefaultTargetDataSource(writeDataSource);

        return routingDataSource;
    }
}

2.2 Database Sharding

@Configuration
public class ShardingConfig {
    @Bean
    public ShardingDataSource shardingDataSource() {
        Map<String, DataSource> dataSourceMap = createDataSourceMap();
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();

        // Configure table rules
        TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration();
        orderTableRuleConfig.setLogicTable("orders");
        orderTableRuleConfig.setActualDataNodes("ds${0..1}.orders_${0..1}");

        // Configure sharding strategy
        orderTableRuleConfig.setTableShardingStrategyConfig(
            new StandardShardingStrategyConfiguration(
                "order_id",
                new PreciseOrderShardingAlgorithm()
            )
        );

        shardingRuleConfig.getTableRuleConfigs().add(orderTableRuleConfig);
        return new ShardingDataSource(dataSourceMap, shardingRuleConfig);
    }
}

public class PreciseOrderShardingAlgorithm 
    implements PreciseShardingAlgorithm<Long> {

    @Override
    public String doSharding(
            Collection<String> availableTargetNames, 
            PreciseShardingValue<Long> shardingValue) {
        for (String each : availableTargetNames) {
            if (each.endsWith(shardingValue.getValue() % 2 + "")) {
                return each;
            }
        }
        throw new UnsupportedOperationException();
    }
}

3. Caching Strategies

3.1 Multi-level Caching

@Configuration
@EnableCaching
public class CacheConfig {
    @Bean
    public CacheManager cacheManager() {
        CompositeCacheManager compositeCacheManager = new CompositeCacheManager();

        // First level: Caffeine (Local Cache)
        CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
        caffeineCacheManager.setCaffeine(Caffeine.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .maximumSize(100));

        // Second level: Redis (Distributed Cache)
        RedisCacheManager redisCacheManager = RedisCacheManager
            .builder(redisConnectionFactory())
            .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(1)))
            .build();

        compositeCacheManager.setCacheManagers(Arrays.asList(
            caffeineCacheManager,
            redisCacheManager
        ));

        return compositeCacheManager;
    }
}

@Service
public class UserService {
    @Cacheable(value = "users", unless = "#result == null")
    public User getUser(Long id) {
        return userRepository.findById(id).orElse(null);
    }

    @CacheEvict(value = "users", key = "#user.id")
    public User updateUser(User user) {
        return userRepository.save(user);
    }
}

3.2 Cache Synchronization

@Service
public class CacheEventHandler {
    private final CacheManager cacheManager;

    @KafkaListener(topics = "cache-events")
    public void handleCacheEvent(CacheEvent event) {
        switch (event.getType()) {
            case EVICT:
                cacheManager.getCache(event.getCacheName())
                    .evict(event.getKey());
                break;
            case UPDATE:
                cacheManager.getCache(event.getCacheName())
                    .put(event.getKey(), event.getValue());
                break;
        }
    }
}

4. Asynchronous Processing

4.1 Message Queues

@Configuration
public class AsyncConfig {
    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(500);
        executor.setThreadNamePrefix("async-");
        executor.initialize();
        return executor;
    }
}

@Service
public class OrderProcessor {
    @Async
    public CompletableFuture<Order> processOrder(Order order) {
        return CompletableFuture.supplyAsync(() -> {
            // Process order asynchronously
            return processOrderDetails(order);
        }).thenApply(processedOrder -> {
            // Post-processing
            return finalizeOrder(processedOrder);
        });
    }
}

4.2 Event Streaming

@Configuration
public class KafkaStreamConfig {
    @Bean
    public StreamsBuilder streamsBuilder() {
        StreamsBuilder builder = new StreamsBuilder();

        builder.stream("orders")
            .groupByKey()
            .windowedBy(TimeWindows.of(Duration.ofMinutes(5)))
            .aggregate(
                () -> new OrderAggregate(),
                (key, value, aggregate) -> aggregate.add(value),
                Materialized.as("order-stats")
            );

        return builder;
    }
}

5. Microservices Scaling

5.1 Service Discovery

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

@SpringBootApplication
@EnableDiscoveryClient
public class MicroserviceApplication {
    @Bean
    @LoadBalanced
    public WebClient.Builder loadBalancedWebClientBuilder() {
        return WebClient.builder();
    }
}

5.2 Circuit Breaker

@Configuration
public class ResilienceConfig {
    @Bean
    public CircuitBreakerConfig circuitBreakerConfig() {
        return CircuitBreakerConfig.custom()
            .failureRateThreshold(50)
            .waitDurationInOpenState(Duration.ofMillis(1000))
            .slidingWindowSize(2)
            .build();
    }

    @Bean
    public TimeLimiterConfig timeLimiterConfig() {
        return TimeLimiterConfig.custom()
            .timeoutDuration(Duration.ofSeconds(2))
            .build();
    }
}

@Service
public class OrderService {
    private final CircuitBreaker circuitBreaker;
    private final TimeLimiter timeLimiter;

    public Order getOrder(Long id) {
        return circuitBreaker.executeSupplier(() ->
            timeLimiter.executeFutureSupplier(() ->
                CompletableFuture.supplyAsync(() ->
                    orderRepository.findById(id)
                        .orElseThrow(() -> new OrderNotFoundException(id))
                )
            )
        );
    }
}

6. API Gateway Scaling

6.1 Rate Limiting

@Configuration
public class RateLimitConfig {
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest()
                .getHeaders()
                .getFirst("X-API-Key")
        );
    }

    @Bean
    public RateLimiter rateLimiter() {
        return RedisRateLimiter.builder()
            .replenishRate(10)
            .burstCapacity(20)
            .build();
    }
}

@Configuration
public class GatewayConfig {
    @Bean
    public RouteLocator customRouteLocator(
            RouteLocatorBuilder builder,
            RateLimiter rateLimiter) {
        return builder.routes()
            .route("rate_limited_route", r -> r
                .path("/api/**")
                .filters(f -> f.requestRateLimiter()
                    .rateLimiter(rateLimiter)
                    .configure(c -> c.setKeyResolver(userKeyResolver())))
                .uri("lb://api-service"))
            .build();
    }
}

6.2 Request Buffering

@Configuration
public class WebFluxConfig {
    @Bean
    public WebClient webClient() {
        return WebClient.builder()
            .filter(new BufferingWebFilter())
            .build();
    }
}

public class BufferingWebFilter implements ExchangeFilterFunction {
    @Override
    public Mono<ClientResponse> filter(
            ClientRequest request, 
            ExchangeFunction next) {
        return next.exchange(request)
            .map(response -> response.mutate()
                .body(response.body(BodyExtractors.toDataBuffers())
                    .buffer()
                    .map(dataBuffer -> {
                        // Buffer and process response
                        return dataBuffer;
                    }))
                .build());
    }
}

7. Monitoring và Metrics

7.1 Performance Metrics

@Configuration
public class MetricsConfig {
    @Bean
    MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
        return registry -> registry.config()
            .commonTags("application", "scalability-demo");
    }
}

@Component
public class PerformanceMonitor {
    private final MeterRegistry registry;

    public void recordLatency(String operation, long duration) {
        registry.timer("operation.latency",
            "operation", operation)
            .record(duration, TimeUnit.MILLISECONDS);
    }

    public void incrementCounter(String operation) {
        registry.counter("operation.count",
            "operation", operation)
            .increment();
    }
}

7.2 Health Checks

@Component
public class CustomHealthIndicator extends AbstractHealthIndicator {
    @Override
    protected void doHealthCheck(Builder builder) throws Exception {
        try {
            // Perform health check
            Map<String, Object> details = new HashMap<>();
            details.put("version", "1.0.0");
            details.put("timestamp", System.currentTimeMillis());

            builder.up()
                .withDetails(details);
        } catch (Exception e) {
            builder.down()
                .withException(e);
        }
    }
}

8. Best Practices

8.1 Design Guidelines

  • Stateless services
  • Eventual consistency
  • Fail fast and independently
  • Cache strategically
  • Monitor and alert
  • Automated scaling
  • Data partitioning
  • Asynchronous processing

8.2 Implementation Guidelines

  • Use connection pooling
  • Implement retry mechanisms
  • Handle partial failures
  • Use appropriate serialization
  • Implement proper logging
  • Use appropriate timeouts
  • Implement circuit breakers
  • Use appropriate caching strategies

9. References

  • Java Concurrency in Practice by Brian Goetz
  • Designing Data-Intensive Applications by Martin Kleppmann
  • Building Microservices by Sam Newman
  • Cloud Native Java by Josh Long & Kenny Bastani