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

El Componente Main: El Último Detalle

Main es el componente más sucio y de más bajo nivel — el punto de entrada que conecta todas las dependencias y entrega el control a la arquitectura limpia que está sobre él.

Por qué importa

Main es el módulo de política de más bajo nivel en el sistema — es un plugin para la aplicación. Su trabajo es crear todas las implementaciones concretas, inyectarlas en las interfaces abstractas de las que dependen las capas internas, y luego pasar el control hacia adentro. Main es intencionalmente "sucio": conoce Flask, SQLAlchemy, Stripe, variables de entorno y cada otro detalle concreto. Ese es su propósito.

La clave: Main debe pensarse como un círculo exterior en Clean Architecture — conoce todo, pero nada de adentro conoce a Main. Esto significa que puedes tener múltiples componentes Main. Un Main de producción conecta repositorios reales y proveedores de pago reales. Un Main de prueba conecta stubs en memoria. Un Main de staging conecta servicios simulados. Las capas internas son completamente ajenas a cuál Main se usó.

Cuando la lógica de negocio está dispersa a nivel de módulo junto con la configuración del framework, se vuelve imposible intercambiar la infraestructura. Todo el cableado, la configuración y la lógica de negocio se vuelven inseparables. Main te obliga a hacer explícita la composición — y esa explicitud es lo que hace que tu arquitectura sea testeable y mantenible.

✗El problema

Framework setup and business logic mixed at module level — impossible to test the business logic without starting Flask and connecting to MySQL.

Bad

# app.py — business logic and infrastructure inseparable at module level
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://user:pass@localhost/orders"
db = SQLAlchemy(app)

class Order(db.Model):  # ORM model is also the domain entity
    id     = db.Column(db.Integer, primary_key=True)
    total  = db.Column(db.Float)

    def calculate_tax(self):
        return self.total * 0.21  # business rule tangled with ORM

@app.route("/orders/<int:order_id>")
def get_order(order_id):
    order = Order.query.get_or_404(order_id)
    return jsonify({"id": order.id, "tax": order.calculate_tax()})

if __name__ == "__main__":
    app.run()

# Cannot test calculate_tax() without Flask and MySQL running.
// index.ts — composition scattered, business logic and infrastructure mixed
import express        from "express";
import { DataSource } from "typeorm";

const ds = new DataSource({ type: "mysql", host: "localhost", database: "orders",
  entities: [__dirname + "/entities/*.ts"], synchronize: true });

const app = express();
app.get("/orders/:id", async (req, res) => {
  await ds.initialize();
  const order = await ds.getRepository(OrderEntity).findOneBy({ id: req.params.id });
  if (!order) return res.status(404).json({ error: "not found" });
  const tax = order.total * 0.21;  // business rule buried in handler
  res.json({ id: order.id, tax });
});

app.listen(3000);
// Impossible to test the tax rule without starting the full server.

✓La solución

main.py wires the concrete implementations and injects them into the inner layers — which know nothing about Flask or MySQL. For tests, inject in-memory stubs instead.

Good

# order.py (inner layer — no Flask, no SQLAlchemy)
from dataclasses import dataclass
from decimal import Decimal

@dataclass
class Order:
    order_id: str
    total: Decimal

    def calculate_tax(self) -> Decimal:
        return self.total * Decimal("0.21")

# order_service.py (inner layer)
class OrderService:
    def __init__(self, repo):  # abstract interface
        self._repo = repo

    def get_order_with_tax(self, order_id: str):
        order = self._repo.find_by_id(order_id)
        return {"id": order.order_id, "tax": str(order.calculate_tax())}

# main.py — the ONLY file that knows about Flask and MySQL
from flask import Flask, jsonify
from mysql_order_repository import MySQLOrderRepository
from order_service import OrderService

def create_app() -> Flask:
    repo    = MySQLOrderRepository("mysql://user:pass@localhost/orders")
    service = OrderService(repo)
    app     = Flask(__name__)

    @app.route("/orders/<order_id>")
    def get_order(order_id):
        return jsonify(service.get_order_with_tax(order_id))

    return app

if __name__ == "__main__":
    create_app().run()

# Tests: inject InMemoryOrderRepository — no Flask, no MySQL needed.
// Order.ts (inner layer — no imports from outer layers)
export class Order {
  constructor(readonly orderId: string, readonly total: number) {}
  calculateTax(): number { return this.total * 0.21; }
}

// OrderRepository.ts (abstract port)
export interface OrderRepository { findById(id: string): Promise<Order | null>; }

// GetOrderUseCase.ts (inner layer)
export class GetOrderUseCase {
  constructor(private readonly repo: OrderRepository) {}
  async execute(orderId: string) {
    const order = await this.repo.findById(orderId);
    if (!order) return null;
    return { id: order.orderId, tax: order.calculateTax() };
  }
}

// main.ts — the ONLY file that knows about Express and TypeORM
import express from "express";
import { TypeOrmOrderRepository } from "./TypeOrmOrderRepository";
import { GetOrderUseCase }        from "./GetOrderUseCase";

const repo    = new TypeOrmOrderRepository(dataSource);
const useCase = new GetOrderUseCase(repo);

const app = express();
app.get("/orders/:id", async (req, res) => {
  const result = await useCase.execute(req.params.id);
  if (!result) return res.status(404).json({ error: "not found" });
  res.json(result);
});
app.listen(3000);
// Tests: inject InMemoryOrderRepository — no Express, no TypeORM, no DB.

💡Conclusión clave

Main es el único componente autorizado a conocer clases concretas. Cablea todo y pasa el control hacia adentro. Todo lo de adentro depende solo de abstracciones. Con múltiples componentes Main — producción, prueba, staging — la misma lógica de negocio se ejecuta sin cargar ningún framework. Main es un detalle; la arquitectura sobre él es el diseño.

🔧 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: Main es el único componente que puede conocer las clases concretas. Todo lo demás debe depender solo de abstracciones.

✗ Tu versión