viernes, 16 de enero de 2026

Principios Fundamentales de Diseño en Python - Parte 6 - Patrones de Diseño Arquitectónico

Patrones de Diseño Arquitectónico en Python
Python // Design Patterns

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.

PATTERN_01

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.

Arquitectura MVC
MODEL
Datos + Lógica
CONTROLLER
Coordinación
VIEW
Presentación

Componentes Clave

MODEL
Contiene y gestiona la lógica de negocio, datos, estado y reglas de la aplicación
VIEW
Representación visual del modelo. Solo muestra datos, no los maneja
CONTROLLER
El enlace entre modelo y vista. Toda comunicación pasa por él
Ejemplos del Mundo Real
  • 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

mvc.py
# 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()
python mvc.py
Which quote number would you like to see? 3
And the quote is: "Black holes really suck..."
Which quote number would you like to see? 6
And the quote is: "Not found!"
Un Model es smart (contiene lógica de negocio), un Controller es thin (solo coordina), y una View es dumb (solo muestra datos).
PATTERN_02

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.

Arquitectura de Microservicios
OrderService
PaymentService
CustomerService
Empresas que usan Microservices
  • 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

payment.proto
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

payment_service.py
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

client.py
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}")
python client.py
Response status: SUCCESS
Los contenedores Docker facilitan el despliegue de microservicios. Cada servicio puede tener sus propias dependencias: uno puede usar MySQL y otro Redis.
PATTERN_03

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.

Flujo Serverless
Event Trigger
Lambda Function
Response

Casos de Uso

BACKUPS
Funciones programadas para respaldos automáticos a cloud storage
PROCESAMIENTO
Procesamiento de imágenes: redimensionar, comprimir, aplicar filtros
PDF GENERATION
Generar recibos PDF después de una compra y enviarlos por email

AWS Lambda Function

lambda_function_square.py
import json

def lambda_handler(event, context):
    number = event["number"]
    squared = number * number
    return f"The square of {number} is {squared}."

Despliegue con LocalStack

terminal_commands
# 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
cat output.txt
The square of 6 is 36.
PATTERN_04

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.

Componentes de Event Sourcing
Event
Aggregate
Event Store

Casos de Uso

TRANSACCIONES
Registrar cada cambio de balance como eventos inmutables
INVENTARIO
Rastrear el ciclo de vida de cada item mediante eventos
UNDO/REDO
Proveer capacidad de deshacer/rehacer acciones en apps

Implementación Manual: Cuenta Bancaria

bankaccount.py
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()
python bankaccount.py
{'type': 'deposited', 'amount': 100}
{'type': 'deposited', 'amount': 50}
{'type': 'withdrawn', 'amount': 30}
{'type': 'deposited', 'amount': 30}
Balance: 150

Usando la Librería eventsourcing

inventory.py
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()
El Event Sourcing permite reconstruir estados pasados reproduciendo la secuencia de eventos. La librería eventsourcing simplifica la implementación.
PATTERN_05

Otros Patrones Arquitectónicos

EDA
Event-Driven Architecture: enfatiza producción, detección y reacción a eventos en tiempo real
CQRS
Separa modelos de lectura y escritura para arquitecturas más escalables
CLEAN ARCH
Encapsula lógica de negocio separándola de las interfaces de exposición

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.

REQUIREMENTS

Requerimientos Técnicos

instalación
# 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

Basado en "Mastering Python Design Patterns" // Kamon Ayeva & Sakis Kasampalis

Capítulo 6: Architectural Design Patterns