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 Limpia70 XP7 min

Servicios y Microservicios: ¿Son Arquitectura?

Los servicios no definen la arquitectura — un servicio con estructura interna pobre es solo una llamada de función costosa. Los límites arquitectónicos reales pueden existir dentro de un monolito o entre servicios.

Por qué importa

El movimiento de microservicios creó dos mitos populares. El primer mito: los servicios son inherentemente independientes. Esto es falso — si dos servicios comparten un esquema de base de datos y la migración de un servicio rompe las consultas del otro, están acoplados independientemente del protocolo que los separa. El segundo mito: los servicios definen la arquitectura. Esto también es falso — la arquitectura está definida por la Regla de Dependencia aplicada al código fuente, no por si la comunicación ocurre en proceso o a través de HTTP.

Un monolito con excelentes fronteras internas — capas limpias, interfaces abstractas, DTOs apropiados — puede ser más mantenible que una flota de microservicios que comparten una base de datos, se pasan filas SQL crudas entre ellos vía REST y requieren despliegues sincronizados. El protocolo de comunicación (llamada a función vs HTTP vs cola de mensajes) es un detalle. La pregunta estructural es la misma: ¿los círculos interiores dependen de los exteriores?

El verdadero desacoplamiento en microservicios viene de una regla: cada servicio posee sus propios datos. Los servicios se comunican a través de contratos de eventos publicados, no tablas compartidas. Un evento es un hecho estable y versionado sobre algo que ocurrió. Cruza la frontera del servicio como un DTO simple. El servicio receptor decide qué hacer con él — independientemente.

✗El problema

Three "microservices" that share the same database table — a distributed monolith where one schema change breaks all three simultaneously.

Bad

# service_auth — queries shared "users" table
def get_user_for_login(email: str) -> dict:
    conn = psycopg2.connect("postgresql://localhost/shared_db")  # shared DB!
    cur.execute("SELECT id, email, password_hash FROM users WHERE email = %s", (email,))

# service_profile — same table, different columns
def get_user_profile(user_id: str) -> dict:
    conn = psycopg2.connect("postgresql://localhost/shared_db")  # same table!
    cur.execute("SELECT id, display_name, avatar_url FROM users WHERE id = %s", (user_id,))

# service_billing — same table again
def get_billing_contact(user_id: str) -> dict:
    conn = psycopg2.connect("postgresql://localhost/shared_db")  # same table again!
    cur.execute("SELECT id, email, billing_address FROM users WHERE id = %s", (user_id,))

# Rename "email" to "contact_email" in the DB:
# All three services break. Three deployments must be synchronized.
# This is a distributed monolith — complexity of distribution, zero independence.
// All three services import from the same shared DB type:
// type UserRow = { id: string; email: string; password_hash: string;
//                 display_name: string; avatar_url: string; billing_address: string }

// service-auth
const user = await db.query("SELECT id, email, password_hash FROM users WHERE email=$1", [email]);

// service-profile
const profile = await db.query("SELECT id, display_name, avatar_url FROM users WHERE id=$1", [id]);

// service-billing
const contact = await db.query("SELECT id, email, billing_address FROM users WHERE id=$1", [id]);

// ALTER TABLE users RENAME email TO contact_email;
// → Three services fail. Three teams must coordinate a synchronized deployment.

✓La solución

Each service owns its own data store. They communicate through a published event contract — a stable, versioned DTO. A schema change in one service is invisible to the others.

Good

from dataclasses import dataclass

@dataclass
class UserRegisteredEvent:
    user_id: str
    email:   str  # minimal contract — only what both sides agree on

# service_auth — owns auth_db.credentials, publishes event on registration
def register_user(email: str, password: str) -> str:
    user_id = auth_repo.save_credentials(email, hash_password(password))
    event_bus.publish(UserRegisteredEvent(user_id=user_id, email=email))
    return user_id

# service_profile — subscribes, owns its own profile_db.profiles
def on_user_registered(event: UserRegisteredEvent) -> None:
    profile_repo.create_profile(user_id=event.user_id, display_name=event.email)

# service_billing — subscribes, owns its own billing_db.contacts
def on_user_registered(event: UserRegisteredEvent) -> None:
    billing_repo.create_contact(user_id=event.user_id, billing_email=event.email)

# Auth changes its credentials schema: Profile and Billing are unaffected.
# Each service is independently deployable and changeable.
// Stable, versioned event contract
export interface UserRegisteredEvent {
  readonly userId: string;
  readonly email:  string;
}

// service-auth: owns its own DB, publishes event
export class AuthService {
  async register(email: string, password: string): Promise<string> {
    const userId = await this.credentialsRepo.save({ email, passwordHash: hash(password) });
    await this.eventBus.publish<UserRegisteredEvent>("user.registered", { userId, email });
    return userId;
  }
}

// service-profile: subscribes, owns its own DB
export class ProfileEventHandler {
  async onUserRegistered(event: UserRegisteredEvent): Promise<void> {
    await this.profileRepo.create({ userId: event.userId, displayName: event.email });
  }
}

// service-billing: subscribes, owns its own DB
export class BillingEventHandler {
  async onUserRegistered(event: UserRegisteredEvent): Promise<void> {
    await this.billingRepo.create({ userId: event.userId, billingEmail: event.email });
  }
}
// Auth schema change: Profile and Billing are unaffected. True independence.

💡Conclusión clave

Si tus microservicios comparten un esquema de base de datos, tienes un monolito distribuido — toda la complejidad de la distribución sin ninguna independencia. El verdadero desacoplamiento requiere que cada servicio posea sus propios datos y se comunique a través de contratos de eventos estables, no tablas compartidas. La arquitectura es la Regla de Dependencia; los servicios son solo un detalle de despliegue.

🔧 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 tus microservicios comparten un esquema de base de datos, tienes un monolito distribuido — toda la complejidad de la distribución sin ninguna de la independencia.

✗ Tu versión

Servicios y Microservicios: ¿Son Arquitectura? — CleanKata — CleanKata