Aplicar el patrón Chain of Responsibility a un servicio de pagos en Python mejora la organización de validaciones y facilita la extensión. Aquí verás cómo encapsular datos con Pydantic, crear un chain handler abstracto con ABC y dataclass, implementar un CustomerHandler y ajustar el servicio y el builder para validar en cadena.
¿Cómo estructurar el request y por qué usar Pydantic?
Encapsular en un solo objeto los datos que participan en la validación simplifica la firma de métodos y estandariza la entrada. Se define una clase request que hereda de BaseModel para asegurar tipos y validación básica.
Encapsulación en un único request para customer y payment.
Uso de Pydantic BaseModel: tipado fuerte y validación declarativa.
Menos acoplamiento: validadores reciben el mismo contrato.
from pydantic import BaseModel
# Declarada en el módulo commonsclassRequest(BaseModel): customer_data: CustomerData
payment_data: PaymentData
¿Cómo definir el chain handler abstracto con ABC y dataclass?
La cadena requiere un eslabón común con comportamiento compartido: establecer el siguiente handler y un método handle obligado. Se combina ABC para el contrato, dataclass para el atributo next_handler, y Self para tipos recursivos. El método set_next retorna el propio siguiente eslabón para encadenar con fluidez.
Clase abstracta con @abstractmethod para handle.
Atributo next_handler opcional para el siguiente eslabón.
set_next uniforme en toda la cadena.
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional, Self
from commons import Request
@dataclassclassChainHandler(ABC): next_handler: Optional[Self]=Nonedefset_next(self, handler: Self)-> Self: self.next_handler = handler
return handler
@abstractmethoddefhandle(self, request: Request)->None:...
¿Cómo implementar CustomerHandler y conectar la cadena en el builder?
Este eslabón valida el customer con un validador existente. Si todo es correcto, delega al siguiente handler. Si ocurre un error, lo propaga. Se usa try/except porque el validador lanza excepciones cuando hay datos inválidos.
Delegación al siguiente eslabón solo si existe next_handler.
Propagación de excepciones para detener el flujo ante fallas.
Separación de responsabilidades: cada handler valida una cosa.
from commons import Request
from validators.handler import ChainHandler
from Customer import CustomerValidator # Validador existenteclassCustomerHandler(ChainHandler):defhandle(self, request: Request)->None: validator = CustomerValidator()try: validator.validate(request.customer_data)if self.next_handler: self.next_handler.handle(request)except Exception as exc:print("Fallo en validación de customer.")raise exc
¿Cómo se adapta el servicio y el protocolo?
El servicio deja de depender de múltiples validadores y recibe un único ChainHandler. Construye el request y ejecuta la cadena. Ante error, imprime y relanza la excepción para frenar el proceso.
from commons import Request
# Dentro del servicioreq = Request(customer_data=customer_data, payment_data=payment_data)try: self.validator.handle(req)except Exception as exc:print("Fallo en las validaciones.")raise exc
Protocolo actualizado: depende de ChainHandler, no de una lista de validadores.
Implementación alineada: un único punto de entrada handle.
Flujo claro: construir Request y ejecutar la cadena.
¿Cómo configurar la cadena en el builder?
El builder expone un método para armar la cadena. El primer eslabón se guarda como self.validator. El método retorna self para encadenar configuraciones y el build inyecta el validador en el servicio.
from typing import Optional, Self
from validators.handler import ChainHandler
from validators.customer_handler import CustomerHandler
classPaymentServiceBuilder: validator: Optional[ChainHandler]=Nonedefset_chain_of_validations(self)-> Self: ch1 = CustomerHandler() ch2 = CustomerHandler()# Ejemplo: encadenar otro eslabón ch1.set_next(ch2) self.validator = ch1
return self
defbuild(self)-> PaymentService:return PaymentService(validator=self.validator)
Método fluido: retorna self para continuar la configuración.
Primer eslabón: punto de entrada de la cadena.
Opcionalidad: validator puede ser None hasta configurarlo.
¿Cómo implementarías el handler para payment_data y cómo encadenarías toda la validación en el builder? Comparte tu enfoque y decisiones en los comentarios.