Facade Pattern

1. Giới Thiệu

1.1 Định Nghĩa

Facade Pattern là một structural pattern cung cấp một interface đơn giản cho một hệ thống con phức tạp. Nó định nghĩa một interface cao cấp giúp client dễ dàng sử dụng hệ thống con.

1.2 Mục Đích

  • Đơn giản hóa interface phức tạp
  • Giảm sự phụ thuộc của client vào các subsystem
  • Cung cấp entry point thống nhất cho một tầng của phần mềm
  • Đóng gói logic nghiệp vụ phức tạp

2. Cấu Trúc Cơ Bản

2.1 Computer System Example

// Subsystem components
public class CPU {
    public void freeze() {
        System.out.println("CPU: Freezing...");
    }

    public void jump(long position) {
        System.out.println("CPU: Jumping to position " + 
            position);
    }

    public void execute() {
        System.out.println("CPU: Executing...");
    }
}

public class Memory {
    public void load(long position, String data) {
        System.out.println("Memory: Loading data at " + 
            position + ": " + data);
    }
}

public class HardDrive {
    public String read(long lba, int size) {
        System.out.println("HardDrive: Reading " + 
            size + " bytes from " + lba);
        return "Data from sector " + lba;
    }
}

// Facade
public class ComputerFacade {
    private CPU cpu;
    private Memory memory;
    private HardDrive hardDrive;

    public ComputerFacade() {
        this.cpu = new CPU();
        this.memory = new Memory();
        this.hardDrive = new HardDrive();
    }

    public void start() {
        cpu.freeze();
        String bootData = hardDrive.read(
            BOOT_SECTOR, SECTOR_SIZE);
        memory.load(BOOT_ADDRESS, bootData);
        cpu.jump(BOOT_ADDRESS);
        cpu.execute();
    }

    private static final long BOOT_ADDRESS = 0x0;
    private static final long BOOT_SECTOR = 0x0;
    private static final int SECTOR_SIZE = 512;
}

// Client
public class Client {
    public static void main(String[] args) {
        ComputerFacade computer = new ComputerFacade();
        computer.start();
    }
}

3. Ví Dụ Thực Tế

3.1 Payment Processing System

// Subsystem components
public class PaymentValidator {
    public boolean validate(Payment payment) {
        // Validation logic
        return true;
    }
}

public class FraudDetector {
    public boolean checkFraud(Payment payment) {
        // Fraud detection logic
        return false;
    }
}

public class PaymentProcessor {
    public void process(Payment payment) {
        // Processing logic
    }
}

public class NotificationService {
    public void notify(String message) {
        // Notification logic
    }
}

// Facade
@Service
public class PaymentFacade {
    private final PaymentValidator validator;
    private final FraudDetector fraudDetector;
    private final PaymentProcessor processor;
    private final NotificationService notifier;

    public PaymentFacade(
            PaymentValidator validator,
            FraudDetector fraudDetector,
            PaymentProcessor processor,
            NotificationService notifier) {
        this.validator = validator;
        this.fraudDetector = fraudDetector;
        this.processor = processor;
        this.notifier = notifier;
    }

    public PaymentResult processPayment(Payment payment) {
        try {
            // Validate payment
            if (!validator.validate(payment)) {
                return PaymentResult.invalid(
                    "Invalid payment details");
            }

            // Check for fraud
            if (fraudDetector.checkFraud(payment)) {
                return PaymentResult.fraud(
                    "Suspicious activity detected");
            }

            // Process payment
            processor.process(payment);

            // Notify success
            notifier.notify("Payment processed successfully");

            return PaymentResult.success();
        } catch (Exception e) {
            notifier.notify("Payment processing failed");
            return PaymentResult.error(e.getMessage());
        }
    }
}

3.2 Order Management System

// Subsystem components
public class InventoryService {
    public boolean checkStock(String productId, int quantity) {
        // Check stock logic
        return true;
    }

    public void updateStock(String productId, int quantity) {
        // Update stock logic
    }
}

public class PricingService {
    public BigDecimal calculatePrice(
            String productId, int quantity) {
        // Price calculation logic
        return BigDecimal.TEN;
    }
}

public class ShippingService {
    public ShippingDetails arrangeShipping(Order order) {
        // Shipping arrangement logic
        return new ShippingDetails();
    }
}

public class PaymentService {
    public boolean processPayment(
            Order order, BigDecimal amount) {
        // Payment processing logic
        return true;
    }
}

// Facade
@Service
public class OrderFacade {
    private final InventoryService inventoryService;
    private final PricingService pricingService;
    private final ShippingService shippingService;
    private final PaymentService paymentService;

