Patrones de Diseño Arquitectónico
Plantillas para resolver problemas arquitectónicos comunes en sistemas escalables
Los patrones de diseño arquitectónico proporcionan plantillas para resolver problemas arquitectónicos comunes, facilitando el desarrollo de sistemas escalables, mantenibles y reutilizables.
En este capítulo exploraremos cuatro patrones fundamentales: Model-View-Controller (MVC), Microservices, Serverless y Event Sourcing, junto con otros patrones arquitectónicos relevantes.
Model-View-Controller (MVC)
El patrón MVC es otra aplicación del principio de acoplamiento débil. El nombre del patrón proviene de los tres componentes principales usados para dividir una aplicación de software: el modelo, la vista y el controlador.
Componentes Clave
- Web2py: Framework Python ligero que adopta el patrón MVC
- Django: Framework MVC con nomenclatura diferente (Model-View-Template)
- Restaurante: Los meseros (Controller) comunican entre clientes (View) y cocina (Model)
Implementación: Quote Printer
# Datos (normalmente en BD) quotes = ( "A man is not complete until he is married. Then he is finished.", "As I said before, I never repeat myself.", "Behind a successful man is an exhausted woman.", "Black holes really suck...", "Facts are stubborn things.", ) # MODEL - Lógica y acceso a datos class QuoteModel: def get_quote(self, n): try: value = quotes[n] except IndexError as err: value = "Not found!" return value # VIEW - Presentación al usuario class QuoteTerminalView: def show(self, quote): print(f'And the quote is: "{quote}"') def error(self, msg): print(f"Error: {msg}") def select_quote(self): return input("Which quote number would you like to see? ") # CONTROLLER - Coordinación class QuoteTerminalController: def __init__(self): self.model = QuoteModel() self.view = QuoteTerminalView() def run(self): valid_input = False while not valid_input: try: n = self.view.select_quote() n = int(n) valid_input = True except ValueError as err: self.view.error(f"Incorrect index '{n}'") quote = self.model.get_quote(n) self.view.show(quote) def main(): controller = QuoteTerminalController() while True: controller.run() if __name__ == "__main__": main()
And the quote is: "Black holes really suck..."
Which quote number would you like to see? 6
And the quote is: "Not found!"
Microservices
El patrón de Arquitectura de Microservicios es una de las adiciones principales al catálogo de patrones para ingenieros en años recientes. La idea es construir una aplicación como un conjunto de servicios débilmente acoplados y colaborativos.
- Netflix: Pionero en adoptar microservicios para manejar millones de streams simultáneos
- Uber: Usa microservicios para facturación, notificaciones y tracking de viajes
- Amazon: Transicionó de arquitectura monolítica a microservicios para escalar
Servicio de Pagos con gRPC
syntax = "proto3"; package payment; // Definición del servicio de pagos service PaymentService { rpc ProcessPayment (PaymentRequest) returns (PaymentResponse) {} } message PaymentRequest { string order_id = 1; double amount = 2; string currency = 3; string user_id = 4; } message PaymentResponse { string payment_id = 1; string status = 2; }
Implementación del Servicio
from concurrent.futures import ThreadPoolExecutor import grpc import payment_pb2 import payment_pb2_grpc class PaymentServiceImpl(payment_pb2_grpc.PaymentServiceServicer): def ProcessPayment(self, request, context): return payment_pb2.PaymentResponse( payment_id="12345", status="SUCCESS" ) def main(): print("Payment Processing Service ready!") server = grpc.server(ThreadPoolExecutor(max_workers=10)) payment_pb2_grpc.add_PaymentServiceServicer_to_server( PaymentServiceImpl(), server ) server.add_insecure_port("[::]:50051") server.start() server.wait_for_termination() if __name__ == "__main__": main()
Cliente del Servicio
import grpc import payment_pb2 import payment_pb2_grpc with grpc.insecure_channel("localhost:50051") as chan: stub = payment_pb2_grpc.PaymentServiceStub(chan) resp = stub.ProcessPayment( payment_pb2.PaymentRequest( order_id="order123", amount=99.99, currency="USD", user_id="user456", ) ) print(f"Response status: {resp.status}")
Serverless
El patrón Serverless abstrae la gestión del servidor, permitiendo a los desarrolladores enfocarse únicamente en el código. Los proveedores cloud manejan el escalado y la ejecución basándose en triggers de eventos.
Casos de Uso
AWS Lambda Function
import json def lambda_handler(event, context): number = event["number"] squared = number * number return f"The square of {number} is {squared}."
Despliegue con LocalStack
# Iniciar LocalStack en Docker localstack start -d # Comprimir el código Python zip lambda.zip lambda_function_square.py # Crear la función Lambda awslocal lambda create-function \ --function-name lambda_function_square \ --runtime python3.11 \ --zip-file fileb://lambda.zip \ --handler lambda_function_square.lambda_handler \ --role arn:aws:iam::000000000000:role/lambda-role # Probar la función awslocal lambda invoke --function-name lambda_function_square \ --payload file://payload.json output.txt
Event Sourcing
El patrón Event Sourcing almacena los cambios de estado como una secuencia de eventos, permitiendo la reconstrucción de estados pasados y proporcionando un audit trail.
Casos de Uso
Implementación Manual: Cuenta Bancaria
class Account: def __init__(self): self.balance = 0 self.events = [] # Event Store def apply_event(self, event): if event["type"] == "deposited": self.balance += event["amount"] elif event["type"] == "withdrawn": self.balance -= event["amount"] self.events.append(event) def deposit(self, amount): event = {"type": "deposited", "amount": amount} self.apply_event(event) def withdraw(self, amount): event = {"type": "withdrawn", "amount": amount} self.apply_event(event) def main(): account = Account() account.deposit(100) account.deposit(50) account.withdraw(30) account.deposit(30) for evt in account.events: print(evt) print(f"Balance: {account.balance}") if __name__ == "__main__": main()
{'type': 'deposited', 'amount': 50}
{'type': 'withdrawn', 'amount': 30}
{'type': 'deposited', 'amount': 30}
Balance: 150
Usando la Librería eventsourcing
from eventsourcing.domain import Aggregate, event from eventsourcing.application import Application class InventoryItem(Aggregate): @event("ItemCreated") def __init__(self, name, quantity=0): self.name = name self.quantity = quantity @event("QuantityIncreased") def increase_quantity(self, amount): self.quantity += amount @event("QuantityDecreased") def decrease_quantity(self, amount): self.quantity -= amount class InventoryApp(Application): def create_item(self, name, quantity): item = InventoryItem(name, quantity) self.save(item) return item.id def increase_item_quantity(self, item_id, amount): item = self.repository.get(item_id) item.increase_quantity(amount) self.save(item) def main(): app = InventoryApp() item_id = app.create_item("Laptop", 10) app.increase_item_quantity(item_id, 5) if __name__ == "__main__": main()
eventsourcing simplifica la implementación.
Otros Patrones Arquitectónicos
Resumen de Patrones
01 // MVC
Separa la aplicación en Model, View y Controller para código más manejable y testeable.
02 // Microservices
Estructura la aplicación como servicios pequeños e independientes.
03 // Serverless
Enfoca en lógica de negocio aprovechando servicios cloud para ejecutar código.
04 // Event Sourcing
Almacena cambios como secuencia de eventos con audit trail robusto.
Requerimientos Técnicos
# Microservices - gRPC python -m pip install grpcio grpcio-tools # Serverless - LocalStack python -m pip install localstack awscli-local awscli # Event Sourcing python -m pip install eventsourcing