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 Limpia80 XP8 min

La Secuencia Principal: El Equilibrio A/I

Los componentes deben equilibrar Abstracción (A) e Inestabilidad (I) — la Zona de Dolor (estable+concreto) y la Zona de Inutilidad (abstracto+inestable) son antipatrones a evitar.

Por qué importa

La Secuencia Principal es la línea ideal en un gráfico bidimensional donde los ejes son Abstracción (A) e Inestabilidad (I), ambos de 0 a 1. La Secuencia Principal es la línea A + I = 1. Los componentes en esta línea están perfectamente equilibrados: o estables y abstractos, o volátiles y concretos.

Zona de Dolor (A≈0, I≈0): Estable y concreto. Un componente del que dependen muchos módulos, pero que contiene solo implementación concreta. No puede extenderse sin modificación. Cada nuevo requisito requiere editarlo, lo que se propaga a todos los dependientes. Esta es la zona más peligrosa — el componente resiste el cambio pero no puede evolucionar. Piensa en una clase dios o un módulo utilitario para todo.

Zona de Inutilidad (A≈1, I≈1): Abstracto e inestable. Un componente de interfaces puras del que nadie depende. Quizás fue creado en anticipación de uso futuro, o fue muy dependido en su momento pero todos esos dependientes fueron refactorizados. Es abstracto pero vestigial — presente sin propósito.

Distancia de la Secuencia Principal: D = |A + I − 1|. D ideal = 0. Cualquier componente con D > 0.3 vale la pena investigarlo. Puedes medir esto automáticamente con herramientas de análisis estático e incluir D en los criterios de calidad del CI.

✗El problema

A DatabaseManager class highly depended upon (I≈0) but entirely concrete (A≈0). D = 1.0 — maximum distance from the Main Sequence. Every new requirement forces editing this stable component.

✗ Bad — Pain Zone (A≈0, I≈0)

# database_manager.py  ← imported by 15 modules (stable, I≈0)
import sqlite3

class DatabaseManager:
    def __init__(self, db_path: str):
        self._conn = sqlite3.connect(db_path)  # concrete: SQLite

    def get_user(self, user_id: str) -> dict:
        cursor = self._conn.execute(
            "SELECT * FROM users WHERE id = ?", (user_id,)
        )
        return dict(cursor.fetchone())

    def save_order(self, order: dict) -> None:
        self._conn.execute(
            "INSERT INTO orders (id, total) VALUES (?, ?)",
            (order["id"], order["total"])
        )

# D = |0 + 0 - 1| = 1.0  ← maximum distance from Main Sequence
# Adding PostgreSQL support: rewrite everything. Pain Zone confirmed.
// DatabaseManager.ts  ← imported by 15 modules (stable, I≈0)
import { Pool } from "pg";

export class DatabaseManager {
  private pool = new Pool({ connectionString: process.env.DATABASE_URL });

  async getUser(userId: string): Promise> {
    const result = await this.pool.query(
      "SELECT * FROM users WHERE id = $1", [userId]
    );
    return result.rows[0];
  }

  async saveOrder(order: { id: string; total: number }): Promise {
    await this.pool.query(
      "INSERT INTO orders (id, total) VALUES ($1, $2)", [order.id, order.total]
    );
  }
}

// D = |0 + 0 - 1| = 1.0  ← maximum distance from Main Sequence
// Adding MySQL support: rewrite everything. Pain Zone confirmed.

✓La solución

Split into DatabasePort (stable, abstract, D=0) and PostgresDatabaseAdapter (volatile, concrete, D=0). Each sits exactly on the Main Sequence.

✓ Good — On the Main Sequence

# database_port.py  ← stable (I≈0), abstract (A≈1)
from abc import ABC, abstractmethod
from dataclasses import dataclass

@dataclass
class UserRecord:
    id: str
    email: str

@dataclass
class OrderRecord:
    id: str
    total: float

class DatabasePort(ABC):
    @abstractmethod
    def get_user(self, user_id: str) -> UserRecord: ...

    @abstractmethod
    def save_order(self, order: OrderRecord) -> None: ...

# D(DatabasePort) = |1 + 0 - 1| = 0  ← on the Main Sequence

# postgres_adapter.py  ← volatile (I≈1), concrete (A≈0)
import psycopg2

class PostgresDatabaseAdapter(DatabasePort):
    def __init__(self, dsn: str):
        self._conn = psycopg2.connect(dsn)

    def get_user(self, user_id: str) -> UserRecord:
        cur = self._conn.cursor()
        cur.execute("SELECT id, email FROM users WHERE id = %s", (user_id,))
        row = cur.fetchone()
        return UserRecord(id=row[0], email=row[1])

    def save_order(self, order: OrderRecord) -> None:
        cur = self._conn.cursor()
        cur.execute("INSERT INTO orders VALUES (%s, %s)", (order.id, order.total))
        self._conn.commit()

# D(PostgresDatabaseAdapter) = |0 + 1 - 1| = 0  ← on the Main Sequence
// DatabasePort.ts  ← stable (I≈0), abstract (A≈1)
export interface UserRecord  { id: string; email: string; }
export interface OrderRecord { id: string; total: number; }

export interface DatabasePort {
  getUser(userId: string): Promise;
  saveOrder(order: OrderRecord): Promise;
}

// D(DatabasePort) = |1 + 0 - 1| = 0  ← on the Main Sequence

// PostgresDatabaseAdapter.ts  ← volatile (I≈1), concrete (A≈0)
import { Pool } from "pg";
import { DatabasePort, UserRecord, OrderRecord } from "./DatabasePort";

export class PostgresDatabaseAdapter implements DatabasePort {
  private pool = new Pool({ connectionString: process.env.DATABASE_URL });

  async getUser(userId: string): Promise {
    const result = await this.pool.query(
      "SELECT id, email FROM users WHERE id = $1", [userId]
    );
    return result.rows[0] as UserRecord;
  }

  async saveOrder(order: OrderRecord): Promise {
    await this.pool.query(
      "INSERT INTO orders (id, total) VALUES ($1, $2)", [order.id, order.total]
    );
  }
}

// D(PostgresDatabaseAdapter) = |0 + 1 - 1| = 0  ← on the Main Sequence

💡Conclusión clave

Calcula D = |A + I − 1| para tus componentes clave. Cualquier valor por encima de 0.3 es un olor de diseño que merece investigación. Los componentes en la Zona de Dolor (estable+concreto) necesitan que se extraigan interfaces de ellos. Los componentes en la Zona de Inutilidad (abstracto+inestable) necesitan nuevos dependientes o eliminación. La Secuencia Principal es el óptimo arquitectónico — equilibra abstracción con estabilidad.

🔧 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: Calcula D = |A + I - 1| para tus componentes clave. Cualquier valor superior a 0,3 es un mal olor de diseño que merece investigación.

✗ Tu versión

La Secuencia Principal: El Equilibrio A/I — CleanKata — CleanKata