    public OrderFacade(
            InventoryService inventoryService,
            PricingService pricingService,
            ShippingService shippingService,
            PaymentService paymentService) {
        this.inventoryService = inventoryService;
        this.pricingService = pricingService;
        this.shippingService = shippingService;
        this.paymentService = paymentService;
    }

    public OrderResult placeOrder(Order order) {
        // Check inventory
        if (!checkInventory(order)) {
            return OrderResult.outOfStock();
        }

        // Calculate total price
        BigDecimal totalPrice = calculateTotalPrice(order);

        // Process payment
        if (!paymentService.processPayment(
                order, totalPrice)) {
            return OrderResult.paymentFailed();
        }

        // Update inventory
        updateInventory(order);

        // Arrange shipping
        ShippingDetails shipping = 
            shippingService.arrangeShipping(order);

        return OrderResult.success(shipping);
    }

    private boolean checkInventory(Order order) {
        return order.getItems().stream()
            .allMatch(item -> 
                inventoryService.checkStock(
                    item.getProductId(), 
                    item.getQuantity()));
    }

    private BigDecimal calculateTotalPrice(Order order) {
        return order.getItems().stream()
            .map(item -> pricingService.calculatePrice(
                item.getProductId(), 
                item.getQuantity()))
            .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    private void updateInventory(Order order) {
        order.getItems().forEach(item -> 
            inventoryService.updateStock(
                item.getProductId(), 
                -item.getQuantity()));
    }
}

4. Spring Framework Integration

4.1 Configuration

@Configuration
public class FacadeConfig {
    @Bean
    public PaymentFacade paymentFacade(
            PaymentValidator validator,
            FraudDetector fraudDetector,
            PaymentProcessor processor,
            NotificationService notifier) {
        return new PaymentFacade(
            validator, fraudDetector, 
            processor, notifier);
    }

    @Bean
    public OrderFacade orderFacade(
            InventoryService inventoryService,
            PricingService pricingService,
            ShippingService shippingService,
            PaymentService paymentService) {
        return new OrderFacade(
            inventoryService, pricingService,
            shippingService, paymentService);
    }
}

4.2 REST Controller

@RestController
@RequestMapping("/api/orders")
public class OrderController {
    private final OrderFacade orderFacade;

    public OrderController(OrderFacade orderFacade) {
        this.orderFacade = orderFacade;
    }

    @PostMapping
    public ResponseEntity<OrderResult> placeOrder(
            @RequestBody Order order) {
        OrderResult result = orderFacade.placeOrder(order);

        if (result.isSuccess()) {
            return ResponseEntity.ok(result);
        } else {
            return ResponseEntity
                .badRequest()
                .body(result);
        }
    }
}

5. Testing Facade Pattern

5.1 Unit Testing

@ExtendWith(MockitoExtension.class)
class OrderFacadeTest {
    @Mock
    private InventoryService inventoryService;

    @Mock
    private PricingService pricingService;

    @Mock
    private ShippingService shippingService;

    @Mock
    private PaymentService paymentService;

    @InjectMocks
    private OrderFacade orderFacade;

    @Test
    void whenPlaceOrder_thenSuccess() {
        // Given
        Order order = createTestOrder();
        when(inventoryService.checkStock(
            anyString(), anyInt()))
            .thenReturn(true);
        when(pricingService.calculatePrice(
            anyString(), anyInt()))
            .thenReturn(BigDecimal.TEN);
        when(paymentService.processPayment(
            any(), any()))
            .thenReturn(true);
        when(shippingService.arrangeShipping(any()))
            .thenReturn(new ShippingDetails());

        // When
        OrderResult result = orderFacade.placeOrder(order);

        // Then
        assertTrue(result.isSuccess());
        verify(inventoryService).updateStock(
            anyString(), anyInt());
    }

    @Test
    void whenOutOfStock_thenFailure() {
        // Given
        Order order = createTestOrder();
        when(inventoryService.checkStock(
            anyString(), anyInt()))
            .thenReturn(false);

        // When
        OrderResult result = orderFacade.placeOrder(order);

        // Then
        assertFalse(result.isSuccess());
        assertEquals("Out of stock", result.getMessage());
    }
}

5.2 Integration Testing

@SpringBootTest
class OrderFacadeIntegrationTest {
    @Autowired
    private OrderFacade orderFacade;

    @Test
    void whenPlaceOrder_thenSuccess() {
        // Given
        Order order = createTestOrder();

        // When
        OrderResult result = orderFacade.placeOrder(order);

        // Then
        assertTrue(result.isSuccess());
        assertNotNull(result.getShippingDetails());
    }
}

6. Lợi Ích và Nhược Điểm

6.1 Lợi Ích

