Mantener las Opciones Abiertas
Un buen arquitecto maximiza el número de decisiones aún no tomadas — separando la política de los detalles para que las elecciones de base de datos y framework puedan postponerse hasta saber más.
Por qué importa
Todo sistema de software puede dividirse en dos categorías: política y detalles. La política contiene las reglas y procedimientos de negocio — el verdadero valor del sistema. Los detalles son las cosas que permiten a personas, máquinas y otros sistemas comunicarse con la política: bases de datos, frameworks web, dispositivos de E/S. Los detalles son irrelevantes para la política. Nunca deberían influir en ella.
Un buen arquitecto reconoce esto y maximiza el número de decisiones aún no tomadas. Cuanto más retrases comprometerte con una tecnología de base de datos, más sabrás sobre patrones de tráfico, formas de consulta y necesidades de consistencia. Un sistema cuya lógica de negocio funciona con un repositorio en memoria demuestra que la base de datos es un detalle — puedes conectar la real cuando la elección esté bien informada.
✗El problema
Business logic class that directly imports the ORM, queries a DB model, and formats HTML — policy and details welded together, impossible to test without a real database.
Bad
from sqlalchemy.orm import Session
from models import User, Subscription
class UserService:
def __init__(self, session: Session):
self.session = session
def get_welcome_page(self, user_id: int) -> str:
user = self.session.query(User).filter(User.id == user_id).first()
if not user:
return "<h1>Not found</h1>"
sub = self.session.query(Subscription).filter(
Subscription.user_id == user_id, Subscription.active == True
).first()
tier = "Premium" if sub else "Free"
return f"<h1>Welcome {user.name} ({tier})</h1>"
# Test requires a real database with the full ORM schema.
# Switching to MongoDB: rewrite UserService entirely.
# The database is not a detail — it is a core dependency.
import { DataSource } from "typeorm";
import { UserEntity } from "./entities/User";
import { SubscriptionEntity } from "./entities/Subscription";
export class UserService {
constructor(private ds: DataSource) {}
async getWelcomePage(userId: number): Promise {
const user = await this.ds.getRepository(UserEntity)
.findOne({ where: { id: userId } });
if (!user) return "<h1>Not found</h1>";
const sub = await this.ds.getRepository(SubscriptionEntity)
.findOne({ where: { userId, active: true } });
const tier = sub ? "Premium" : "Free";
return `<h1>Welcome ${user.name} (${tier})</h1>`;
}
}
// Test requires TypeORM DataSource with migrations.
// The option to swap the database has already been closed.
✓La solución
UserService depends only on an abstract repository — the database is a plugin you choose when you're ready.
Good
from abc import ABC, abstractmethod
from dataclasses import dataclass
@dataclass
class UserProfile:
name: str
tier: str # "free" | "premium"
class UserRepository(ABC):
@abstractmethod
def get_profile(self, user_id: int) -> UserProfile | None: ...
class UserService:
def __init__(self, repo: UserRepository):
self._repo = repo
def get_welcome_message(self, user_id: int) -> str:
profile = self._repo.get_profile(user_id)
if not profile:
return "User not found"
label = "Premium" if profile.tier == "premium" else "Free"
return f"Welcome {profile.name} ({label})"
# Test with zero infrastructure:
class InMemoryUserRepo(UserRepository):
def __init__(self, data): self._data = data
def get_profile(self, uid): return self._data.get(uid)
# The database option is still open. Commit when you know enough.
export interface UserProfile { name: string; tier: "free" | "premium"; }
export interface UserRepository {
getProfile(userId: number): Promise;
}
export class UserService {
constructor(private repo: UserRepository) {}
async getWelcomeMessage(userId: number): Promise {
const profile = await this.repo.getProfile(userId);
if (!profile) return "User not found";
const label = profile.tier === "premium" ? "Premium" : "Free";
return `Welcome ${profile.name} (${label})`;
}
}
// In tests — no DB, no ORM, no network:
const stub: UserRepository = {
getProfile: async (id) => id === 1 ? { name: "Alice", tier: "premium" } : null,
};
// In production — TypeORM or Prisma adapter implements UserRepository.
// Decision deferred. Option kept open.
💡Conclusión clave
Si tu lógica de negocio no puede ejecutarse sin una conexión a base de datos, la base de datos no es un detalle — es una dependencia central que nunca cuestionaste. Separa la política de los detalles, y retrasa comprometerte con los detalles hasta que tengas suficiente información para elegir sabiamente.
🔧 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 no puede ejecutarse sin una conexión a la base de datos, la base de datos no es un detalle — es una dependencia central que nunca cuestionaste.
✗ Tu versión