Top 100 Preguntas Completas de Spring
Las 100 Preguntas Completas de Spring
Section titled “Las 100 Preguntas Completas de Spring”Esta guía cubre desde fundamentos hasta temas avanzados. Las preguntas 1-50 están en las guías anteriores.
Spring Boot Avanzado (51-65)
Section titled “Spring Boot Avanzado (51-65)”51. ¿Qué es Spring Boot Actuator?
Section titled “51. ¿Qué es Spring Boot Actuator?”¿Qué es? Módulo que expone endpoints para monitorear y gestionar la aplicación en producción.
¿Para qué sirve? Health checks, métricas, info de la app, gestión de logs.
// Dependencia// spring-boot-starter-actuator
management:endpoints: web: exposure: include: health, info, metrics, envendpoint: health: show-details: always
// Endpoints disponibles:// /actuator/health - Estado de la aplicación// /actuator/info - Información de la app// /actuator/metrics - Métricas// /actuator/env - Variables de entorno// /actuator/loggers - Configuración de logs// /actuator/httptrace - Trazas HTTP52. ¿Cómo crear un endpoint personalizado de Actuator?
Section titled “52. ¿Cómo crear un endpoint personalizado de Actuator?”¿Cómo? Usar @Endpoint con operaciones @ReadOperation, @WriteOperation, @DeleteOperation.
@Component@Endpoint(id = "custom")public class CustomEndpoint {
@ReadOperation public Map<String, Object> info() { return Map.of( "status", "OK", "timestamp", LocalDateTime.now() ); }
@WriteOperation public void update(@Selector String name, String value) { // Actualizar configuración }
@DeleteOperation public void delete(@Selector String name) { // Eliminar }}53. ¿Qué es Spring Boot DevTools?
Section titled “53. ¿Qué es Spring Boot DevTools?”¿Qué es? Herramientas para mejorar la experiencia de desarrollo.
Incluye: Restart automático, LiveReload, caché deshabilitado para templates.
// Dependencia// spring-boot-devtools
// Características:// - Restart automático al cambiar código// - LiveReload del navegador// - Configuraciones de desarrollo// - Cache deshabilitado para templates
// application.ymlspring:devtools: restart: enabled: true exclude: static/**,public/** livereload: enabled: true54. ¿Cómo externalizar configuración?
Section titled “54. ¿Cómo externalizar configuración?”¿Qué es? Separar configuración del código para diferentes entornos (dev, prod, etc.).
Orden de precedencia: Command line > Env vars > application-[profile].yml > application.yml.
// Orden de precedencia (mayor a menor):// 1. Command line arguments// 2. SPRING_APPLICATION_JSON// 3. ServletConfig/ServletContext parameters// 4. JNDI attributes// 5. System properties// 6. OS environment variables// 7. application-[profile].properties// 8. application.properties// 9. @PropertySource// 10. Default properties
// Múltiples archivosspring:config: import: - classpath:db.yml - classpath:security.yml - optional:file:./config/custom.yml55. ¿Qué es @ConfigurationProperties validation?
Section titled “55. ¿Qué es @ConfigurationProperties validation?”¿Qué es? Validar propiedades de configuración al iniciar la aplicación.
¿Cómo? Usar @Validated + anotaciones de Bean Validation (@NotBlank, @Min, etc.).
@Configuration@ConfigurationProperties(prefix = "app")@Validatedpublic class AppProperties {
@NotBlank private String nombre;
@Min(1) @Max(100) private int maxConnections;
@Valid private Security security;
public static class Security { @NotBlank private String secretKey;
@DurationMin(seconds = 60) private Duration tokenExpiration; }}56. ¿Cómo crear un starter personalizado?
Section titled “56. ¿Cómo crear un starter personalizado?”¿Qué es? Librería que autoconfigura funcionalidad en Spring Boot.
Pasos: 1) Módulo autoconfigure con @Configuration, 2) Registrar en META-INF, 3) Módulo starter con dependencias.
// 1. Crear módulo autoconfigure@Configuration@ConditionalOnClass(MiServicio.class)@EnableConfigurationProperties(MiProperties.class)public class MiAutoConfiguration {
@Bean @ConditionalOnMissingBean public MiServicio miServicio(MiProperties props) { return new MiServicio(props); }}
// 2. Registrar en META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importscom.ejemplo.MiAutoConfiguration
// 3. Crear módulo starter con dependencia a autoconfigure// pom.xml del starter solo tiene dependencias57. ¿Qué es GraalVM Native Image?
Section titled “57. ¿Qué es GraalVM Native Image?”¿Qué es? Compilar aplicación Spring Boot a ejecutable nativo (sin JVM).
Ventajas: Arranque instantáneo (~50ms), menor memoria. Desventajas: Compilación lenta, limitaciones de reflection.
// Compilar a ejecutable nativo// Ventajas: arranque instantáneo, menor memoria// Desventajas: tiempo de compilación, limitaciones de reflection
// pom.xml<plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId></plugin>
// Compilarmvn -Pnative native:compile
// Configurar reflection@RegisterReflectionForBinding(Usuario.class)public class MiConfig {}58. ¿Qué es Spring Boot Docker Compose?
Section titled “58. ¿Qué es Spring Boot Docker Compose?”¿Qué es? Integración nativa con Docker Compose (Spring Boot 3.1+).
¿Para qué sirve? Iniciar/detener contenedores automáticamente al arrancar la app.
// Spring Boot 3.1+ - integración con Docker Compose
// compose.yml en raíz del proyectoservices:postgres: image: postgres:15 environment: POSTGRES_DB: midb POSTGRES_USER: user POSTGRES_PASSWORD: pass ports: - "5432:5432"
// application.ymlspring:docker: compose: enabled: true lifecycle-management: start-and-stop
// Spring Boot inicia/detiene contenedores automáticamente59. ¿Qué es Problem Details (RFC 7807)?
Section titled “59. ¿Qué es Problem Details (RFC 7807)?”¿Qué es? Estándar para respuestas de error HTTP estructuradas.
¿Para qué sirve? Errores consistentes con type, title, status, detail, instance.
// Spring Boot 3+ - respuestas de error estandarizadas
// Habilitarspring:mvc: problemdetails: enabled: true
// Respuesta automática{"type": "about:blank","title": "Not Found","status": 404,"detail": "Usuario no encontrado","instance": "/api/usuarios/123"}
// Personalizar@RestControllerAdvicepublic class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(UsuarioNoEncontradoException.class) public ProblemDetail handleUsuarioNoEncontrado(UsuarioNoEncontradoException ex) { ProblemDetail problem = ProblemDetail.forStatusAndDetail( HttpStatus.NOT_FOUND, ex.getMessage()); problem.setTitle("Usuario No Encontrado"); problem.setProperty("usuarioId", ex.getUsuarioId()); return problem; }}60. ¿Qué es Virtual Threads en Spring Boot?
Section titled “60. ¿Qué es Virtual Threads en Spring Boot?”¿Qué es? Soporte para Virtual Threads de Java 21 en Spring Boot 3.2+.
Beneficio: Cada request usa un virtual thread. Mejor escalabilidad sin cambiar código.
// Spring Boot 3.2+ con Java 21
spring:threads: virtual: enabled: true
// Ahora cada request usa un virtual thread// Mejor para I/O-bound workloads// No cambiar código, solo configuración
// Tomcat con virtual threadsserver:tomcat: threads: max: 200 # Con virtual threads, este límite es menos relevante61. ¿Qué es Observability en Spring Boot?
Section titled “61. ¿Qué es Observability en Spring Boot?”¿Qué es? Capacidad de entender el estado interno del sistema mediante métricas, logs y traces.
Spring Boot 3+: Integración con Micrometer para observabilidad unificada.
// Spring Boot 3+ - Observability integrada
// Dependencias// micrometer-tracing-bridge-brave (o otel)// micrometer-observation
// application.ymlmanagement:tracing: sampling: probability: 1.0observations: key-values: application: mi-app
// Crear observaciones personalizadas@Componentpublic class MiServicio {
private final ObservationRegistry registry;
public void procesar() { Observation.createNotStarted("mi.operacion", registry) .lowCardinalityKeyValue("tipo", "procesamiento") .observe(() -> { // Lógica }); }}62. ¿Qué es HTTP Interface Client?
Section titled “62. ¿Qué es HTTP Interface Client?”¿Qué es? Cliente HTTP declarativo usando interfaces (similar a Feign pero nativo).
¿Para qué sirve? Definir clientes HTTP con anotaciones, sin implementación manual.
// Spring 6+ - Cliente HTTP declarativo
@HttpExchange("/api/usuarios")public interface UsuarioClient {
@GetExchange List<Usuario> listar();
@GetExchange("/{id}") Usuario buscar(@PathVariable Long id);
@PostExchange Usuario crear(@RequestBody UsuarioDTO dto);
@DeleteExchange("/{id}") void eliminar(@PathVariable Long id);}
// Configuración@Configurationpublic class ClientConfig {
@Bean public UsuarioClient usuarioClient(WebClient.Builder builder) { WebClient webClient = builder.baseUrl("http://api.ejemplo.com").build();
HttpServiceProxyFactory factory = HttpServiceProxyFactory .builderFor(WebClientAdapter.create(webClient)) .build();
return factory.createClient(UsuarioClient.class); }}63. ¿Qué es RestClient?
Section titled “63. ¿Qué es RestClient?”¿Qué es? Cliente HTTP fluido de Spring 6.1+. Reemplazo moderno de RestTemplate.
Ventajas: API fluida, mejor integración con Spring, soporte para Virtual Threads.
// Spring 6.1+ - Cliente HTTP fluido (reemplazo de RestTemplate)
@Configurationpublic class ClientConfig {
@Bean public RestClient restClient(RestClient.Builder builder) { return builder .baseUrl("http://api.ejemplo.com") .defaultHeader("Authorization", "Bearer token") .build(); }}
@Servicepublic class UsuarioService {
private final RestClient restClient;
public Usuario buscar(Long id) { return restClient.get() .uri("/usuarios/{id}", id) .retrieve() .body(Usuario.class); }
public Usuario crear(UsuarioDTO dto) { return restClient.post() .uri("/usuarios") .contentType(MediaType.APPLICATION_JSON) .body(dto) .retrieve() .body(Usuario.class); }}64. ¿Qué es SSL Bundles?
Section titled “64. ¿Qué es SSL Bundles?”¿Qué es? Gestión simplificada de certificados SSL en Spring Boot 3.1+.
¿Para qué sirve? Configurar keystores/truststores de forma declarativa y reutilizable.
// Spring Boot 3.1+ - Gestión simplificada de SSL
spring:ssl: bundle: jks: mi-bundle: keystore: location: classpath:keystore.jks password: secret truststore: location: classpath:truststore.jks password: secret
// Usar en RestClient@Beanpublic RestClient restClient(RestClient.Builder builder, SslBundles sslBundles) { return builder .baseUrl("https://api.ejemplo.com") .apply(restClientSsl -> restClientSsl.sslBundle(sslBundles.getBundle("mi-bundle"))) .build();}65. ¿Qué es Service Connection?
Section titled “65. ¿Qué es Service Connection?”¿Qué es? Autoconfiguración de conexiones a servicios externos (BD, caches, etc.).
¿Para qué sirve? Con Testcontainers/Docker Compose, configura propiedades automáticamente.
// Spring Boot 3.1+ - Conexión automática a servicios
// Con Testcontainers@SpringBootTest@Testcontainersclass IntegrationTest {
@Container @ServiceConnection static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
// No necesita @DynamicPropertySource // Spring Boot configura automáticamente spring.datasource.*}
// Con Docker Compose// compose.ymlservices:postgres: image: postgres:15 labels: org.springframework.boot.service-connection: postgresSpring Cloud (66-80)
Section titled “Spring Cloud (66-80)”66. ¿Qué es Spring Cloud?
Section titled “66. ¿Qué es Spring Cloud?”¿Qué es? Conjunto de proyectos para construir microservicios y sistemas distribuidos.
**Incluye:
- Config Server: Configuración centralizada
- Service Discovery: Eureka, Consul
- Load Balancing: Spring Cloud LoadBalancer
- Circuit Breaker: Resilience4j
- API Gateway: Spring Cloud Gateway
- Distributed Tracing: Micrometer Tracing
67. ¿Qué es Spring Cloud Config?
Section titled “67. ¿Qué es Spring Cloud Config?”¿Qué es? Servidor centralizado de configuración para microservicios.
¿Para qué sirve? Configuración externa en Git, cambios sin redesplegar, perfiles por entorno.
// Config Server@SpringBootApplication@EnableConfigServerpublic class ConfigServerApplication {}
// application.yml del serverspring:cloud: config: server: git: uri: https://github.com/mi-org/config-repo default-label: main
// Clientespring:config: import: configserver:http://localhost:8888application: name: mi-servicio
// Refrescar configuración@RefreshScope@RestControllerpublic class MiController { @Value("${mi.propiedad}") private String propiedad;}68. ¿Qué es Service Discovery con Eureka?
Section titled “68. ¿Qué es Service Discovery con Eureka?”¿Qué es? Servidor de registro donde los microservicios se registran y descubren entre sí.
¿Para qué sirve? No hardcodear URLs, balanceo de carga, alta disponibilidad.
// Eureka Server@SpringBootApplication@EnableEurekaServerpublic class EurekaServerApplication {}
// application.yml del servereureka:client: register-with-eureka: false fetch-registry: false
// Cliente (servicio)@SpringBootApplication@EnableDiscoveryClientpublic class MiServicioApplication {}
// application.yml del clienteeureka:client: service-url: defaultZone: http://localhost:8761/eureka/instance: prefer-ip-address: true69. ¿Qué es Spring Cloud Gateway?
Section titled “69. ¿Qué es Spring Cloud Gateway?”¿Qué es? API Gateway reactivo para microservicios. Punto de entrada único.
Funciones: Routing, filtros, rate limiting, circuit breaker, autenticación.
@SpringBootApplicationpublic class GatewayApplication {}
spring:cloud: gateway: routes: - id: usuarios-service uri: lb://USUARIOS-SERVICE predicates: - Path=/api/usuarios/** filters: - StripPrefix=1 - AddRequestHeader=X-Request-Source, gateway
- id: pedidos-service uri: lb://PEDIDOS-SERVICE predicates: - Path=/api/pedidos/** - Method=GET,POST filters: - CircuitBreaker=name=pedidosCB,fallbackUri=forward:/fallback
// Filtro personalizado@Componentpublic class LoggingFilter implements GlobalFilter {
@Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("Request: {}", exchange.getRequest().getPath()); return chain.filter(exchange); }}70. ¿Qué es Resilience4j?
Section titled “70. ¿Qué es Resilience4j?”¿Qué es? Librería de tolerancia a fallos para Java. Reemplazo de Hystrix.
Patrones: Circuit Breaker, Retry, Rate Limiter, Bulkhead, Time Limiter.
// Circuit Breaker@Servicepublic class UsuarioService {
@CircuitBreaker(name = "usuariosCB", fallbackMethod = "fallback") public Usuario buscar(Long id) { return restClient.get() .uri("/usuarios/{id}", id) .retrieve() .body(Usuario.class); }
public Usuario fallback(Long id, Exception ex) { return new Usuario(id, "Usuario no disponible"); }}
// application.ymlresilience4j:circuitbreaker: instances: usuariosCB: sliding-window-size: 10 failure-rate-threshold: 50 wait-duration-in-open-state: 10s permitted-number-of-calls-in-half-open-state: 3
retry: instances: usuariosRetry: max-attempts: 3 wait-duration: 1s
ratelimiter: instances: usuariosRL: limit-for-period: 10 limit-refresh-period: 1s71. ¿Qué es Spring Cloud LoadBalancer?
Section titled “71. ¿Qué es Spring Cloud LoadBalancer?”¿Qué es? Balanceador de carga del lado del cliente. Reemplazo de Ribbon.
¿Cómo funciona? Usa nombre del servicio en vez de URL, resuelve instancias vía Service Discovery.
// Configuración automática con service discovery
@Configurationpublic class LoadBalancerConfig {
@Bean @LoadBalanced public RestClient.Builder restClientBuilder() { return RestClient.builder(); }}
@Servicepublic class UsuarioService {
private final RestClient restClient;
public Usuario buscar(Long id) { // Usa nombre del servicio, no URL return restClient.get() .uri("http://USUARIOS-SERVICE/usuarios/{id}", id) .retrieve() .body(Usuario.class); }}
// Configuración personalizada@Configurationpublic class CustomLoadBalancerConfig {
@Bean public ServiceInstanceListSupplier serviceInstanceListSupplier( ConfigurableApplicationContext context) { return ServiceInstanceListSupplier.builder() .withDiscoveryClient() .withHealthChecks() .withCaching() .build(context); }}72. ¿Qué es Spring Cloud Stream?
Section titled “72. ¿Qué es Spring Cloud Stream?”¿Qué es? Framework para construir microservicios event-driven con message brokers.
Soporta: Kafka, RabbitMQ, otros. Abstracción sobre el broker específico.
// Mensajería con Kafka/RabbitMQ
// Productor@Componentpublic class PedidoProducer {
private final StreamBridge streamBridge;
public void enviar(Pedido pedido) { streamBridge.send("pedidos-out-0", pedido); }}
// Consumidor@Configurationpublic class PedidoConsumer {
@Bean public Consumer<Pedido> procesarPedido() { return pedido -> { System.out.println("Procesando: " + pedido); }; }}
// application.ymlspring:cloud: stream: bindings: pedidos-out-0: destination: pedidos-topic procesarPedido-in-0: destination: pedidos-topic group: pedidos-group kafka: binder: brokers: localhost:909273. ¿Qué es Spring Cloud Function?
Section titled “73. ¿Qué es Spring Cloud Function?”¿Qué es? Framework para escribir funciones serverless portables.
Despliega en: AWS Lambda, Azure Functions, GCP Functions, Knative.
// Funciones serverless
@SpringBootApplicationpublic class FunctionApplication {
@Bean public Function<String, String> uppercase() { return value -> value.toUpperCase(); }
@Bean public Consumer<String> log() { return value -> System.out.println("Recibido: " + value); }
@Bean public Supplier<String> hello() { return () -> "Hello World"; }}
// Composiciónspring:cloud: function: definition: uppercase|log
// Desplegar en AWS Lambda, Azure Functions, etc.74. ¿Qué es Distributed Tracing?
Section titled “74. ¿Qué es Distributed Tracing?”¿Qué es? Seguimiento de requests a través de múltiples microservicios.
¿Para qué sirve? Debugging, identificar cuellos de botella, entender flujos.
// Dependencias// micrometer-tracing-bridge-brave// zipkin-reporter-brave
// application.ymlmanagement:tracing: sampling: probability: 1.0zipkin: tracing: endpoint: http://localhost:9411/api/v2/spans
// Propagación automática de trace IDs entre servicios// Logs incluyen traceId y spanId// logging.pattern.level: %5p [appName,traceId,spanId]
// Span personalizado@Observed(name = "mi.operacion")public void miMetodo() { // Automáticamente crea span}75. ¿Qué es Spring Cloud Kubernetes?
Section titled “75. ¿Qué es Spring Cloud Kubernetes?”¿Qué es? Integración nativa de Spring Boot con Kubernetes.
Incluye: Service Discovery via K8s, ConfigMaps como properties, Secrets.
// Integración nativa con Kubernetes
// Service Discoveryspring:cloud: kubernetes: discovery: enabled: true all-namespaces: false
// Config Maps como PropertySourcespring:cloud: kubernetes: config: enabled: true name: mi-app-config secrets: enabled: true name: mi-app-secrets
// Reload automáticospring:cloud: kubernetes: reload: enabled: true mode: polling period: 1500076. ¿Qué es Spring Cloud Vault?
Section titled “76. ¿Qué es Spring Cloud Vault?”¿Qué es? Integración con HashiCorp Vault para gestión de secretos.
¿Para qué sirve? Secretos seguros, rotación automática de credenciales.
// Integración con HashiCorp Vault
spring:cloud: vault: uri: http://localhost:8200 token: mi-token kv: enabled: true backend: secret application-name: mi-app
// Secrets disponibles como properties@Value("${database.password}")private String dbPassword;
// Rotación automática de credencialesspring:cloud: vault: database: enabled: true role: mi-role backend: database77. ¿Qué es Spring Cloud OpenFeign?
Section titled “77. ¿Qué es Spring Cloud OpenFeign?”¿Qué es? Cliente HTTP declarativo con soporte para circuit breaker y load balancing.
Alternativa: HTTP Interface Client (nativo desde Spring 6).
// Cliente HTTP declarativo (alternativa a HTTP Interface)
@FeignClient(name = "usuarios-service", fallback = UsuarioClientFallback.class)public interface UsuarioClient {
@GetMapping("/usuarios/{id}") Usuario buscar(@PathVariable Long id);
@PostMapping("/usuarios") Usuario crear(@RequestBody UsuarioDTO dto);}
@Componentpublic class UsuarioClientFallback implements UsuarioClient {
@Override public Usuario buscar(Long id) { return new Usuario(id, "Fallback"); }
@Override public Usuario crear(UsuarioDTO dto) { throw new ServiceUnavailableException(); }}
// Configuración@EnableFeignClients@SpringBootApplicationpublic class Application {}78. ¿Qué es el patrón Saga?
Section titled “78. ¿Qué es el patrón Saga?”¿Qué es? Patrón para manejar transacciones distribuidas mediante secuencia de transacciones locales.
Tipos: Orquestación (coordinador central), Coreografía (eventos).
// Saga para transacciones distribuidas
// Orquestación (coordinador central)@Servicepublic class PedidoSaga {
public void crearPedido(PedidoDTO dto) { try { // Paso 1: Reservar inventario inventarioService.reservar(dto.getProductos());
// Paso 2: Procesar pago pagoService.procesar(dto.getPago());
// Paso 3: Crear pedido pedidoService.crear(dto);
} catch (PagoException e) { // Compensación inventarioService.liberarReserva(dto.getProductos()); throw e; } }}
// Coreografía (eventos)@Componentpublic class InventarioListener {
@EventListener public void onPedidoCreado(PedidoCreado event) { try { reservar(event.getProductos()); eventPublisher.publish(new InventarioReservado(event.getPedidoId())); } catch (Exception e) { eventPublisher.publish(new InventarioFallido(event.getPedidoId())); } }}79. ¿Qué es el patrón Outbox?
Section titled “79. ¿Qué es el patrón Outbox?”¿Qué es? Patrón que garantiza consistencia entre BD y mensajería usando una tabla intermedia.
¿Cómo? Guardar evento en tabla outbox en misma transacción, proceso separado publica a broker.
// Garantizar consistencia entre BD y mensajería
@Entity@Table(name = "outbox")public class OutboxEvent { @Id private UUID id; private String aggregateType; private String aggregateId; private String type; private String payload; private LocalDateTime createdAt; private boolean processed;}
@Service@Transactionalpublic class PedidoService {
public Pedido crear(PedidoDTO dto) { // Guardar pedido y evento en misma transacción Pedido pedido = pedidoRepository.save(new Pedido(dto));
OutboxEvent event = new OutboxEvent(); event.setAggregateType("Pedido"); event.setAggregateId(pedido.getId().toString()); event.setType("PedidoCreado"); event.setPayload(objectMapper.writeValueAsString(pedido));
outboxRepository.save(event);
return pedido; }}
// Proceso separado lee outbox y publica a Kafka@Scheduled(fixedDelay = 1000)public void processOutbox() { List<OutboxEvent> events = outboxRepository.findByProcessedFalse(); for (OutboxEvent event : events) { kafkaTemplate.send(event.getType(), event.getPayload()); event.setProcessed(true); outboxRepository.save(event); }}80. ¿Qué es CQRS con Spring?
Section titled “80. ¿Qué es CQRS con Spring?”¿Qué es? Command Query Responsibility Segregation: separar modelos de lectura y escritura.
¿Para qué sirve? Escalar lecturas/escrituras independientemente, optimizar cada modelo.
// Command Query Responsibility Segregation
// Commands (escritura)@Servicepublic class PedidoCommandService {
private final PedidoRepository repository; private final EventPublisher eventPublisher;
@Transactional public void crear(CrearPedidoCommand command) { Pedido pedido = new Pedido(command); repository.save(pedido); eventPublisher.publish(new PedidoCreado(pedido)); }}
// Queries (lectura) - puede usar BD diferente@Servicepublic class PedidoQueryService {
private final PedidoReadRepository readRepository; // Elasticsearch, MongoDB, etc.
public PedidoDTO buscar(Long id) { return readRepository.findById(id); }
public List<PedidoDTO> buscarPorCliente(Long clienteId) { return readRepository.findByClienteId(clienteId); }}
// Sincronizar modelo de lectura@Componentpublic class PedidoProjection {
@EventListener public void on(PedidoCreado event) { PedidoReadModel model = new PedidoReadModel(event); readRepository.save(model); }}Arquitectura y Mejores Prácticas (81-100)
Section titled “Arquitectura y Mejores Prácticas (81-100)”81. ¿Cómo estructurar un proyecto Spring Boot?
Section titled “81. ¿Cómo estructurar un proyecto Spring Boot?”Estructura por capas: controller, service, repository, model, config, exception.
Alternativa: Estructura por features/módulos para proyectos grandes.
src/main/java/com/ejemplo/├── MiAplicacion.java├── config/│ ├── SecurityConfig.java│ └── WebConfig.java├── controller/│ └── UsuarioController.java├── service/│ ├── UsuarioService.java│ └── impl/│ └── UsuarioServiceImpl.java├── repository/│ └── UsuarioRepository.java├── model/│ ├── entity/│ │ └── Usuario.java│ └── dto/│ └── UsuarioDTO.java├── exception/│ ├── UsuarioNoEncontradoException.java│ └── GlobalExceptionHandler.java└── util/ └── Mapper.java82. ¿Qué es Hexagonal Architecture con Spring?
Section titled “82. ¿Qué es Hexagonal Architecture con Spring?”¿Qué es? Arquitectura de puertos y adaptadores. El dominio en el centro, sin dependencias externas.
Beneficios: Testabilidad, independencia de frameworks, flexibilidad.
// Puertos y Adaptadores
// Domain (centro)com.ejemplo.domain/├── model/│ └── Usuario.java├── port/│ ├── in/│ │ └── UsuarioUseCase.java│ └── out/│ └── UsuarioRepository.java└── service/ └── UsuarioService.java
// Adapters (exterior)com.ejemplo.adapter/├── in/│ └── web/│ └── UsuarioController.java└── out/ └── persistence/ ├── UsuarioJpaRepository.java └── UsuarioRepositoryAdapter.java
// El dominio no depende de frameworkspublic interface UsuarioRepository { Usuario save(Usuario usuario); Optional<Usuario> findById(Long id);}
// Adapter implementa el puerto@Repositorypublic class UsuarioRepositoryAdapter implements UsuarioRepository {
private final UsuarioJpaRepository jpaRepository;
@Override public Usuario save(Usuario usuario) { return jpaRepository.save(usuario); }}83. ¿Cómo implementar API Versioning?
Section titled “83. ¿Cómo implementar API Versioning?”Estrategias: Por URL (/v1/), por Header (X-API-VERSION), por Media Type.
Recomendación: URL versioning es más simple y visible.
// Por URL@RestController@RequestMapping("/api/v1/usuarios")public class UsuarioControllerV1 {}
@RestController@RequestMapping("/api/v2/usuarios")public class UsuarioControllerV2 {}
// Por Header@GetMapping(value = "/usuarios", headers = "X-API-VERSION=1")public List<UsuarioV1> listarV1() {}
@GetMapping(value = "/usuarios", headers = "X-API-VERSION=2")public List<UsuarioV2> listarV2() {}
// Por Media Type@GetMapping(value = "/usuarios", produces = "application/vnd.ejemplo.v1+json")public List<UsuarioV1> listarV1() {}
@GetMapping(value = "/usuarios", produces = "application/vnd.ejemplo.v2+json")public List<UsuarioV2> listarV2() {}84. ¿Cómo implementar HATEOAS?
Section titled “84. ¿Cómo implementar HATEOAS?”¿Qué es? Hypermedia As The Engine Of Application State: incluir links en respuestas REST.
Beneficio: Clientes descubren acciones disponibles dinámicamente.
// Dependencia: spring-boot-starter-hateoas
@RestController@RequestMapping("/api/usuarios")public class UsuarioController {
@GetMapping("/{id}") public EntityModel<Usuario> buscar(@PathVariable Long id) { Usuario usuario = service.buscar(id);
return EntityModel.of(usuario, linkTo(methodOn(UsuarioController.class).buscar(id)).withSelfRel(), linkTo(methodOn(UsuarioController.class).listar()).withRel("usuarios"), linkTo(methodOn(PedidoController.class).listarPorUsuario(id)).withRel("pedidos") ); }
@GetMapping public CollectionModel<EntityModel<Usuario>> listar() { List<EntityModel<Usuario>> usuarios = service.findAll().stream() .map(u -> EntityModel.of(u, linkTo(methodOn(UsuarioController.class).buscar(u.getId())).withSelfRel())) .toList();
return CollectionModel.of(usuarios, linkTo(methodOn(UsuarioController.class).listar()).withSelfRel()); }}85. ¿Cómo documentar APIs con OpenAPI/Swagger?
Section titled “85. ¿Cómo documentar APIs con OpenAPI/Swagger?”¿Qué es? Especificación estándar para documentar APIs REST.
Herramienta: springdoc-openapi genera documentación automáticamente.
// Dependencia: springdoc-openapi-starter-webmvc-ui
// Configuración@Configurationpublic class OpenApiConfig {
@Bean public OpenAPI customOpenAPI() { return new OpenAPI() .info(new Info() .title("Mi API") .version("1.0") .description("Documentación de la API")) .addSecurityItem(new SecurityRequirement().addList("Bearer")) .components(new Components() .addSecuritySchemes("Bearer", new SecurityScheme() .type(SecurityScheme.Type.HTTP) .scheme("bearer") .bearerFormat("JWT"))); }}
// Anotaciones en controlador@RestController@RequestMapping("/api/usuarios")@Tag(name = "Usuarios", description = "Gestión de usuarios")public class UsuarioController {
@Operation(summary = "Buscar usuario por ID") @ApiResponses({ @ApiResponse(responseCode = "200", description = "Usuario encontrado"), @ApiResponse(responseCode = "404", description = "Usuario no encontrado") }) @GetMapping("/{id}") public Usuario buscar(@PathVariable Long id) {}}
// URL: http://localhost:8080/swagger-ui.html86. ¿Cómo implementar Rate Limiting?
Section titled “86. ¿Cómo implementar Rate Limiting?”¿Qué es? Limitar número de requests por cliente/tiempo para proteger el servicio.
Herramientas: Bucket4j, Resilience4j RateLimiter, Redis.
// Con Bucket4j@Configurationpublic class RateLimitConfig {
@Bean public Bucket createBucket() { return Bucket.builder() .addLimit(Bandwidth.classic(100, Refill.intervally(100, Duration.ofMinutes(1)))) .build(); }}
@Componentpublic class RateLimitFilter extends OncePerRequestFilter {
private final Bucket bucket;
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { if (bucket.tryConsume(1)) { chain.doFilter(request, response); } else { response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); response.getWriter().write("Rate limit exceeded"); } }}87. ¿Cómo implementar Caching?
Section titled “87. ¿Cómo implementar Caching?”¿Qué es? Almacenar resultados frecuentes para reducir latencia y carga.
Anotaciones: @Cacheable, @CachePut, @CacheEvict.
@Configuration@EnableCachingpublic class CacheConfig {
@Bean public CacheManager cacheManager() { return new ConcurrentMapCacheManager("usuarios", "productos"); }}
@Servicepublic class UsuarioService {
@Cacheable(value = "usuarios", key = "#id") public Usuario buscar(Long id) { return repository.findById(id).orElseThrow(); }
@CachePut(value = "usuarios", key = "#usuario.id") public Usuario actualizar(Usuario usuario) { return repository.save(usuario); }
@CacheEvict(value = "usuarios", key = "#id") public void eliminar(Long id) { repository.deleteById(id); }
@CacheEvict(value = "usuarios", allEntries = true) public void limpiarCache() {}}
// Con Redisspring:cache: type: redisredis: host: localhost port: 637988. ¿Cómo implementar Health Checks personalizados?
Section titled “88. ¿Cómo implementar Health Checks personalizados?”¿Qué es? Verificar estado de dependencias (BD, servicios externos).
Uso: Kubernetes readiness/liveness probes, monitoreo.
@Componentpublic class DatabaseHealthIndicator implements HealthIndicator {
private final DataSource dataSource;
@Override public Health health() { try (Connection conn = dataSource.getConnection()) { if (conn.isValid(1)) { return Health.up() .withDetail("database", "PostgreSQL") .withDetail("status", "Connected") .build(); } } catch (SQLException e) { return Health.down() .withDetail("error", e.getMessage()) .build(); } return Health.down().build(); }}
// Health groupsmanagement:endpoint: health: group: readiness: include: db, redis liveness: include: ping89. ¿Cómo manejar archivos/uploads?
Section titled “89. ¿Cómo manejar archivos/uploads?”¿Cómo? MultipartFile para uploads, Resource para downloads.
Configuración: spring.servlet.multipart.max-file-size.
@RestController@RequestMapping("/api/archivos")public class ArchivoController {
@PostMapping("/upload") public ResponseEntity<String> upload(@RequestParam("file") MultipartFile file) { if (file.isEmpty()) { return ResponseEntity.badRequest().body("Archivo vacío"); }
String filename = StringUtils.cleanPath(file.getOriginalFilename()); Path path = Paths.get("uploads").resolve(filename); Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
return ResponseEntity.ok("Archivo subido: " + filename); }
@GetMapping("/download/{filename}") public ResponseEntity<Resource> download(@PathVariable String filename) { Path path = Paths.get("uploads").resolve(filename); Resource resource = new UrlResource(path.toUri());
return ResponseEntity.ok() .contentType(MediaType.APPLICATION_OCTET_STREAM) .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename="" + filename + """) .body(resource); }}
// Configuraciónspring:servlet: multipart: max-file-size: 10MB max-request-size: 10MB90. ¿Cómo implementar WebSocket?
Section titled “90. ¿Cómo implementar WebSocket?”¿Qué es? Comunicación bidireccional en tiempo real entre cliente y servidor.
Uso: Chat, notificaciones, dashboards en vivo.
@Configuration@EnableWebSocketMessageBrokerpublic class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/topic"); registry.setApplicationDestinationPrefixes("/app"); }
@Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws").withSockJS(); }}
@Controllerpublic class ChatController {
@MessageMapping("/chat.send") @SendTo("/topic/messages") public ChatMessage send(ChatMessage message) { return message; }
@MessageMapping("/chat.private.{userId}") public void sendPrivate(@DestinationVariable String userId, ChatMessage message) { messagingTemplate.convertAndSendToUser(userId, "/queue/private", message); }}91. ¿Cómo implementar Server-Sent Events?
Section titled “91. ¿Cómo implementar Server-Sent Events?”¿Qué es? Streaming unidireccional del servidor al cliente.
Diferencia con WebSocket: Más simple, solo servidor->cliente, HTTP estándar.
@RestController@RequestMapping("/api/eventos")public class EventoController {
@GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<ServerSentEvent<String>> stream() { return Flux.interval(Duration.ofSeconds(1)) .map(seq -> ServerSentEvent.<String>builder() .id(String.valueOf(seq)) .event("mensaje") .data("Evento " + seq) .build()); }}92. ¿Cómo implementar GraphQL con Spring?
Section titled “92. ¿Cómo implementar GraphQL con Spring?”¿Qué es? Lenguaje de consulta para APIs. Cliente pide exactamente lo que necesita.
Ventajas: No over-fetching, un endpoint, tipado fuerte.
// Dependencia: spring-boot-starter-graphql
type Query { usuario(id: ID!): Usuario usuarios: [Usuario]}
type Mutation { crearUsuario(input: UsuarioInput!): Usuario}
type Usuario { id: ID! nombre: String! email: String! pedidos: [Pedido]}
input UsuarioInput { nombre: String! email: String!}
// Controller@Controllerpublic class UsuarioGraphQLController {
@QueryMapping public Usuario usuario(@Argument Long id) { return service.buscar(id); }
@QueryMapping public List<Usuario> usuarios() { return service.findAll(); }
@MutationMapping public Usuario crearUsuario(@Argument UsuarioInput input) { return service.crear(input); }
@SchemaMapping public List<Pedido> pedidos(Usuario usuario) { return pedidoService.findByUsuarioId(usuario.getId()); }}93. ¿Cómo implementar Async Processing?
Section titled “93. ¿Cómo implementar Async Processing?”¿Qué es? Ejecutar tareas en segundo plano sin bloquear el thread principal.
¿Cómo? @Async + @EnableAsync. Retorna CompletableFuture o void.
@Configuration@EnableAsyncpublic class AsyncConfig {
@Bean public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(2); executor.setMaxPoolSize(10); executor.setQueueCapacity(500); executor.setThreadNamePrefix("Async-"); executor.initialize(); return executor; }}
@Servicepublic class EmailService {
@Async public CompletableFuture<Void> enviarEmail(String destinatario, String mensaje) { // Proceso lento return CompletableFuture.completedFuture(null); }
@Async public void enviarEmailSinRetorno(String destinatario, String mensaje) { // Fire and forget }}94. ¿Cómo implementar Batch Processing?
Section titled “94. ¿Cómo implementar Batch Processing?”¿Qué es? Procesar grandes volúmenes de datos en lotes (jobs).
Componentes: Job, Step, ItemReader, ItemProcessor, ItemWriter.
@Configuration@EnableBatchProcessingpublic class BatchConfig {
@Bean public Job importJob(JobRepository jobRepository, Step step1) { return new JobBuilder("importJob", jobRepository) .start(step1) .build(); }
@Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) .<Usuario, Usuario>chunk(100, transactionManager) .reader(reader()) .processor(processor()) .writer(writer()) .build(); }
@Bean public FlatFileItemReader<Usuario> reader() { return new FlatFileItemReaderBuilder<Usuario>() .name("usuarioReader") .resource(new ClassPathResource("usuarios.csv")) .delimited() .names("nombre", "email") .targetType(Usuario.class) .build(); }
@Bean public ItemProcessor<Usuario, Usuario> processor() { return usuario -> { usuario.setNombre(usuario.getNombre().toUpperCase()); return usuario; }; }
@Bean public JpaItemWriter<Usuario> writer(EntityManagerFactory emf) { return new JpaItemWriterBuilder<Usuario>() .entityManagerFactory(emf) .build(); }}95. ¿Cómo implementar Multi-tenancy?
Section titled “95. ¿Cómo implementar Multi-tenancy?”¿Qué es? Una aplicación sirve a múltiples clientes (tenants) aislados.
Estrategias: Por schema, por base de datos, por columna discriminadora.
// Por schemapublic class TenantContext { private static final ThreadLocal<String> TENANT = new ThreadLocal<>();
public static void setTenant(String tenant) { TENANT.set(tenant); }
public static String getTenant() { return TENANT.get(); }
public static void clear() { TENANT.remove(); }}
@Componentpublic class TenantFilter extends OncePerRequestFilter {
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { String tenant = request.getHeader("X-Tenant-ID"); TenantContext.setTenant(tenant); try { chain.doFilter(request, response); } finally { TenantContext.clear(); } }}
// Hibernate multi-tenancy@Configurationpublic class HibernateConfig {
@Bean public MultiTenantConnectionProvider connectionProvider() { return new SchemaMultiTenantConnectionProvider(); }
@Bean public CurrentTenantIdentifierResolver tenantResolver() { return () -> TenantContext.getTenant(); }}96. ¿Cómo implementar Feature Flags?
Section titled “96. ¿Cómo implementar Feature Flags?”¿Qué es? Activar/desactivar funcionalidades sin redesplegar.
Herramientas: Togglz, FF4j, LaunchDarkly.
// Con Togglz@Configurationpublic class FeatureConfig {
@Bean public FeatureManager featureManager() { return new FeatureManagerBuilder() .featureEnum(Features.class) .stateRepository(new FileBasedStateRepository(new File("features.properties"))) .build(); }}
public enum Features implements Feature { @EnabledByDefault NUEVA_UI,
BETA_FEATURE;}
@Servicepublic class MiServicio {
private final FeatureManager featureManager;
public void procesar() { if (featureManager.isActive(Features.NUEVA_UI)) { // Nueva lógica } else { // Lógica anterior } }}97. ¿Cómo implementar Idempotency?
Section titled “97. ¿Cómo implementar Idempotency?”¿Qué es? Garantizar que múltiples requests idénticos producen el mismo resultado.
¿Cómo? Idempotency-Key header + cache de resultados.
@RestControllerpublic class PedidoController {
@PostMapping("/pedidos") public ResponseEntity<Pedido> crear( @RequestHeader("Idempotency-Key") String idempotencyKey, @RequestBody PedidoDTO dto) {
// Verificar si ya se procesó Optional<Pedido> existente = cache.get(idempotencyKey); if (existente.isPresent()) { return ResponseEntity.ok(existente.get()); }
// Procesar Pedido pedido = service.crear(dto);
// Guardar resultado cache.put(idempotencyKey, pedido, Duration.ofHours(24));
return ResponseEntity.status(HttpStatus.CREATED).body(pedido); }}98. ¿Cómo implementar Retry Pattern?
Section titled “98. ¿Cómo implementar Retry Pattern?”¿Qué es? Reintentar operaciones fallidas automáticamente.
Herramientas: Spring Retry (@Retryable), Resilience4j.
@Configuration@EnableRetrypublic class RetryConfig {}
@Servicepublic class ExternalService {
@Retryable( retryFor = { ServiceUnavailableException.class }, maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 2) ) public String llamarServicioExterno() { // Puede fallar return restClient.get().uri("/api/data").retrieve().body(String.class); }
@Recover public String recover(ServiceUnavailableException e) { return "Valor por defecto"; }}99. ¿Cómo implementar Graceful Shutdown?
Section titled “99. ¿Cómo implementar Graceful Shutdown?”¿Qué es? Apagar la aplicación permitiendo que requests en curso terminen.
Configuración: server.shutdown=graceful + timeout.
server:shutdown: graceful
spring:lifecycle: timeout-per-shutdown-phase: 30s
// Cleanup personalizado@Componentpublic class GracefulShutdown implements DisposableBean {
@Override public void destroy() { // Cerrar conexiones // Completar tareas pendientes // Liberar recursos }}
// Con SmartLifecycle@Componentpublic class MiServicio implements SmartLifecycle {
@Override public void stop(Runnable callback) { // Cleanup asíncrono executor.submit(() -> { cleanup(); callback.run(); }); }}100. ¿Cuáles son las mejores prácticas de Spring?
Section titled “100. ¿Cuáles son las mejores prácticas de Spring?”Principios clave para aplicaciones Spring Boot robustas:
- Usar constructor injection en vez de field injection
- Preferir interfaces para servicios y repositorios
- Usar @Transactional solo donde sea necesario
- Externalizar configuración con @ConfigurationProperties
- Implementar health checks personalizados
- Usar perfiles para diferentes entornos
- Documentar APIs con OpenAPI
- Implementar manejo global de excepciones
- Usar DTOs para transferencia de datos
- Escribir tests unitarios e integración
- Implementar logging estructurado
- Usar métricas con Micrometer
- Implementar circuit breakers para servicios externos
- Usar caching estratégicamente
- Seguir principios SOLID