  1. Giảm sự phức tạp cho client
  2. Giảm sự phụ thuộc giữa client và subsystem
  3. Tăng tính bảo mật và kiểm soát
  4. Dễ dàng thay đổi subsystem
  5. Cải thiện khả năng bảo trì

6.2 Nhược Điểm

  1. Có thể trở thành God object
  2. Khó mở rộng khi có nhiều subsystem
  3. Có thể vi phạm Single Responsibility Principle
  4. Khó tái sử dụng các thành phần riêng lẻ

7. Best Practices

7.1 Interface Segregation

public interface OrderProcessing {
    OrderResult placeOrder(Order order);
}

public interface OrderManagement {
    Order getOrder(String orderId);
    void cancelOrder(String orderId);
}

public interface OrderReporting {
    List<Order> getOrderHistory(String customerId);
    OrderStatistics getStatistics(DateRange range);
}

@Service
public class OrderFacade implements 
        OrderProcessing, 
        OrderManagement,
        OrderReporting {
    // Implementation
}

7.2 Builder Pattern Integration

public class OrderFacadeBuilder {
    private InventoryService inventoryService;
    private PricingService pricingService;
    private ShippingService shippingService;
    private PaymentService paymentService;

    public OrderFacadeBuilder withInventoryService(
            InventoryService service) {
        this.inventoryService = service;
        return this;
    }

    public OrderFacadeBuilder withPricingService(
            PricingService service) {
        this.pricingService = service;
        return this;
    }

    public OrderFacadeBuilder withShippingService(
            ShippingService service) {
        this.shippingService = service;
        return this;
    }

    public OrderFacadeBuilder withPaymentService(
            PaymentService service) {
        this.paymentService = service;
        return this;
    }

    public OrderFacade build() {
        validateServices();
        return new OrderFacade(
            inventoryService,
            pricingService,
            shippingService,
            paymentService);
    }

    private void validateServices() {
        if (inventoryService == null || 
            pricingService == null ||
            shippingService == null || 
            paymentService == null) {
            throw new IllegalStateException(
                "All services must be provided");
        }
    }
}

8. Common Issues và Solutions

8.1 Error Handling

public class OrderFacade {
    private final ErrorHandler errorHandler;

    public OrderResult placeOrder(Order order) {
        try {
            // Normal order processing
            return processOrder(order);
        } catch (InventoryException e) {
            return errorHandler.handleInventoryError(e);
        } catch (PaymentException e) {
            return errorHandler.handlePaymentError(e);
        } catch (ShippingException e) {
            return errorHandler.handleShippingError(e);
        } catch (Exception e) {
            return errorHandler.handleUnexpectedError(e);
        }
    }
}

@Component
public class ErrorHandler {
    private final NotificationService notifier;
    private final Logger logger;

    public OrderResult handleInventoryError(
            InventoryException e) {
        logger.error("Inventory error", e);
        notifier.notifyAdmin(
            "Inventory error: " + e.getMessage());
        return OrderResult.error(
            "Product not available");
    }

    public OrderResult handlePaymentError(
            PaymentException e) {
        logger.error("Payment error", e);
        return OrderResult.error(
            "Payment processing failed");
    }

    // Other error handling methods
}

8.2 Performance Optimization

public class CachingOrderFacade extends OrderFacade {
    private final Cache<String, BigDecimal> priceCache;
    private final Cache<String, Boolean> stockCache;

    public CachingOrderFacade(
            InventoryService inventoryService,
            PricingService pricingService,
            ShippingService shippingService,
            PaymentService paymentService) {
        super(inventoryService, pricingService,
            shippingService, paymentService);

        this.priceCache = Caffeine.newBuilder()
            .expireAfterWrite(Duration.ofMinutes(5))
            .maximumSize(1000)
            .build();

        this.stockCache = Caffeine.newBuilder()
            .expireAfterWrite(Duration.ofSeconds(30))
            .maximumSize(1000)
            .build();
    }

    @Override
    protected BigDecimal calculateTotalPrice(Order order) {
        return order.getItems().stream()
            .map(item -> getPriceFromCache(
                item.getProductId(), 
                item.getQuantity()))
            .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    private BigDecimal getPriceFromCache(
            String productId, int quantity) {
        String cacheKey = productId + ":" + quantity;
        return priceCache.get(cacheKey, key -> 
            super.getPricingService().calculatePrice(
                productId, quantity));
    }
}

9. References

9.1 Books

  • Design Patterns: Elements of Reusable Object-Oriented Software
  • Head First Design Patterns
  • Clean Architecture by Robert C. Martin
  • Patterns of Enterprise Application Architecture

9.2 Online Resources