Dlaczego Mediator Pattern jest ważny?
W zespołach programistycznych często spotykamy się z problemem „spaghetti code” – sytuacją gdzie klasy są ściśle powiązane i zmiany w jednej z nich wymuszają modyfikacje w wielu innych. Mediator Pattern rozwiązuje ten problem poprzez wprowadzenie centralnego „pośrednika” który zarządza komunikacją między komponentami.
W kontekście biznesowym oznacza to łatwiejsze wprowadzanie zmian, szybsze testowanie i mniejsze ryzyko wprowadzenia bugów przy rozwijaniu aplikacji.
Co się nauczysz:
- Czym jest Mediator Pattern i kiedy go stosować
- Jak implementować wzorzec w Spring Boot z użyciem ApplicationEventPublisher
- Praktyczne zastosowania w systemach e-commerce i CRM
- Testowanie aplikacji z wzorcem Mediator
- Różnice między Command Pattern a Mediator Pattern
Wymagania wstępne:
- Znajomość Spring Boot i Dependency Injection
- Podstawowa znajomość wzorców projektowych
- Doświadczenie z testowaniem jednostkowym
- Zrozumienie koncepcji loose coupling
Czym jest Mediator Pattern?
Mediator Pattern definiuje sposób w jaki zbiór obiektów współpracuje ze sobą. Zamiast bezpośredniego odwoływania się do innych obiektów, klasy komunikują się poprzez wspólny interfejs – mediator.
Struktura wzorca
Klasyczna implementacja Mediator Pattern składa się z:
- Mediator – interfejs definiujący kontakt między komponentami
- ConcreteMediator – implementacja logiki komunikacji
- Colleague – klasy które komunikują się przez mediatora
Implementacja w Spring Boot
Spring Framework oferuje kilka sposobów implementacji Mediator Pattern. Najpopularniejsze to wykorzystanie ApplicationEventPublisher lub stworzenie dedykowanych klas mediatorów.
Podejście 1: ApplicationEventPublisher
Spring Boot posiada wbudowany mechanizm event-driven programming, który idealnie nadaje się do implementacji wzorca Mediator.
// Event reprezentujący zamówienie public class OrderCreatedEvent { private final Long orderId; private final String customerEmail; private final BigDecimal amount; public OrderCreatedEvent(Long orderId, String customerEmail, BigDecimal amount) { this.orderId = orderId; this.customerEmail = customerEmail; this.amount = amount; } // gettery... } @Service public class OrderService { private final ApplicationEventPublisher eventPublisher; private final OrderRepository orderRepository; public OrderService(ApplicationEventPublisher eventPublisher, OrderRepository orderRepository) { this.eventPublisher = eventPublisher; this.orderRepository = orderRepository; } public void createOrder(OrderRequest request) { Order order = new Order(request.getCustomerEmail(), request.getAmount()); orderRepository.save(order); // Publikujemy event - mediator w akcji eventPublisher.publishEvent( new OrderCreatedEvent(order.getId(), order.getCustomerEmail(), order.getAmount()) ); } }
Teraz różne komponenty mogą reagować na event bez bezpośredniej zależności od OrderService:
@Component public class EmailNotificationService { @EventListener public void handleOrderCreated(OrderCreatedEvent event) { // Wysyłamy email potwierdzający sendOrderConfirmationEmail(event.getCustomerEmail(), event.getOrderId()); } } @Component public class InventoryService { @EventListener public void handleOrderCreated(OrderCreatedEvent event) { // Aktualizujemy stan magazynu updateInventoryAfterOrder(event.getOrderId()); } } @Component public class AnalyticsService { @EventListener public void handleOrderCreated(OrderCreatedEvent event) { // Zapisujemy dane do analityki trackOrderCreated(event.getAmount()); } }
Podejście 2: Dedykowany Mediator
Dla bardziej złożonych przypadków możemy stworzyć dedykowaną klasę mediatora:
public interface OrderMediator { void processOrder(OrderRequest request); } @Service public class OrderMediatorImpl implements OrderMediator { private final OrderService orderService; private final EmailService emailService; private final PaymentService paymentService; private final InventoryService inventoryService; public OrderMediatorImpl(OrderService orderService, EmailService emailService, PaymentService paymentService, InventoryService inventoryService) { this.orderService = orderService; this.emailService = emailService; this.paymentService = paymentService; this.inventoryService = inventoryService; } @Override @Transactional public void processOrder(OrderRequest request) { try { // Krok 1: Sprawdź dostępność w magazynie inventoryService.checkAvailability(request.getProductId(), request.getQuantity()); // Krok 2: Przetworz płatność PaymentResult payment = paymentService.processPayment(request.getPaymentDetails()); // Krok 3: Utwórz zamówienie Order order = orderService.createOrder(request, payment.getTransactionId()); // Krok 4: Wyślij email emailService.sendOrderConfirmation(order); // Krok 5: Zaktualizuj magazyn inventoryService.updateStock(request.getProductId(), request.getQuantity()); } catch (Exception e) { // Obsłuż błędy w jednym miejscu handleOrderError(request, e); } } }
Praktyczne zastosowania
System e-commerce
W systemach e-commerce Mediator Pattern świetnie sprawdza się przy obsłudze workflow zamówień:
@Service public class ECommerceMediator { public void processCheckout(CheckoutRequest request) { // Koordynacja wielu usług bez bezpośrednich zależności validateCart(request.getCartId()); applyDiscounts(request.getPromoCodes()); processPayment(request.getPaymentData()); createShipment(request.getShippingAddress()); sendNotifications(request.getCustomerId()); } }
System CRM
W aplikacjach CRM mediator może koordynować działania związane z zarządzaniem klientami:
@Component public class CustomerLifecycleMediator { @EventListener public void handleCustomerRegistered(CustomerRegisteredEvent event) { // Koordynacja akcji dla nowego klienta createCustomerProfile(event.getCustomerId()); assignToSalesperson(event.getCustomerId()); scheduleWelcomeEmail(event.getCustomerId()); triggerOnboardingProcess(event.getCustomerId()); } }
Testowanie z Mediator Pattern
Jedną z największych korzyści wzorca Mediator jest łatwość testowania. Możemy testować komponenty w izolacji:
@ExtendWith(MockitoExtension.class) class OrderMediatorTest { @Mock private OrderService orderService; @Mock private EmailService emailService; @Mock private PaymentService paymentService; @Mock private InventoryService inventoryService; @InjectMocks private OrderMediatorImpl orderMediator; @Test void shouldProcessOrderSuccessfully() { // Given OrderRequest request = new OrderRequest("product-123", 2, "customer@email.com"); when(inventoryService.checkAvailability(anyString(), anyInt())).thenReturn(true); when(paymentService.processPayment(any())).thenReturn(new PaymentResult("tx-123")); // When orderMediator.processOrder(request); // Then verify(inventoryService).checkAvailability("product-123", 2); verify(paymentService).processPayment(any()); verify(orderService).createOrder(eq(request), eq("tx-123")); verify(emailService).sendOrderConfirmation(any()); } }
Mediator vs Command Pattern
Często mylone są wzorce Mediator i Command. Oto kluczowe różnice:
Aspekt | Mediator Pattern | Command Pattern |
---|---|---|
Cel | Redukuje coupling między obiektami | Enkapsuluje żądanie jako obiekt |
Struktura | Centralny mediator + komponenty | Invoker + Command + Receiver |
Użycie | Koordynacja wielu operacji | Parametryzacja operacji |
Spring | ApplicationEventPublisher | @Component + interfaces |
Najlepsze praktyki
Kiedy używać Mediator Pattern:
- Kompleksowe workflow: Proces składa się z wielu kroków wymagających koordynacji
- Loose coupling: Chcesz uniknąć bezpośrednich zależności między komponentami
- Centralna logika: Logika biznesowa ma być w jednym miejscu
- Event-driven architecture: Aplikacja opiera się na reagowaniu na wydarzenia
Kiedy unikać wzorca:
- Proste operacje: Gdy logika jest prosta i nie wymaga koordynacji
- Performance critical: Dodatkowa warstwa może wpłynąć na wydajność
- Małe aplikacje: Overhead może przewyższać korzyści
Performance i monitoring
Przy implementacji Mediator Pattern w środowisku produkcyjnym warto monitorować:
@Service public class MonitoredOrderMediator { private final MeterRegistry meterRegistry; @Timed(value = "order.processing.time", description = "Order processing time") public void processOrder(OrderRequest request) { Timer.Sample sample = Timer.start(meterRegistry); try { // Logika biznesowa doProcessOrder(request); meterRegistry.counter("order.processed.success").increment(); } catch (Exception e) { meterRegistry.counter("order.processed.error").increment(); throw e; } finally { sample.stop(Timer.builder("order.processing.time").register(meterRegistry)); } } }
Mediator może wprowadzić minimalny overhead, ale korzyści z łatwiejszego testowania i maintainability przeważają nad niewielkim spadkiem wydajności. W aplikacjach enterprise różnica jest niezauważalna.
Tak, to dobra praktyka. Lepiej mieć wyspecjalizowanych mediatorów dla różnych domen (OrderMediator, CustomerMediator, PaymentMediator) niż jeden uniwersalny.
Używaj @TestEventListener lub ApplicationEventPublisher w testach. Możesz też użyć CountDownLatch dla synchronizacji testów asynchronicznych.
Nie, możesz też tworzyć dedykowane klasy mediatorów lub używać bibliotek typu MediatR dla .NET (equivalenty dla Java jak Axon Framework).
Używaj @TransactionalEventListener z fazą AFTER_ROLLBACK lub implementuj retry mechanism z Spring Retry.
Nie, Mediator Pattern współpracuje z Service Layer. Mediator koordynuje wywołania między serwisami, ale nie zastępuje logiki biznesowej w serwisach.
Używaj logowania na poziomie DEBUG w mediatorze, Spring Boot Actuator do monitorowania eventów, oraz narzędzi typu Zipkin do distributed tracing.
Przydatne zasoby:
- Spring Framework Reference – Events
- Refactoring Guru – Mediator Pattern
- Spring Blog – Application Events
🚀 Zadanie dla Ciebie
Zaimplementuj system powiadomień dla bloga używając Mediator Pattern. Gdy użytkownik dodaje nowy post, system powinien automatycznie wysłać email do subskrybentów, zaktualizować RSS feed i wygenerować notyfikację push. Użyj Spring Events do koordynacji tych działań.
Mediator Pattern to potężne narzędzie do tworzenia loosely coupled aplikacji. W Spring Boot mamy do dyspozycji gotowe mechanizmy które ułatwiają implementację tego wzorca. Czy używasz już event-driven architecture w swoich projektach? Podziel się swoimi doświadczeniami w komentarzach!