Els sistemes distribuïts són aquells en els quals components situats en diferents nodes de la xarxa col·laboren per aconseguir un objectiu comú. Aquests sistemes presenten desafiaments únics, com la latència de xarxa, la tolerància a fallades i la consistència de dades. Els patrons de disseny poden ajudar a abordar aquests desafiaments de manera eficient i estructurada.
Objectius d'aquest Tema
- Entendre els desafiaments específics dels sistemes distribuïts.
- Aprendre sobre patrons de disseny específics per a sistemes distribuïts.
- Veure exemples pràctics de com implementar aquests patrons.
- Realitzar exercicis pràctics per reforçar els conceptes apresos.
Desafiaments dels Sistemes Distribuïts
- Latència de Xarxa: La comunicació entre nodes pot ser lenta i imprevisible.
- Tolerància a Fallades: Els nodes poden fallar, i el sistema ha de continuar funcionant.
- Consistència de Dades: Mantenir les dades consistents entre diferents nodes és complex.
- Escalabilitat: El sistema ha de poder manejar un augment en la càrrega de treball.
- Seguretat: La comunicació entre nodes ha de ser segura.
Patrons de Disseny per a Sistemes Distribuïts
- Patró de Proxy Distribuït
Descripció
El patró de Proxy Distribuït permet que un objecte actui com a representant d'un altre objecte situat en un node diferent. Això ajuda a encapsular la complexitat de la comunicació remota.
Exemple de Codi
// Interfície del servei public interface RemoteService { String fetchData(); } // Implementació del servei en el servidor public class RemoteServiceImpl implements RemoteService { @Override public String fetchData() { return "Dades des del servidor"; } } // Proxy que actua com a representant del servei remot public class RemoteServiceProxy implements RemoteService { private RemoteService remoteService; public RemoteServiceProxy(RemoteService remoteService) { this.remoteService = remoteService; } @Override public String fetchData() { // Aquí es podria afegir la lògica de comunicació remota return remoteService.fetchData(); } } // Client que utilitza el proxy public class Client { public static void main(String[] args) { RemoteService service = new RemoteServiceProxy(new RemoteServiceImpl()); System.out.println(service.fetchData()); } }
Explicació
- RemoteService: Interfície que defineix el servei.
- RemoteServiceImpl: Implementació del servei en el servidor.
- RemoteServiceProxy: Proxy que encapsula la comunicació remota.
- Client: Client que utilitza el servei a través del proxy.
- Patró de Consistència Eventual
Descripció
Aquest patró permet que les dades siguin eventualment consistents en tots els nodes, acceptant que poden estar temporalment inconsistents.
Exemple de Codi
// Classe que representa un esdeveniment public class Event { private String data; public Event(String data) { this.data = data; } public String getData() { return data; } } // Classe que maneja la replicació d'esdeveniments public class EventReplicator { private List<Event> eventLog = new ArrayList<>(); public void replicateEvent(Event event) { eventLog.add(event); // Lògica per replicar l'esdeveniment a altres nodes } public List<Event> getEventLog() { return eventLog; } } // Exemple d'ús public class Main { public static void main(String[] args) { EventReplicator replicator = new EventReplicator(); Event event = new Event("Dades noves"); replicator.replicateEvent(event); // Simulació de la consistència eventual System.out.println("Esdeveniments replicats: " + replicator.getEventLog().size()); } }
Explicació
- Event: Classe que representa un esdeveniment que ha de ser replicat.
- EventReplicator: Classe que maneja la replicació d'esdeveniments.
- Main: Exemple d'ús que mostra com replicar un esdeveniment.
- Patró de Circuit Breaker
Descripció
El patró de Circuit Breaker evita que un sistema intenti fer operacions que probablement fallaran, permetent que es recuperi més ràpidament.
Exemple de Codi
// Classe que implementa el patró de Circuit Breaker public class CircuitBreaker { private boolean isOpen = false; private int failureCount = 0; private int failureThreshold = 3; public void callService() throws Exception { if (isOpen) { throw new Exception("Circuit is open. Service call blocked."); } try { // Simulació de la crida al servei boolean success = simulateServiceCall(); if (!success) { failureCount++; if (failureCount >= failureThreshold) { isOpen = true; } } else { failureCount = 0; } } catch (Exception e) { failureCount++; if (failureCount >= failureThreshold) { isOpen = true; } throw e; } } private boolean simulateServiceCall() { // Simulació d'una crida al servei que pot fallar return Math.random() > 0.5; } public void reset() { isOpen = false; failureCount = 0; } } // Exemple d'ús public class Main { public static void main(String[] args) { CircuitBreaker circuitBreaker = new CircuitBreaker(); for (int i = 0; i < 10; i++) { try { circuitBreaker.callService(); System.out.println("Service call successful"); } catch (Exception e) { System.out.println(e.getMessage()); } } } }
Explicació
- CircuitBreaker: Implementació del patró de Circuit Breaker.
- simulateServiceCall: Simulació d'una crida al servei que pot fallar.
- Main: Exemple d'ús que mostra com utilitzar el Circuit Breaker.
Exercicis Pràctics
Exercici 1: Implementar un Proxy Distribuït
Descripció: Implementa un proxy distribuït per a un servei que retorna la data i hora actual des d'un servidor remot.
Solució:
// Interfície del servei public interface DateTimeService { String getCurrentDateTime(); } // Implementació del servei en el servidor public class DateTimeServiceImpl implements DateTimeService { @Override public String getCurrentDateTime() { return LocalDateTime.now().toString(); } } // Proxy que actua com a representant del servei remot public class DateTimeServiceProxy implements DateTimeService { private DateTimeService dateTimeService; public DateTimeServiceProxy(DateTimeService dateTimeService) { this.dateTimeService = dateTimeService; } @Override public String getCurrentDateTime() { // Aquí es podria afegir la lògica de comunicació remota return dateTimeService.getCurrentDateTime(); } } // Client que utilitza el proxy public class Client { public static void main(String[] args) { DateTimeService service = new DateTimeServiceProxy(new DateTimeServiceImpl()); System.out.println(service.getCurrentDateTime()); } }
Exercici 2: Implementar un Circuit Breaker
Descripció: Implementa un Circuit Breaker per a un servei que retorna un missatge de benvinguda. El servei ha de fallar aleatòriament per simular un entorn real.
Solució:
// Classe que implementa el patró de Circuit Breaker public class CircuitBreaker { private boolean isOpen = false; private int failureCount = 0; private int failureThreshold = 3; public void callService() throws Exception { if (isOpen) { throw new Exception("Circuit is open. Service call blocked."); } try { // Simulació de la crida al servei boolean success = simulateServiceCall(); if (!success) { failureCount++; if (failureCount >= failureThreshold) { isOpen = true; } } else { failureCount = 0; } } catch (Exception e) { failureCount++; if (failureCount >= failureThreshold) { isOpen = true; } throw e; } } private boolean simulateServiceCall() { // Simulació d'una crida al servei que pot fallar return Math.random() > 0.5; } public void reset() { isOpen = false; failureCount = 0; } } // Exemple d'ús public class Main { public static void main(String[] args) { CircuitBreaker circuitBreaker = new CircuitBreaker(); for (int i = 0; i < 10; i++) { try { circuitBreaker.callService(); System.out.println("Service call successful"); } catch (Exception e) { System.out.println(e.getMessage()); } } } }
Resum
En aquesta secció, hem explorat diversos patrons de disseny específics per a sistemes distribuïts, com el Proxy Distribuït, la Consistència Eventual i el Circuit Breaker. Aquests patrons ajuden a abordar els desafiaments únics dels sistemes distribuïts, com la latència de xarxa, la tolerància a fallades i la consistència de dades. Hem vist exemples pràctics de com implementar aquests patrons i hem realitzat exercicis per reforçar els conceptes apresos.
En el següent mòdul, explorarem com aplicar aquests patrons en arquitectures modernes com els microserveis i els sistemes distribuïts.
Curs de Patrons de Disseny de Programari
Mòdul 1: Introducció als Patrons de Disseny
- Què són els Patrons de Disseny?
- Història i Origen dels Patrons de Disseny
- Classificació dels Patrons de Disseny
- Avantatges i Desavantatges d'Usar Patrons de Disseny
Mòdul 2: Patrons Creacionals
Mòdul 3: Patrons Estructurals
Mòdul 4: Patrons de Comportament
- Introducció als Patrons de Comportament
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
Mòdul 5: Aplicació de Patrons de Disseny
- Com Seleccionar el Patró Adequat
- Exemples Pràctics d'Ús de Patrons
- Patrons de Disseny en Projectes Reals
- Refactorització Usant Patrons de Disseny
Mòdul 6: Patrons de Disseny Avançats
- Patrons de Disseny en Arquitectures Modernes
- Patrons de Disseny en Microserveis
- Patrons de Disseny en Sistemes Distribuïts
- Patrons de Disseny en Desenvolupament Àgil