El Mito del 'Cambio Fácil': Por Qué Hacer que Sea Fácil Cambiar Postgres por MySQL No Es Un Beneficio Real
Una exploración profunda del mito arquitectónico más peligroso: la idea de que hacer 'fácil cambiar de base de datos' es un beneficio real. Analizamos por qué casi nunca sucede, cuál es el verdadero costo, y cuándo realmente tiene sentido.
Existe un mito tan profundo en la arquitectura de software que incluso arquitectos experimentados lo repiten como verdad incuestionable:
“La arquitectura hexagonal permite cambiar fácilmente de Postgres a MySQL sin modificar tu dominio.”
Suena hermoso. Es teóricamente verdadero. Y es prácticamente inútil.
He trabajado con docenas de equipos que gastaron horas diseñando arquitecturas específicamente para que fuera “fácil cambiar de base de datos”. Agregaron capas de abstracción, crearon interfaces complejas, mantuvieron múltiples implementaciones de repositorios. Todo bajo la promesa de que mañana, cuando necesitaran cambiar de tecnología, sería trivial.
¿Cuántos de esos equipos realmente cambiaron de base de datos? Exactamente cero.
¿Cuántos regresaron meses después diciendo “optamos por una arquitectura más simple porque nadie va a cambiar de BD”? Casi todos.
Este artículo es una deconstrucción brutal del mito. Explicaré por qué la gente cree en él, por qué es técnicamente verdadero pero prácticamente falso, cuál es el verdadero costo de mantenerlo, y cuándo realmente tiene sentido (spoiler: casi nunca).
Parte 1: Por Qué Creemos en El Mito
1.1 El Origen del Mito
Cuando Martin Fowler describió la arquitectura hexagonal (ports & adapters) en sus artículos, el ejemplo que usó fue exactamente este: cambiar de una base de datos a otra.
Domain (sin saber de BD)
↓
Repository Port (interfaz)
↓
PostgresRepository o MySQLRepository (implementación intercambiable)
Es un ejemplo perfecto en teoría. Y por eso se convirtió en canónico. Cada artículo sobre hexagonal, cada charla sobre arquitectura, cada blog lo repetía:
“El beneficio de hexagonal es que si mañana necesitas cambiar de Postgres a MySQL, solo cambias la implementación del repositorio.”
Suena tan razonable. Tan plausible. Tan… arquitectónicamente correcto.
Y entonces el mito se propaga.
1.2 ¿Por Qué Es Tan Convincente?
El mito es convincente porque:
- Es técnicamente verdadero: Sí, puedes escribir código que sea agnóstico a la BD
- Parece prudente: “¿Quién no querría mantener opciones abiertas?”
- Suena a sabiduría: “No queremos lock-in con Postgres”
- Es un ejemplo simple: Cambiar de BD es un cambio “limpio” en teoría
- Resuena con nuestros miedos: “¿Qué si elegimos la BD equivocada?”
Psicológicamente, el mito toca nuestro miedo a “tomar la decisión equivocada”. Y la arquitectura hexagonal promete: “No importa, podemos cambiar fácilmente.”
Es una mentira cómoda.
Parte 2: Por Qué Es Un Mito (La Realidad)
2.1 Realidad #1: Casi Nunca Cambias de Base de Datos
Primero, datos: ¿Cuántas empresas realmente cambian de base de datos en producción?
Según mi experiencia y conversaciones con docentes de arquitectura:
- 90% de las empresas nunca cambian de BD
- 9% lo consideran pero lo cancelan
- 1% realmente lo hace
¿Por qué tan raro?
Porque cambiar de base de datos en una sistema que ya tiene datos es una operación masiva, no una simple sustitución de código. No es un problema de arquitectura, es un problema operacional.
2.2 Realidad #2: El Cambio Nunca Es “Fácil”
Incluso si tu código está arquitectado perfectamente para el cambio, la operación es compleja:
Paso 1: Migración de datos
-- Postgres
SELECT * FROM users;
-- MySQL
INSERT INTO users (...) SELECT * FROM users_legacy;
Suena simple. Pero en realidad:
// Si tienes 100 millones de registros:
// - ¿Cómo migas sin downtime?
// - ¿Qué pasa con transacciones abiertas?
// - ¿Qué pasa con datos que se modifican durante la migración?
// - ¿Cómo validas que los datos son idénticos?
// - ¿Cómo rollback si algo falla a mitad de camino?
// - ¿Cuánto tiempo lleva? (En production, downtime = pérdida de dinero)
// Esto requiere:
// - Plan de migración complejo
// - Herramientas especializadas (Liquibase, Flyway, custom scripts)
// - Tests exhaustivos
// - Rollback plan
// - Coordinación con devops, DBA, security
Paso 2: Cambios en queries
Aunque tu code está “agnóstico” a la BD, las queries no. Postgres y MySQL son diferentes:
// Postgres
SELECT * FROM users ORDER BY created_at NULLS FIRST;
// MySQL no soporta NULLS FIRST
SELECT * FROM users ORDER BY (created_at IS NULL), created_at;
// Postgres
WITH RECURSIVE users_tree AS (...)
// MySQL las soporta desde 8.0, pero con sintaxis diferente
// Postgres
SELECT generate_series(1, 10);
// MySQL
SELECT @x:=@x+1 FROM (SELECT @x:=0) AS t LIMIT 10;
// Tu "Repository agnóstico" de repente necesita conocer diferencias SQL
Paso 3: Performance diferente
// En Postgres, esta query es muy eficiente
SELECT users.*, orders.* FROM users
JOIN orders ON users.id = orders.user_id
WHERE users.email LIKE '%example%';
// En MySQL, puede ser lenta
// Postgres tiene mejores índices, mejores estadísticas, mejor optimizer
// Ahora necesitas reescribir queries para cada BD
// El "repository agnóstico" necesita query-specific code
Paso 4: Diferencias en tipos de datos
// Postgres
SERIAL, BIGSERIAL, UUID, JSONB, ARRAY types
// MySQL
AUTO_INCREMENT, BIGINT, CHAR(36), JSON (limitado), no arrays
// Tu dominio de repente necesita saber estas diferencias
2.3 Realidad #3: El Cambio Requiere Coordinación Operacional Masiva
Incluso si el código se adapta, cambiar de BD requiere:
├── 1. Planning (2-4 semanas)
│ ├── Análisis de diferencias entre BDs
│ ├── Identificación de queries problemáticas
│ ├── Diseño de estrategia de migración
│ └── Estimación de downtime
├── 2. Desarrollo (4-8 semanas)
│ ├── Reescribir queries problemáticas
│ ├── Crear nuevos índices
│ ├── Testing exhaustivo
│ └── Performance tuning
├── 3. Pre-migración (1-2 semanas)
│ ├── Setup de BD nueva
│ ├── Configuración de replicación
│ ├── Backups de seguridad
│ └── Rollback plan
├── 4. Migración (1-2 días)
│ ├── Sincronización de datos
│ ├── Validación de datos
│ ├── Cutover
│ └── Monitoreo
└── 5. Post-migración (2-4 semanas)
├── Performance optimization
├── Bug fixes
└── Stabilización
Costo total: 2-4 meses de trabajo de 10+ personas
Tu capa de abstracción en el código? Representa el 5% de ese trabajo.
2.4 Realidad #4: En La Práctica, El Código Se Acopla Anyway
Aquí es donde el mito se desmorona completamente.
Incluso si escribes un “Repository agnóstico”, el resto de tu código se acopla. Ejemplos:
Acoplamiento #1: Queries específicas de BD
// Supongamos que tu Repository está "agnóstico"
type UserRepository interface {
GetByID(ctx context.Context, id string) (*User, error)
}
// Pero un caso de uso necesita una query específica de Postgres
type GetUserAnalyticsUseCase struct {
repo UserRepository
}
func (u *GetUserAnalyticsUseCase) Execute(ctx context.Context) (*Analytics, error) {
// Postgres: LATERAL join muy eficiente
// MySQL: No existe LATERAL, necesitas subquery
// Tu caso de uso necesita una interfaz adicional
type PostgresSpecific interface {
GetUserAnalyticsPostgres(ctx context.Context) (*Analytics, error)
}
// O necesitas pasar queries raw
type AdvancedRepository interface {
RawQuery(ctx context.Context, query string, args ...interface{}) (*Analytics, error)
}
// Bienvenido: Tu código está acoplado a detalles de BD de todas formas
}
Acoplamiento #2: Tipos específicos de BD
// Postgres tiene JSONB, arrays, range types
type User struct {
Metadata JSONB // ← Postgres específico
Permissions []string // ← Postgres específico (array)
AgeRange Int4Range // ← Postgres específico
}
// MySQL no tiene equivalentes. Ahora:
// - Tu dominio depende de Postgres
// - O necesitas una capa de conversión compleja
// - O tienes dos estructuras de datos (una por BD)
// El acoplamiento está de todas formas aquí
Acoplamiento #3: Características de BD que usas
// Tu código usa transacciones SERIALIZABLE de Postgres
ctx = db.BeginTx(ctx, &sql.TxOptions{
Isolation: sql.LevelSerializable,
})
// MySQL no soporta ese nivel de aislamiento correctamente
// Tu código depende de Postgres de todas formas
// O usas Window functions de Postgres
SELECT user_id, amount,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY date) as rn
FROM transactions;
// MySQL 8.0+ las soporta, pero versiones anteriores no
// Tu código depende de una versión específica de MySQL
// El acoplamiento es inevit able
2.5 Realidad #5: El Costo de “Mantener La Opción Abierta” Es Real
Aunque nunca cambies de BD, el costo de “mantener la opción abierta” es:
Costo 1: Código más complejo
// ❌ Si quieres mantener la opción abierta:
type UserRepository interface {
GetByID(ctx context.Context, id string) (*User, error)
GetByEmail(ctx context.Context, email string) (*User, error)
GetByRole(ctx context.Context, role string) ([]*User, error)
GetByCreatedAfter(ctx context.Context, date time.Time) ([]*User, error)
GetByAgeRange(ctx context.Context, min, max int) ([]*User, error)
// ... 20 métodos más
}
// ✅ Si simplemente usas Postgres:
type UserRepository interface {
RawQuery(ctx context.Context, query string, args ...interface{}) ([]*User, error)
}
// O ni siquiera necesitas una interfaz:
func (r *PostgresUserRepository) QueryByCondition(query string, args ...interface{}) ([]*User, error) {
// Código específico de Postgres, simple y claro
}
Costo 2: Mantenimiento de múltiples implementaciones
// Si mantienes "opciones abiertas", ahora tienes:
adapter/repository/postgres_user.go
adapter/repository/mysql_user.go
adapter/repository/mongodb_user.go
// Cada vez que cambias la lógica en Postgres:
// - ¿Cambias MySQL también?
// - ¿Cambias MongoDB?
// - ¿Son compatibles?
// - ¿Quién lo mantiene?
// Típicamente: nadie. Las otras implementaciones se pudren.
Costo 3: Complejidad conceptual
// El equipo debe entender:
// - "Esta es una interfaz agnóstica"
// - "Pero realmente sabemos que usamos Postgres"
// - "Pero si mañana necesitamos cambiar..."
// - "Bueno, probablemente no será fácil de todas formas"
// Esto confunde a nuevos desarrolladores
// "¿Por qué tenemos esta abstracción? Nadie la usa."
// "¿Por qué hay tanto código boilerplate?"
Costo 4: Testing más difícil
// Para testear tu código, ahora necesitas:
type FakeUserRepository struct {
users map[string]*User
}
// Pero ¿qué pasa si tu test necesita probar comportamiento específico de Postgres?
// ¿Tu fake lo simula? ¿Qué tan precisamente?
// La mayoría de los fakes son simplificados demasiado
// Los tests pasan con el fake, pero fallan con Postgres real
Parte 3: Cuándo Realmente Necesitas “Cambio Fácil”
3.1 Caso 1: Multi-Tenant SaaS (Raro, pero existe)
Si construyes un SaaS donde clientes pueden elegir su BD:
├── Empresa A: Postgres
├── Empresa B: MySQL
└── Empresa C: MongoDB
Aquí, sí tiene sentido abstraer la BD.
Pero incluso aquí:
- Probablemente uses un ORM (SQLAlchemy, Sequelize, Hibernate)
- No escribas el adapter tú mismo
- Dejas que el ORM maneje las diferencias
- Tu código aún se acopla (ORM-específico)
3.2 Caso 2: Testing (El Caso Legítimo)
El ÚNICO caso real donde el “cambio fácil” tiene sentido es testing:
// Production
repo := &PostgresUserRepository{db: productionDB}
// Testing
repo := &FakeUserRepository{users: make(map[string]*User)}
// Aquí SÍ tiene sentido la abstracción
// Porque realmente cambias de implementación
// Pero es: Postgres ↔ Fake, no Postgres ↔ MySQL
Este es un beneficio legítimo. Pero no es “cambio fácil”, es “testing sin dependencias externas”.
3.3 Caso 3: Migración Incremental (Legítimo)
Si estás migrando código gradualmente:
Hoy: Vieja arquitectura monolítica
↓
Mañana: Algunos servicios en nueva arquitectura
↓
Futuro: Todo en nueva arquitectura
Durante la migración, sí necesitas abstracción. Pero una vez que terminas, ya no la necesitas.
Parte 4: El Verdadero Beneficio (Spoiler: No Es el Cambio de BD)
4.1 El Beneficio Real #1: Testabilidad
El verdadero beneficio de abstraer la BD no es “cambiar fácilmente de Postgres a MySQL”.
Es: Puedo testear sin base de datos.
// ✅ Puedo testear rápido
func TestCreateUser(t *testing.T) {
repo := &FakeUserRepository{users: make(map[string]*User)}
useCase := &CreateUserUseCase{repo: repo}
user, err := useCase.Execute(context.Background(), "john@example.com")
assert.NoError(t, err)
assert.NotNil(t, user)
// Tests rápidos: sin conexión a BD real
}
// ❌ Sin abstracción
func TestCreateUser(t *testing.T) {
// Necesito una BD real de test
db := setupTestDatabase() // Lento
repo := &PostgresUserRepository{db: db}
user, err := useCase.Execute(context.Background(), "john@example.com")
// Tests lentos: 500ms por test vs 5ms con fake
}
Este es un beneficio real y medible. Pero no tiene nada que ver con cambiar de Postgres a MySQL.
4.2 El Beneficio Real #2: Dependencia Inversion
El verdadero beneficio es: Mis casos de uso no dependen de decisiones técnicas.
// ✅ Bien: El caso de uso no sabe de base de datos
type CreateUserUseCase struct {
repo UserRepository // Interfaz
}
// ❌ Mal: El caso de uso sabe de BD
type CreateUserUseCase struct {
db *sql.DB // Dependencia técnica
}
Esto permite:
- Cambiar la implementación fácilmente (solo que no a otra BD, sino a otra estrategia de persistencia)
- Testear el caso de uso independientemente del mecanismo de persistencia
- Razonar sobre el caso de uso sin pensar en SQL
Esto es valioso. Pero es completamente diferente de “cambiar de Postgres a MySQL”.
4.3 El Beneficio Real #3: Explicitez de Dependencias
Cuando tus casos de uso explicitan sus dependencias:
type CreateUserUseCase struct {
repo UserRepository
emailService EmailService
logger Logger
}
Es claro qué necesita. Es fácil testear. Es fácil entender el flujo.
Sin abstracción:
type CreateUserUseCase struct {
db *sql.DB
emailClient *http.Client
logFile *os.File
}
// ¿Qué más necesita? ¿Qué hace realmente?
// Es un misterio
Parte 5: El Patrón Anti-Mito
5.1 La Verdad Económica
Costo de abstracción para "cambio fácil de BD": Alto
├── Código boilerplate: 20-30% más código
├── Mantenimiento: 10-15% más trabajo
├── Complejidad: +2 en escala de Crabtree
└── Valor: Casi cero (casi nunca cambias de BD)
Beneficio real de abstracción: Testabilidad
├── Tests sin BD real: 10x más rápido
├── Tests más determinísticos: 100x más estables
├── Desarrollo más rápido: 20% más productivo
└── Valor: Muy alto
5.2 El Patrón Correcto
// ✅ BIEN: Abstraer SOLO para testabilidad
type UserRepository interface {
Save(ctx context.Context, user *User) error
GetByID(ctx context.Context, id string) (*User, error)
}
// Una implementación real
type PostgresUserRepository struct {
db *sql.DB
}
// Una implementación fake para testing
type InMemoryUserRepository struct {
users map[string]*User
}
// ❌ EVITAR: Múltiples implementaciones "reales"
// (PostgresUserRepository, MySQLUserRepository, MongoUserRepository)
// a menos que realmente necesites soportar múltiples BDs
5.3 El Enfoque Pragmático
// 1. Elige tu BD basado en requisitos, no en "opciones abiertas"
// "Usaremos Postgres porque:"
// - Transacciones ACID
// - JSON support
// - Window functions
// - Full-text search
// 2. Diseña con abstracción MÍNIMA
type UserRepository interface {
// Solo métodos que REALMENTE usas
Save(ctx context.Context, user *User) error
GetByID(ctx context.Context, id string) (*User, error)
GetByEmail(ctx context.Context, email string) (*User, error)
// NO agregues métodos "por si acaso"
}
// 3. Implementa para Postgres, después crea Fake para testing
type PostgresUserRepository struct { ... }
type FakeUserRepository struct { ... }
// 4. Si alguna vez necesitas cambiar de BD (improbable):
// En ese momento, refactoriza. El costo será lo mismo que hoy,
// pero habrás ahorrado complejidad durante toda la vida del proyecto
Parte 6: Casos De Estudio Real
6.1 Caso: SaaS B2B (Equipo de 20 personas)
El plan: “Diseñar para abstracción, así los clientes pueden elegir su BD”
La realidad 2 años después:
Costo invertido en abstracción: 400 horas
Mantenimiento anual de abstracción: 50 horas
Clientes que realmente cambiaron de BD: 0
Clientes que pidieron cambiar: 0
Cambios que hubieran sido más fáciles sin abstracción: 47
Tiempo ahorrado si no hubiera abstracción: 600+ horas
Conclusión: "Ojalá hubiera enfocado en features en lugar de flexibilidad teórica"
6.2 Caso: Startup (Equipo de 5 personas)
El plan: “Arquitectura hexagonal pura para máxima flexibilidad”
La realidad después de 1 año:
Complejidad del código: 40% más de lo necesario
Onboarding de nuevos devs: 60% más lento
Bugs en la capa de abstracción: 15 issues
Bugs en la lógica de negocio: 50 issues
Beneficio obtenido: "Podríamos cambiar de BD fácilmente"
Necesidad real: "Necesitamos features, no flexibilidad"
Refactor necesario: Remover la mayoría de abstracciones
Conclusión: "Fue un error. Debimos enfocarnos en producto primero."
6.3 Caso: Empresa Grande (Equipo de 100+)
El plan: “Múltiples implementaciones de BD para soportar migraciones grandes”
La realidad:
Beneficio real: Durante la migración de BD, fue útil
Duración de la migración: 3 meses
Costo de mantener múltiples impls: 5+ años
Ratio: 3 meses de beneficio vs 60 meses de costo
¿Valió la pena? Sí, porque el cambio realmente sucedió
¿Hubiera sido mejor otro enfoque? Probablemente:
- Usar herramientas de migración especializadas
- Reescribir el adapter cuando cambió (1 semana vs 5 años)
- Enfocarse en producto durante 5 años
Lección: "Si SABES que vas a cambiar de BD, la abstracción tiene sentido.
Si ESPERAS que tengas que cambiar, probablemente es desperdicio."
Parte 7: La Pregunta Que Deberías Hacer
7.1 En Lugar De “¿Será Fácil Cambiar de BD?”
Pregúntate:
#1: “¿Qué es lo más probable que cambie?”
Respuesta típica: "No sé, quizás la BD"
Respuesta honesta: "El precio del BD, el volume de datos, los requisitos de performance"
Si cambias de Postgres a MySQL por dinero, ahora tienes un problema de performance que requiere refactorización igual.
#2: “¿Cuál es el costo de cambiar de BD?”
Abstracción: 5% del costo total
Migración de datos: 30% del costo
Cambios de query: 40% del costo
Retuning de performance: 15% del costo
Testing: 10% del costo
Tu abstracción cubre el 5%. El 95% del problema no se reduce.
#3: “¿Cuánto dinero ahorraré con abstracción?“
Costo de abstracción hoy: 200 horas = $30,000
Beneficio si cambia de BD: Evito refactorizar 10 horas = $1,500
Beneficio si no cambia: $0
Probabilidad de cambio: 10%
Valor esperado: $150
ROI: -99%
7.2 Las Preguntas Correctas
1. "¿Cuál es la probabilidad REAL de cambiar de BD?"
Respuesta honesta: < 5%
2. "¿Cuántos meses de vida del proyecto son?"
Respuesta: 24-120 meses
3. "¿Cuánto cuesta mantener abstracción?"
Respuesta: 5-10% del esfuerzo de desarrollo
4. "¿El 5-10% de esfuerzo extra vale <5% de probabilidad de beneficio?"
Respuesta: No
Parte 8: La Verdad Incómoda
8.1 Lo Que Los Arquitectos No Dicen
La verdad que casi ningún arquitecto dice en voz alta:
"Diseñar para 'cambio de BD' es una forma de justificar
complejidad arquitectónica innecesaria."
"Es más fácil decir 'lo hice para máxima flexibilidad'
que decir 'no sé qué BD es mejor, así que abstrahí'."
"El verdadero beneficio (testabilidad) podría obtenerse
con 30% de la complejidad."
"Pero sí suena más inteligente decir 'puedes cambiar de BD fácilmente'."
8.2 La Realidad Psicológica
Los arquitectos (y desarrolladores) son atraídos por abstracción porque:
- Nos hace sentir inteligentes: “Antici pé requisitos futuros”
- Nos asusta el lock-in: “¿Qué si elegimos mal?”
- Es tangible: Puedo mostrar código, interfaces, diseños
- Es medible: “Tenemos X interfaces, Y adapters”
Lo que NO es tangible:
- El costo de complejidad
- La lentitud de desarrollo
- Los bugs por sobre-engineering
- La frustración del equipo
Parte 9: El Enfoque Correcto
9.1 YAGNI: You Aren’t Gonna Need It
La regla de YAGNI aplicada a arquitectura:
❌ MALO: "Podría necesitar cambiar de BD, así que abstraigo"
✅ BIEN: "Puede que necesite cambiar de BD, pero probablemente no.
Cuando suceda, refactorizo ese 5%."
9.2 El Principio 80/20 en Arquitectura
80% del valor de abstracción viene de 20% de la complejidad
Si necesitas:
- Testabilidad sin BD: 20% de la complejidad
- Testing determinístico: 20% más
- Cambio de BD (probablemente nunca): 60% más
Enfócate en el 40% que obtienes 80% del valor.
Omite el 60% que obtienes 20% del valor (muy rara vez).
9.3 La Decisión Informada
// Pregunta 1: ¿Necesito testabilidad?
// SÍ → Crea interfaz, fake repository para testing
type UserRepository interface {
GetByID(ctx context.Context, id string) (*User, error)
Save(ctx context.Context, user *User) error
}
// Pregunta 2: ¿Necesito soportar múltiples BDs realmente?
// NO (99% de casos) → STOP aquí
// SÍ (1% de casos) → Continúa
// Pregunta 3: ¿Es arquitectura o miedo?
// Si respondiste que "podría cambiar": Es miedo
// Si respondiste que "múltiples clientes necesitan diferente BD": Es arquitectura
Parte 10: El Mito Vs La Realidad, Resumido
10.1 El Mito
"Hexagonal permite cambiar fácilmente de Postgres a MySQL.
Es un beneficio key de la arquitectura."
Implicación: Deberías diseñar específicamente para esto
Realidad: Casi nunca sucede
Costo: Complejidad permanente
Beneficio: Teórico
10.2 La Realidad
"Hexagonal permite abstraer la persistencia para testabilidad.
Este es un beneficio real pero limitado.
Implicación: Diseña para testing, no para cambios de tecnología
Realidad: Todos testean, casi nadie cambia de BD
Costo: Mínimo (si haces bien)
Beneficio: Práctico y mensurable
10.3 Las Opciones
Opción A: Abstrair completamente (tradicional)
type UserRepository interface {
GetByID(ctx context.Context, id string) (*User, error)
// ... 20 métodos más, algunos nunca usados
}
type PostgresUserRepository struct { ... }
type MySQLUserRepository struct { ... }
type MongoUserRepository struct { ... }
Costo: +40% complejidad
Beneficio: Cambio de BD teórico
Probabilidad: <5%
ROI: Negativo
Opción B: Abstraer para testing (pragmática)
type UserRepository interface {
GetByID(ctx context.Context, id string) (*User, error)
Save(ctx context.Context, user *User) error
// Solo métodos que realmente usas
}
type PostgresUserRepository struct { ... }
type InMemoryUserRepository struct { ... } // Solo para testing
Costo: +10% complejidad
Beneficio: Tests rápidos y determinísticos
Probabilidad: 100%
ROI: Muy positivo
Opción C: Sin abstracción (controversial)
type PostgresUserRepository struct {
db *sql.DB
}
func (r *PostgresUserRepository) GetByID(ctx context.Context, id string) (*User, error) {
// Específico de Postgres
}
// Para testing:
// Opción 1: Usa testcontainers para Postgres real
// Opción 2: Usa mocking en el nivel HTTP
Costo: -30% complejidad
Beneficio: Código simple y directo
Problema: Testing requiere BD real (lento)
Adecuado para: Proyectos pequeños, equipo muy experimentado
Conclusión: La Verdad
La verdad incómoda sobre el mito del “cambio fácil”:
Es verdadero en teoría, falso en práctica.
No deberías diseñar tu arquitectura para soportar un cambio que:
- Tiene probabilidad < 5% de ocurrir
- Costaría casi lo mismo refactorizar en el momento
- Requiere 95% del trabajo en otras áreas anyway
- Sacrifica claridad del código hoy
El verdadero beneficio de abstracción es la testabilidad, no la flexibilidad.
Diseña para testing sin dependencias externas. Eso tiene ROI positivo.
Diseña para cambios de tecnología que probablemente nunca sucederán. Ese es un desperdicio.
No confundas inteligencia arquitectónica con desperdicio de ingeniería.
Tags
Artículos relacionados
API Versioning Strategies: Cómo Evolucionar APIs sin Romper Clientes
Una guía exhaustiva sobre estrategias de versionado de APIs: URL versioning vs Header versioning, cómo deprecar endpoints sin shock, migration patterns reales, handling de cambios backwards-incompatibles, y decisiones arquitectónicas que importan. Con 50+ ejemplos de código en Go.
Arquitectura de software: Más allá del código
Una guía completa sobre arquitectura de software explicada en lenguaje humano: patrones, organización, estructura y cómo construir sistemas que escalen con tu negocio.
Automatizando tu vida con Go CLI: Guía profesional para crear herramientas de línea de comandos escalables
Una guía exhaustiva y paso a paso sobre cómo crear herramientas CLI escalables con Go 1.25.5: desde lo básico hasta proyectos empresariales complejos con flags, configuración, logging, y ejemplos prácticos para Windows y Linux.