Skip to main content

Inicia sesión en CleanKata

Sigue tu progreso, gana XP y desbloquea todas las lecciones.

Al iniciar sesión aceptas nuestros Términos de uso y Política de privacidad.

Arquitectura Limpia60 XP6 min

Independencia del Dispositivo

Atar la lógica de negocio a dispositivos de E/S específicos es un error costoso — la arquitectura debe ser agnóstica a si los datos vienen de tarjetas, terminales, archivos o APIs.

Por qué importa

En los años 60, los programas se escribían directamente para tarjetas perforadas. Cuando apareció la cinta magnética, el código tenía que reescribirse completamente — no porque cambiara la lógica de negocio, sino porque cambió el dispositivo de entrada. Los sistemas operativos independientes del dispositivo resolvieron esto abstrayendo la E/S en una interfaz uniforme: el mismo programa funcionaba con tarjetas, cintas o discos sin modificación. La misma lección se aplica hoy.

HTTP, stdin, colas de mensajes y cargas de archivos son todos mecanismos de entrega — son el equivalente moderno de las tarjetas perforadas y las cintas. Tu lógica de negocio debería ser tan indiferente a cómo llegan los datos como lo es un SO respecto a qué disco físico está instalado. Cuando la lógica de nómina conoce sys.stdin, es un programa escrito para tarjetas perforadas otra vez.

✗El problema

A payroll function that reads from stdin and writes to a printer device — the business rule is buried inside device-specific code and cannot be reused or tested alone.

Bad

import sys

def run_payroll():
    for line in sys.stdin:          # tied to stdin
        parts = line.strip().split(",")
        employee_id = parts[0]
        hours, rate = float(parts[1]), float(parts[2])
        gross = hours * rate
        tax   = gross * 0.2
        net   = gross - tax
        with open("/dev/lp0", "w") as printer:  # tied to printer device
            printer.write(f"Employee {employee_id}: net={net:.2f}\n")

# Switching to REST API input: rewrite run_payroll entirely.
# Switching output from printer to PDF: rewrite run_payroll entirely.
# Testing: must pipe data through stdin and capture printer output.
import { Request, Response } from "express";

export function processPayroll(req: Request, res: Response): void {
  const employees = req.body.employees as Array<{ id: string; hours: number; rate: number }>;
  const results = employees.map(emp => {
    const gross = emp.hours * emp.rate;
    const tax   = gross * 0.2;
    const net   = gross - tax;
    return `<tr><td>${emp.id}</td><td>${net.toFixed(2)}</td></tr>`;
  });
  res.send(`<table>${results.join("")}</table>`);
}
// Business rule (tax calculation) is trapped inside an HTTP handler.
// Can't reuse for CLI payroll runs, PDF reports, or batch jobs.

✓La solución

Pure calculation function with no I/O — input source and output destination are wired at the boundary, not inside the business rule.

Good

from dataclasses import dataclass
from decimal import Decimal

@dataclass
class Employee:
    id: str
    hours: Decimal
    rate: Decimal

@dataclass
class PaySlip:
    employee_id: str
    gross: Decimal
    tax: Decimal
    net: Decimal

def calculate_payroll(employees: list[Employee]) -> list[PaySlip]:
    """Pure function — no stdin, no files, no HTTP. Device-agnostic."""
    slips = []
    for emp in employees:
        gross = emp.hours * emp.rate
        tax   = gross * Decimal("0.2")
        net   = gross - tax
        slips.append(PaySlip(emp.id, gross, tax, net))
    return slips

# Wired at the boundary — not inside the rule:
# stdin:    employees = parse_stdin_csv(sys.stdin)
# REST API: employees = parse_request_body(request.json)
# The same calculate_payroll() call in all cases.
export interface Employee { id: string; hours: number; rate: number; }
export interface PaySlip  { employeeId: string; gross: number; tax: number; net: number; }

export function calculatePayroll(employees: Employee[]): PaySlip[] {
  return employees.map(emp => {
    const gross = emp.hours * emp.rate;
    const tax   = gross * 0.2;
    const net   = gross - tax;
    return { employeeId: emp.id, gross, tax, net };
  });
}

// HTTP boundary wires it for REST:
// app.post("/payroll", (req, res) => {
//   const slips = calculatePayroll(req.body.employees);
//   res.json(slips);
// });

// CLI boundary wires it for stdin:
// const employees = parseStdinCsv(process.stdin);
// const slips     = calculatePayroll(employees);
// printToConsole(slips);

// calculatePayroll is testable with plain arrays — no I/O mocking needed.

💡Conclusión clave

Si tu lógica de negocio sabe qué es HTTP, está acoplada a un mecanismo de entrega que no debería importarle. HTTP, stdin, colas de mensajes y archivos son todos lectores equivalentes de tarjetas perforadas — conéctalos en la frontera y mantén la regla de negocio agnóstica al dispositivo.

🔧 Algunos ejercicios pueden tener errores. Si algo parece incorrecto, usa el botón Feedback (abajo a la derecha) para reportarlo — nos ayuda a corregirlo rápido.

Pista: Si tu lógica de negocio sabe qué es HTTP, está acoplada a un mecanismo de entrega del que no debería saber nada.

✗ Tu versión

Independencia del Dispositivo — CleanKata — CleanKata