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