¿Por qué la arquitectura tradicional no puede con las tareas modernas?
\n\n¿Alguna vez te has encontrado en una situación donde agregar una nueva funcionalidad a una aplicación Swift se convierte en una pesadilla? ¿Los errores se acumulan, los datos se pierden y la lógica crece hasta alcanzar proporciones increíbles? ¿Te suena familiar? Bienvenido al mundo donde CQRS y Event Sourcing se convierten en tus mejores amigos.
\n\nEstos patrones no son solo palabras de moda. Son una forma de pensar que cambia radicalmente el enfoque para construir aplicaciones en Swift. Y hoy los analizaremos con ejemplos reales. Sin rodeos, sin teorías complicadas, solo práctica.
\n\n¿Qué es CQRS? Divide y vencerás
\n\nCQRS (Command Query Responsibility Segregation) es un principio que dice: "Basta de mezclar lectura y escritura en una misma clase". Suena simple, pero las consecuencias son enormes.
\n\nEn la arquitectura CRUD (Create, Read, Update, Delete) clásica, usas un solo modelo para todo. Por ejemplo, User. Se guarda, se lee y se actualiza. ¿Y qué pasa si para leer necesitas unos campos y para escribir otros? Empiezan los parches: campos opcionales, banderas, mapeos...
\n\nCQRS propone separar la responsabilidad:
\n- \n
- Comandos (Commands) — modifican el estado. Mutan los datos, pero no devuelven nada. \n
- Consultas (Queries) — leen datos. No cambian el estado, solo devuelven un resultado. \n
En Swift, esto podría verse así:
\n\n// Comando — solo mutación\nstruct CreateUserCommand {\n let id: UUID\n let name: String\n let email: String\n}\n\n// Consulta — solo lectura\nstruct GetUserQuery {\n let userId: UUID\n}\n\n// Manejador de comando\nclass CreateUserCommandHandler {\n func handle(_ command: CreateUserCommand) {\n // Lógica de creación de usuario\n print("Usuario creado: \\(command.name)")\n }\n}\n\n// Manejador de consulta\nclass GetUserQueryHandler {\n func handle(_ query: GetUserQuery) -> User? {\n // Lógica de obtención de usuario\n return User(id: query.userId, name: "Alexey", email: "alex@example.com")\n }\n}\n\n¿Ves? Sin mezclas. Cada clase hace exactamente una cosa. Esto simplifica las pruebas, el mantenimiento y la escalabilidad.
\n\nEvent Sourcing: Almacenamos la historia, no el estado
\n\nAhora pasamos a Event Sourcing — un patrón que no almacena el estado actual de un objeto, sino toda la secuencia de eventos que llevaron a ese estado.
\n\nImagina una cuenta bancaria. En lugar de almacenar el saldo (1000 rublos), almacenas una lista de eventos: "Apertura de cuenta", "Depósito de 500", "Retiro de 200", "Depósito de 700". Para conocer el saldo actual, simplemente reproduces todos los eventos. Esto ofrece posibilidades increíbles: auditoría, reversión de cambios, análisis.
\n\nImplementemos esto en Swift:
\n\n// Evento\nprotocol Event {}\n\nstruct AccountCreated: Event {\n let accountId: UUID\n let initialBalance: Double\n}\n\nstruct MoneyDeposited: Event {\n let accountId: UUID\n let amount: Double\n}\n\nstruct MoneyWithdrawn: Event {\n let accountId: UUID\n let amount: Double\n}\n\n// Agregado — restaura el estado a partir de eventos\nclass BankAccount {\n private(set) var balance: Double = 0\n private(set) var isActive: Bool = false\n private(set) var accountId: UUID?\n\n func apply(_ event: Event) {\n switch event {\n case let created as AccountCreated:\n accountId = created.accountId\n balance = created.initialBalance\n isActive = true\n case let deposited as MoneyDeposited:\n balance += deposited.amount\n case let withdrawn as MoneyWithdrawn:\n balance -= withdrawn.amount\n default:\n break\n }\n }\n\n // Restauración desde el historial de eventos\n static func load(from history: [Event]) -> BankAccount {\n l