Observer en sistemas de pagos con Python

Clase 24 de 27Curso de Patrones de Diseño y SOLID en Python

Resumen

Implementa notificaciones de pagos en Python con confianza: aquí verás cómo aplicar el patrón Observer para informar a múltiples sistemas cuando un pago es exitoso o fallido, usando listeners, manager, Protocol, genéricos y el patrón builder para un diseño limpio y extensible.

¿Qué resuelve el patrón observer en el sistema de pagos?

El objetivo es notificar a distintos sistemas ante dos eventos: pago exitoso y pago fallido. Para lograrlo, se define una interfaz de listener con un método único, notify, y un manager que centraliza la suscripción, desuscripción y la propagación del evento a todos los listeners interesados.

  • Interfaz mínima y clara: un solo método notify que recibe un evento genérico de tipo T.
  • Gestión centralizada: un Listeners Manager con métodos subscribe, unsubscribe y notify_all.
  • Extensibilidad: agregar nuevos listeners es simple y no rompe el código existente.
  • Integración con el servicio: el builder configura el manager y sus listeners al construir la clase principal.

¿Cómo se implementa el patrón observer paso a paso?

A continuación, una guía práctica basada en el código explicado.

¿Cómo se define la interfaz listener con Protocol y genéricos?

Se crea una carpeta listeners/ y un archivo listener.py que declara el Protocol con un genérico T y el método notify.

# listeners/listener.py
from typing import Protocol, TypeVar

T = TypeVar("T")

class Listener(Protocol[T]):
    def notify(self, event: T) -> None:
        ...

Puntos clave: - Uso de typing.Protocol para definir la forma del listener. - Genérico T para aceptar cualquier tipo de evento sin perder tipado. - El método requiere self y no retorna valor.

¿Cómo gestionar suscripciones con un manager usando dataclass?

Se crea manager.py con un atributo lista de listeners y tres métodos: subscribe, unsubscribe y notify_all. La lista se inicializa vacía con field(default_factory=list).

# listeners/manager.py
from dataclasses import dataclass, field
from typing import List, TypeVar
from .listener import Listener

T = TypeVar("T")

@dataclass
class ListenersManager:
    listeners: List[Listener[T]] = field(default_factory=list)

    def subscribe(self, listener: Listener[T]) -> None:
        self.listeners.append(listener)

    def unsubscribe(self, listener: Listener[T]) -> None:
        self.listeners.remove(listener)

    def notify_all(self, event: T) -> None:
        for listener in self.listeners:
            listener.notify(event)

Puntos clave: - dataclass simplifica la construcción de la clase. - default_factory=list evita pasar la lista por parámetro y previene listas mutables compartidas. - notify_all itera y llama a notify en cada listener suscrito.

¿Cómo crear un listener concreto para contabilidad?

Se implementa un listener sencillo, por ejemplo, AccountabilityListener, que imprime el evento recibido.

# listeners/accountability_listener.py
from .listener import Listener

class AccountabilityListener(Listener[object]):
    def notify(self, event: object) -> None:
        print("Notificando el evento:", event)

Puntos clave: - Implementa notify con la lógica específica (aquí, un print controlado). - El tipo de evento puede ajustarse según el dominio.

¿Cómo integrarlo en el servicio con el patrón builder?

Se agrega al service un atributo de tipo ListenersManager y un método del builder para configurarlo y suscribir listeners por defecto.

# service_builder.py (fragmento)
from typing import Optional
from listeners.manager import ListenersManager
from listeners.accountability_listener import AccountabilityListener

class ServiceBuilder:
    def __init__(self) -> None:
        self.listeners: Optional[ListenersManager] = None

    def set_listeners(self) -> "ServiceBuilder":
        manager = ListenersManager()
        accountability = AccountabilityListener()
        manager.subscribe(accountability)
        self.listeners = manager
        return self

En el flujo de pagos, se notifica en una transacción exitosa después de obtener el payment_response dentro de process_transaction:

# dentro del servicio, luego de un pago exitoso
self.listeners.notify_all(f"pago exitoso: {payment_response.transaction_id}")

Sugerencia pendiente: notificar también el pago fallido cuando el payment_response lo indique.

¿Qué habilidades y keywords se refuerzan en esta implementación?

La construcción refuerza conceptos de diseño y tipado en Python que facilitan el mantenimiento y la escalabilidad.

  • Patrón Observer: desacopla emisores de eventos y suscriptores.
  • Listener como Protocol: interfaz explícita con un único método notify.
  • Genéricos (T): eventos de cualquier tipo con tipado estático.
  • Listeners Manager: métodos subscribe, unsubscribe, notify_all para orquestación.
  • dataclass y field(default_factory=list): inicialización segura de colecciones.
  • AccountabilityListener: ejemplo de implementación concreta.
  • Módulo listeners con init.py: facilita importaciones limpias.
  • Builder: configuración progresiva del service para agregar dependencias como listeners.
  • Eventos de dominio: "pago exitoso" y "pago fallido" con datos como transaction_id.

¿Ya resolviste la notificación de pago fallido? Cuéntame en los comentarios cómo lo implementaste y qué diseño elegiste para el evento.