Microservices Preguntas de entrevista
Las entrevistas de Microservicios evalúan tu capacidad para diseñar y construir sistemas escalables y descentralizados. A menudo se dirigen a ingenieros senior y arquitectos con conocimiento profundo de computación distribuida, APIs y consistencia de datos. Espera una mezcla de discusiones arquitectónicas, análisis de trade-offs y escenarios prácticos de codificación.
Lo que cubren las entrevistas de Microservices
Descomposición y Diseño de Servicios
Cómo dividir un monolito en servicios, contextos delimitados y principios de diseño impulsado por el dominio.
Comunicación entre Servicios
Patrones síncronos vs asíncronos, API gateways, service mesh y protocolos de comunicación.
Gestión de Datos y Consistencia
Manejo de transacciones distribuidas, consistencia eventual, sagas y CQRS/event sourcing.
Despliegue y Observabilidad
Contenerización, orquestación (Kubernetes), monitoreo, registro y trazado en sistemas distribuidos.
Ejemplos de preguntas de entrevista sobre Microservices
- ¿Cómo descompondrías un gran monolito en microservicios? Explica tu proceso de decisión.Lo que cubre una buena respuesta
- Identificar los bounded contexts usando Domain-Driven Design (DDD).
- Analizar los patrones de acoplamiento y cohesión dentro del monolito.
- Priorizar la extracción por funcionalidad de negocio o equipo.
- Definir contratos de API y bases de datos para cada nuevo servicio.
- Planificar la migración incremental con feature toggles y strangler fig pattern.
Ver respuesta de ejemplo
El proceso comienza con un análisis del dominio para identificar bounded contexts mediante DDD, lo que permite dividir el monolito en módulos con alta cohesión y bajo acoplamiento. Luego, se examinan las dependencias entre estos módulos para asegurar que las extracciones no generen acoplamientos dañinos. Es común empezar por funcionalidades que cambian con frecuencia o que tienen requisitos de escalado independientes. Se definen contratos de API REST o eventos y se replantean las tablas compartidas para que cada servicio tenga su propio esquema de datos. La migración se realiza de forma progresiva usando el patrón strangler fig: se redirige tráfico gradualmente al nuevo servicio mientras el monolito sigue funcionando. Un error común es intentar extraer demasiados servicios a la vez, lo que aumenta la complejidad transaccional. También es clave invertir en pipelines de CI/CD y monitoreo desde el inicio.
- Explica la diferencia entre orquestación y coreografía en patrones saga. ¿Cuándo usarías cada uno?Lo que cubre una buena respuesta
- Orquestación: un coordinador central controla el flujo de la saga.
- Coreografía: cada servicio reacciona a eventos y decide el siguiente paso.
- Orquestación es mejor para flujos complejos con muchas decisiones.
- Coreografía ofrece mayor desacoplamiento y escalabilidad.
- En la orquestación hay un punto único de fallo; en coreografía, es más difícil depurar.
Ver respuesta de ejemplo
En el patrón saga, la orquestación utiliza un componente central (orquestador) que envía comandos a cada servicio y maneja compensaciones en caso de fallo. Esto centraliza la lógica del flujo, facilitando la gestión de transacciones largas y la visibilidad. En cambio, la coreografía se basa en eventos: cada servicio publica eventos cuando completa su operación, y otros servicios suscriben para reaccionar. Esto elimina el acoplamiento al orquestador, permitiendo que los servicios evolucionen de forma independiente. La orquestación es preferible cuando el flujo es complejo, requiere decisiones condicionales o se necesita un control estricto de las compensaciones. La coreografía es mejor para sistemas con alta escalabilidad y equipos autónomos, aunque puede ser más difícil de depurar y monitorear debido a la naturaleza distribuida del flujo. Un error común es subestimar la complejidad de manejar fallos parciales en coreografía, requiriendo herramientas de trazado avanzadas.
- Escribe una implementación simple de circuit breaker en tu lenguaje preferido (se acepta pseudocódigo).Lo que cubre una buena respuesta
- Implementar un circuit breaker con tres estados: cerrado, abierto y semiabierto.
- En estado cerrado, las llamadas pasan; si fallan un umbral, se abre el circuito.
- En abierto, las llamadas fallan inmediatamente; tras un timeout, pasa a semiabierto.
- En semiabierto, se permiten unas pocas llamadas de prueba; si tienen éxito, se cierra.
- Se debe incluir un mecanismo de reset automático y métricas de fallos.
Ver respuesta de ejemplo
El circuit breaker protege a los microservicios de fallos en cascada al evitar llamadas repetidas a un servicio que está fallando. La implementación típica tiene tres estados: cerrado (funcionamiento normal), abierto (rechaza llamadas inmediatamente) y semiabierto (permite un número limitado de llamadas para probar la recuperación). En el código se usan contadores de fallos y temporizadores para cambiar de estado. Es crucial configurar adecuadamente los umbrales y timeouts para evitar falsos positivos. Se recomienda exponer métricas del circuit breaker para monitoreo. Un error común es no resetear los contadores al pasar a semiabierto o no usar locks en entornos concurrentes. Además, se debe integrar con un mecanismo de fallback para ofrecer respuestas degradadas.
Solución de referenciapython import time import threading class CircuitBreaker: def __init__(self, failure_threshold=5, recovery_timeout=10, half_open_max_retries=3): self.failure_threshold = failure_threshold self.recovery_timeout = recovery_timeout self.half_open_max_retries = half_open_max_retries self.state = 'CLOSED' self.failure_count = 0 self.last_failure_time = None self.half_open_retries = 0 self.lock = threading.Lock() def call(self, func, *args, **kwargs): with self.lock: if self.state == 'OPEN': if time.time() - self.last_failure_time >= self.recovery_timeout: self.state = 'HALF_OPEN' self.half_open_retries = 0 else: raise Exception('Circuit breaker is open') try: result = func(*args, **kwargs) with self.lock: if self.state == 'HALF_OPEN': self.half_open_retries += 1 if self.half_open_retries >= self.half_open_max_retries: self.state = 'CLOSED' self.failure_count = 0 elif self.state == 'CLOSED': self.failure_count = 0 # Reset on success return result except Exception as e: with self.lock: self.failure_count += 1 self.last_failure_time = time.time() if self.failure_count >= self.failure_threshold: self.state = 'OPEN' raise e # Uso de ejemplo: # cb = CircuitBreaker() # try: # resultado = cb.call(mi_funcion_remota, arg1, arg2) # except Exception as e: # print('Error:', e) # Complejidad: O(1) por llamada (sin contar la función envuelta). Espacio O(1). - ¿Cómo manejas las transacciones distribuidas con consistencia eventual? Proporciona un ejemplo concreto.Lo que cubre una buena respuesta
- Usar el patrón saga con compensaciones manuales o el patrón outbox.
- Emplear eventos de dominio y listeners para propagar cambios.
- Aceptar que la consistencia fuerte no es posible; diseñar para consistencia eventual.
- Implementar reconciliación periódica y detección de discrepancias.
- Ejemplo: un pedido que descuenta stock y cobra en diferentes servicios.
Ver respuesta de ejemplo
Para transacciones distribuidas con consistencia eventual, se prefiere el patrón saga, donde cada paso publica un evento y los siguientes se ejecutan de forma asíncrona. Si un paso falla, se ejecutan transacciones compensatorias para revertir los efectos de los pasos anteriores. Un ejemplo concreto es un sistema de e-commerce: cuando un cliente realiza un pedido, el servicio de pedidos crea el pedido (estado pendiente) y publica un evento 'PedidoCreado'. El servicio de inventario escucha y descuenta stock; si falla, publica 'StockInsuficiente', y el servicio de pedidos cancela el pedido. El servicio de pagos también escucha y procesa el cobro. Se usa el patrón outbox para garantizar la publicación fiable de eventos. Se debe diseñar la reconciliación para detectar estados inconsistentes, por ejemplo, ejecutando un job nocturno que compare pedidos cancelados con stock no revertido. Un error común es asumir que la consistencia eventual es instantánea; se debe educar al negocio sobre los retrasos y diseñar la UI en consecuencia.
- Diseña un API gateway para un sistema con 10+ microservicios. ¿Qué características incluiría?Lo que cubre una buena respuesta
- Enrutamiento inteligente de peticiones a los microservicios correspondientes.
- Autenticación y autorización centralizadas (JWT, OAuth2).
- Limitación de tasa (rate limiting) y control de cuotas.
- Agregación de respuestas de múltiples servicios en una sola respuesta.
- Monitoreo, logging y trazado distribuido integrados.
Ver respuesta de ejemplo
Un API gateway para un sistema con más de 10 microservicios debe incluir enrutamiento dinámico basado en la ruta o el encabezado, delegando la autenticación mediante un punto único de verificación de tokens JWT o integración con un proveedor OAuth2. Es esencial implementar rate limiting para proteger los servicios internos de abusos, así como balanceo de carga y circuit breakers para tolerancia a fallos. La agregación de respuestas (composición de APIs) reduce el overhead de múltiples llamadas desde el cliente. Además, debe exponer un portal de documentación Swagger/OpenAPI y gestionar versionado de APIs. Funcionalidades como transformación de protocolos (HTTP a gRPC) o caching de respuestas pueden mejorar el rendimiento. Para el monitoreo, se integran métricas (Prometheus) y logging centralizado (ELK). Un error común es convertir el gateway en un cuello de botella, por lo que debe ser stateless y escalable horizontalmente.
- Describe un escenario donde elegirías gRPC sobre REST para la comunicación entre servicios.Lo que cubre una buena respuesta
- gRPC es preferible cuando se requiere baja latencia y alto rendimiento.
- Es útil cuando los servicios están escritos en diferentes lenguajes que tienen buen soporte gRPC.
- Ideal para comunicación interna entre microservicios donde se necesita streaming bidireccional.
- Cuando el contrato de API es estricto y se beneficia de la generación de código.
- Evitar gRPC si los clientes son navegadores web sin proxy especial.
Ver respuesta de ejemplo
Elegiría gRPC sobre REST en escenarios donde la comunicación entre servicios requiere baja latencia y alto throughput, por ejemplo, en un sistema de procesamiento de datos en tiempo real. gRPC usa HTTP/2, que permite multiplexación y compresión de cabeceras, reduciendo la latencia. También es ideal cuando se necesita streaming bidireccional, como en un chat o notificaciones en vivo, ya que gRPC soporta streams de manera nativa. Si los equipos trabajan con diferentes lenguajes de programación, gRPC ofrece generación de código a partir de archivos .proto, garantizando tipos fuertes y contratos claros. Sin embargo, gRPC no es adecuado si los clientes son navegadores web sin un proxy como Envoy, ya que los navegadores no soportan HTTP/2 gRPC directamente (se necesita gRPC-Web). Tampoco es recomendable si se requiere una API pública fácil de consumir con herramientas REST. Un error común es elegir gRPC solo por moda sin evaluar si la complejidad adicional vale la pena.
- ¿Cómo implementarías el trazado distribuido a través de microservicios? ¿Qué herramientas usarías?Lo que cubre una buena respuesta
- Usar un identificador único de traza (trace ID) que se propaga entre servicios.
- Inyectar el trace ID en los encabezados HTTP (por ejemplo, 'X-Request-Id').
- Implementar spans para cada operación y enviarlos a un sistema de recolección.
- Herramientas: OpenTelemetry, Jaeger, Zipkin, AWS X-Ray.
- Instrumentar bibliotecas cliente y frameworks automáticamente.
Ver respuesta de ejemplo
Para implementar trazado distribuido, se debe propagar un trace ID único a través de todos los microservicios involucrados en una solicitud. Esto se logra inyectando el ID en encabezados HTTP (como 'X-Request-Id' o usando los estándares de W3C Trace-Context). Cada servicio crea spans (unidades de trabajo) que registran el tiempo de inicio, fin, y metadatos, y los envía a un backend centralizado. OpenTelemetry es la herramienta estándar actual, ya que proporciona SDKs para múltiples lenguajes y se integra con Jaeger o Zipkin para visualización. Se deben instrumentar tanto el código de negocio como las bibliotecas de acceso a bases de datos, cachés, y mensajería. Para facilitar, se puede usar instrumentación automática mediante agentes (Java, Python) o decoradores. Un error común es no propagar el contexto de traza en comunicación asíncrona (colas de mensajes), perdiendo la visibilidad. También es importante muestrear adecuadamente para no generar sobrecarga de almacenamiento.
- Codifica un endpoint de health check que reporte el estado de las dependencias posteriores (por ejemplo, base de datos, caché, otros servicios).Lo que cubre una buena respuesta
- Endpoint HTTP GET /health que devuelve estado global y de dependencias.
- Verificar conectividad a base de datos (conexión simple o ping).
- Verificar estado de caché (Redis, Memcached) mediante comando PING.
- Verificar disponibilidad de otros servicios internos mediante llamada ligera.
- Incluir latencia opcional y permitir checks detallados en modo 'verbose'.
Ver respuesta de ejemplo
Un endpoint de health check debe exponer un estado general del servicio y el estado de cada dependencia externa (base de datos, caché, otros microservicios). La implementación típica para cada dependencia realiza una operación ligera: un SELECT 1 para base de datos, un PING para Redis, y una petición HTTP GET a un endpoint de health de otros servicios. Es importante manejar timeouts para no bloquear el health check. Se recomienda devolver un código 200 si todo está bien, y 503 si alguna dependencia crítica falla, permitiendo que los orquestadores (Kubernetes) actúen. Se puede incluir un parámetro 'verbose' para obtener detalles adicionales. Un error común es no separar los checks de dependencias internas vs externas, o hacer que el health check dependa de otros servicios que a su vez dependen de él (circular), causando fallos en cascada. También se debe evitar que el health check tenga efectos secundarios (como escribir en BD).
Solución de referenciapython from flask import Flask, jsonify import redis import psycopg2 import requests app = Flask(__name__) def check_database(): try: conn = psycopg2.connect(host='db_host', dbname='mydb', user='user', password='pass') cursor = conn.cursor() cursor.execute('SELECT 1') cursor.close() conn.close() return True, 'Database OK' except Exception as e: return False, str(e) def check_cache(): try: r = redis.Redis(host='cache_host', port=6379, socket_connect_timeout=2) r.ping() return True, 'Cache OK' except Exception as e: return False, str(e) def check_service(service_url): try: response = requests.get(service_url + '/health/healthz', timeout=3) if response.status_code == 200: return True, 'Service OK' else: return False, f'Service returned {response.status_code}' except Exception as e: return False, str(e) @app.route('/health') def health(): db_ok, db_msg = check_database() cache_ok, cache_msg = check_cache() # Suponiendo que tenemos un servicio interno 'orders' orders_ok, orders_msg = check_service('http://orders-service') overall_status = 'UP' if (db_ok and cache_ok and orders_ok) else 'DEGRADED' response = { 'status': overall_status, 'checks': { 'database': {'status': 'UP' if db_ok else 'DOWN', 'message': db_msg}, 'cache': {'status': 'UP' if cache_ok else 'DOWN', 'message': cache_msg}, 'orders': {'status': 'UP' if orders_ok else 'DOWN', 'message': orders_msg} } } return jsonify(response), 200 if overall_status == 'UP' else 503 if __name__ == '__main__': app.run(port=8080) # Complejidad: O(dependencias) en tiempo, O(1) en espacio. Depende del número de dependencias verificadas.
Cómo prepararse
- Domina los fundamentos de sistemas distribuidos: teorema CAP, modelos de consistencia y tolerancia a fallos.
- Practica la descomposición de servicios usando diseño impulsado por el dominio (entidades, agregados, contextos delimitados).
- Comprende la consistencia eventual y cómo implementar patrones saga (por ejemplo, con transacciones de compensación).
- Prepárate para discutir trade-offs: orquestación vs coreografía, síncrono vs asíncrono, stateful vs stateless.
- Prepárate para problemas de diseño de sistemas: practica dibujar en una pizarra una arquitectura de microservicios con enfoque en comunicación, datos y observabilidad.
Preguntas frecuentes
¿Cuáles son los conceptos más importantes para una entrevista de microservicios?
Los conceptos clave incluyen descomposición de servicios, comunicación entre servicios (REST, gRPC, mensajería), consistencia de datos (sagas, consistencia eventual) y observabilidad (registro, métricas, trazado).
¿Debo conocer Kubernetes para entrevistas de microservicios?
Sí, Kubernetes se usa a menudo para orquestar contenedores. Debes entender pods, services, deployments y cómo gestionar microservicios en un clúster.
¿En qué se diferencian las entrevistas de microservicios de las entrevistas generales de diseño de sistemas?
Las entrevistas de microservicios se centran específicamente en desafíos de arquitectura distribuida como límites de servicios, distribución de datos y resiliencia de red, mientras que el diseño de sistemas general puede cubrir también monolitos.
¿Qué preguntas de codificación puedo esperar en una entrevista de microservicios?
Pueden pedirte que implementes un circuit breaker, un mecanismo de reintento o un endpoint simple de salud del servicio. También, puede que necesites diseñar una API o escribir código para comunicación entre servicios.
¿Cómo puedo demostrar experiencia con microservicios si no he trabajado con ellos en producción?
Construye un pequeño proyecto usando patrones de microservicios (por ejemplo, con Docker, Node.js/Spring Boot y una cola de mensajes). Discute los trade-offs y las lecciones aprendidas. El conocimiento teórico combinado con un proyecto personal puede ser convincente.
Practica preguntas sobre Microservices con retroalimentación instantánea de IA
Sube tu currículum, obtén una entrevista simulada personalizada y ve exactamente qué mejorar — gratis para empezar.