Daniel Garcia Costa 3 weeks ago
parent
commit
b87f30848f
30 changed files with 1624 additions and 42 deletions
  1. 443 3
      Slides/SpringDataPoly2026.md
  2. 6 0
      docker/start_redis.sh
  3. 15 0
      pom.xml
  4. 2 0
      src/main/java/bidflow/auction/api/ApiApplication.java
  5. 30 2
      src/main/java/bidflow/auction/api/clients/CatalogFeignClient.java
  6. 18 0
      src/main/java/bidflow/auction/api/clients/PersistenceFeignClient.java
  7. 25 0
      src/main/java/bidflow/auction/api/clients/UsersFeignClient.java
  8. 20 0
      src/main/java/bidflow/auction/api/config/FeignConfig.java
  9. 21 0
      src/main/java/bidflow/auction/api/config/JacksonConfig.java
  10. 29 0
      src/main/java/bidflow/auction/api/config/RedisConfig.java
  11. 83 0
      src/main/java/bidflow/auction/api/controller/AuctionController.java
  12. 23 0
      src/main/java/bidflow/auction/api/controller/TestController.java
  13. 120 0
      src/main/java/bidflow/auction/api/domain/Auction.java
  14. 54 0
      src/main/java/bidflow/auction/api/domain/AuctionState.java
  15. 32 0
      src/main/java/bidflow/auction/api/domain/Bid.java
  16. 85 0
      src/main/java/bidflow/auction/api/domain/Item.java
  17. 27 0
      src/main/java/bidflow/auction/api/domain/User.java
  18. 32 0
      src/main/java/bidflow/auction/api/dto/BidRequest.java
  19. 5 12
      src/main/java/bidflow/auction/api/dto/CategoryDTO.java
  20. 22 0
      src/main/java/bidflow/auction/api/dto/CreateAuctionRequest.java
  21. 55 0
      src/main/java/bidflow/auction/api/dto/ItemDTO.java
  22. 22 9
      src/main/java/bidflow/auction/api/dto/ItemResponse.java
  23. 41 0
      src/main/java/bidflow/auction/api/dto/SellerDTO.java
  24. 25 0
      src/main/java/bidflow/auction/api/dto/UserResponse.java
  25. 38 0
      src/main/java/bidflow/auction/api/listener/AuctionScheduler.java
  26. 217 0
      src/main/java/bidflow/auction/api/service/AuctionService.java
  27. 42 0
      src/main/java/bidflow/auction/api/service/AuctionStateService.java
  28. 54 0
      src/main/java/bidflow/auction/api/service/BidService.java
  29. 25 15
      src/main/resources/META-INF/additional-spring-configuration-metadata.json
  30. 13 1
      src/main/resources/application.properties

+ 443 - 3
Slides/SpringDataPoly2026.md

@@ -51,8 +51,18 @@ style: |
 
 
   .box.warning {
-    border-color: #f97316;
-    background-color: #ffedd5;
+    border-color: #ffeeba;
+    background-color: #fff3cd;
+  }
+
+  .box.danger {
+    border-color: #f5c6cb;
+    background-color: #f8d7da;
+  }
+
+  .box.success {
+    border-color: #c3e6cb;
+    background-color: #d4edda;
   }
 
   /* Alineación */
@@ -94,8 +104,438 @@ style: |
 
 ---
 
+## ¿Qué es una arquitectura políglota?
+
+- Uso de **múltiples tecnologías de almacenamiento** en un mismo sistema
+- Cada tecnología se utiliza según **sus fortalezas**
+- No existe una única base de datos que resuelva todos los problemas
+<br>
+<div class="box w-80 center">
+  <strong>Hay que usar la tecnología adecuada para cada situación</strong>
+</div>
+
+---
+
+### Relacionales (SQL)
+<div class="box-compact">
+
+| Tipo                    | Características principales                                    | Casos de uso                         | Ejemplos                 |
+|-------------------------|--------------------------------------------------------------|--------------------------------------|--------------------------|
+| Relacional clásico   | ACID, modelo tabular, integridad referencial                 | Aplicaciones empresariales, CRUD      | PostgreSQL, MySQL        |
+| OLTP                | Optimizado para muchas transacciones pequeñas                | Sistemas operacionales (apps, APIs)   | PostgreSQL, Oracle       |
+| OLAP / Data Warehouse| Optimizado para consultas analíticas complejas               | BI, reporting, análisis de datos      | Snowflake, Redshift      |
+| HTAP (híbrido)      | Mezcla OLTP + OLAP en tiempo real                            | Sistemas con analítica en vivo        | TiDB, Azure SQL HTAP     |
+| Distribuido         | Escalado horizontal, replicación, particionado              | Sistemas globales y escalables        | CockroachDB, YugabyteDB  |
+
+</div>
+
+---
+
+### No Relacionales (NoSQL)
+
+<div class="box-compact">
+
+| Tipo               | Características principales                          | Casos de uso                          | Ejemplos            |
+|--------------------|-----------------------------------------------------|---------------------------------------|---------------------|
+| Documentales     | Datos JSON/BSON, esquema flexible                   | APIs, datos semiestructurados         | MongoDB, CouchDB    |
+| Clave-Valor      | Acceso ultrarrápido por clave                       | Cache, sesiones, estado temporal      | Redis, DynamoDB     |
+| Grafos           | Relaciones complejas entre nodos                    | Redes sociales, recomendaciones       | Neo4j               |
+| Columnar (Wide)  | Columnas distribuidas, gran escalabilidad           | Big Data, logs, eventos masivos       | Cassandra, HBase    |
+| Búsqueda         | Indexación y búsqueda full‑text                     | Buscadores, analítica textual         | Elasticsearch       |
+| Series temporales| Optimizadas para datos con timestamp                | IoT, métricas, monitoring             | InfluxDB, Timescale |
+| Multi‑modelo     | Soportan varios modelos en un mismo motor           | Sistemas híbridos complejos           | ArangoDB, Cosmos DB |
+
+</div>
+
+---
+
+
+#### No hay una única solución óptima  
+Cada tipo resuelve un problema distinto
+<br>
+<div class="box w-80 center">
+  <strong>Por eso las arquitecturas modernas son **políglotas**</strong>
+</div>
+
+---
+
+<div class="box warning">
+
+#### Complejidad
+- Uso de múltiples tecnologías == mayor complejidad global
+- Diferentes modelos de datos y APIs
+- Necesidad de orquestar varios sistemas
+
+</div>
+<br>
+<div class="box danger">
+
+#### Puntos de fallo
+- Cada tecnología añade:
+  - configuración propia
+  - características específicas de despliegue
+  - monitorización
+
+</div>
+<br>
+
+---
+
+## Problemas más comunes
+
+### Consistencia de datos
+
+- No hay transacciones distribuidas sencillas
+- Posibles inconsistencias:
+  - estado actualizado en un sistema pero no persistido en otro
+  - fallos en mitad del proceso
+
+<br>
+
+**Necesario diseñar tolerancia a fallos**
+
+---
+
+## Sincronización entre sistemas
+
+- ¿Cuándo mover los datos?
+  - en tiempo real
+  - mediante eventos
+  - mediante schedulers
+
+<br>
+
+**El diseño del flujo de datos es crítico**
+
+---
+
+### Transformación de datos
+
+- Cada sistema tiene su propio modelo:
+  - entidades (Java)
+  - DTOs
+  - documentos (Mongo)
+  - Key/Value (Redis)
+  - ...
+
+###  Dificultad de debugging
+
+- Errores distribuidos entre servicios
+- Fallos difíciles de rastrear:
+  - llamadas remotas
+  - errores de serialización
+  - inconsistencias de datos
+
+---
+
+## Conclusión
+
+<div class="box">
+
+ **Mayor flexibilidad implica mayor responsabilidad**
+
+</div>
+
+---
+
+
+## Caso práctico: Plataforma de Subastas
+
+### Problema
+
+Diseñar un sistema capaz de:
+
+- Gestionar subastas en tiempo real
+- Registrar pujas concurrentes
+- Mantener un histórico completo
+- Escalar correctamente ante múltiples usuarios
+
+---
+
+### Enfoque arquitectónico
+
+ Uso de una arquitectura **políglota**
+
+- Diferentes tecnologías para distintos problemas
+- Separación entre:
+  - estado en vivo
+  - persistencia histórica
+
+---
+
+### Objetivo
+
+Construir un sistema:
+
+- escalable
+- modular
+- alineado con prácticas reales de arquitectura moderna
+
+---
+
+### Tecnologías
+
+- BD relacion -> productos, categorías, usuarios... (**integridad**)
+- Redis -> estado activo de las subastas (**velocidad**)
+- MongoDB -> almacenamiento histórico (**flexibilidad**)
+- APIs REST -> integración entre servicios (**desacoplamiento**)
+
+---
+
+## MongoDB (Base de datos documental)
+
+### Características principales
+
+- Modelo basado en documentos (JSON / BSON)
+- Esquema flexible (no fijo)
+- Fácil evolución del modelo de datos
+- Soporte natural para estructuras anidadas
+
+---
+
+### Casos de uso
+
+- Datos semiestructurados
+- Históricos y auditoría
+- Sistemas donde el modelo evoluciona
+- APIs modernas (JSON-centric)
+
+---
+
+<div class="box success">
+
+### Ventajas
+
+- Flexibilidad de diseño
+- Alta productividad en desarrollo
+- Modelo cercano al dominio de negocio
+- Escalabilidad horizontal
+
+</div>
+<br>
+<div class="box danger">
+
+### Desventajas
+
+- Menor control de integridad frente a SQL
+- Redundancia de datos
+- No ideal para transacciones complejas
+
+</div>
+
+---
+
+## Redis (Clave-Valor en memoria)
+
+### Características principales
+
+- Base de datos en memoria
+- Acceso extremadamente rápido (ms)
+- Modelo clave-valor
+- Soporte de TTL (expiración automática)
+- Estructuras avanzadas (listas, sets, etc.)
+
+---
+
+## Casos de uso
+
+- Cache
+- Estado temporal
+- Sesiones
+- Sistemas en tiempo real
+
+---
+
+<div class="box success">
+
+### Ventajas
+
+- Altísimo rendimiento
+- Simplicidad de uso
+- Ideal para datos efímeros
+- TTL nativo
+
+</div>
+<br>
+<div class="box danger">
+
+### Desventajas
+
+- Persistencia limitada (según configuración)
+- No pensado como almacenamiento principal
+- Consumo de memoria
+
+</div>
+
+---
+
+
+## Resolviendo los desafíos
+
+
+No intentar evitar la complejidad, hay que hacerla explícita y controlarla
+
+- Separación clara de responsabilidades
+- Flujo de datos bien definido
+- Uso de cada tecnología para lo que mejor sabe hacer
+
+<div class="box">
+
+**Diseño orientado a evitar acoplamientos innecesarios**
+
+</div>
+
+---
+
+### Separación de responsabilidades
+
+- **Relacional** -> garantiza integridad
+- **Redis** -> estado en tiempo real
+- **MongoDB** -> persistencia histórica
+- **API** -> lógica de negocio
+
+### Beneficios
+
+- Cada componente hace una sola cosa
+- Menor complejidad interna por módulo
+- Sistema más mantenible
+
+<div class="box">
+
+**Evitamos mezclar estado temporal con persistencia**
+
+</div>
+
+---
+
+## Control del ciclo de vida de las subastas
+
+<div class="box danger">
+
+#### Problema
+
+- Redis elimina datos al expirar
+- No podemos recuperar el estado después
+
+</div>
+<br>
+<div class="box success">
+
+#### Solución
+
+Eliminamos dependencia de eventos internos
+
+- Definimos el tiempo de vida de nuestros eventos
+- Introducimos un **scheduler** para gestionar la persistencia
+
+</div>
+
+---
+
+### Flujo
+
+1. Subasta activa en Redis
+2. Scheduler detecta expiración
+3. Persistencia en Mongo
+4. Eliminación controlada
+
+<br>
+<div class="box">
+
+**Control explícito del sistema**
+
+</div>
+
+---
+
+### Transformación y enriquecimiento
+
+<div class="box danger">
+
+#### Problema
+
+- Datos distribuidos en varios servicios
+- Representaciones incompletas 
+
+</div>
+<br>
+<div class="box success">
+
+#### Solución
+
+- Feign obtiene entidades base
+- Resolución de relaciones
+- Construcción de DTO completo (enriquecidos)
+
+</div>
+
+---
+
+### Reactividad
+
+**NO es necesaria, pero si conveniente**
+
+- Sistema con múltiples llamadas remotas
+- Necesidad de gestionar concurrencia de forma eficiente
+- Evitar bloqueos y mejorar rendimiento
+
+---
+
+<div class="box success">
+
+#### Ventajas
+
+- Mejor uso de recursos
+- Alta capacidad de concurrencia
+- Código expresivo para flujos complejos
+
+</div>
+<br>
+<div class="box danger">
+
+#### Inconvenientes
+
+- Mayor complejidad conceptual
+- Manejo cuidadoso de errores
+- Debugging más difícil
+
+</div>
+
+---
+
+#### ¿En qué casos merece la pena?
+
+- Obtener datos
+- Enriquecer información (usuario, categoría, etc.)
+- Persistir resultado final
+
+**Todo en un pipeline no bloqueante**
+
+<div class="box">
+
+La reactividad es una forma distinta de modelar el flujo del sistema
+
+</div>
+
+---
+
+
+### Resultado
+
+- Independencia de servicios externos
+- Gestión de llamadas no bloqueante
+- Datos listos para persistencia
+  - Necesidad de integridad -> sistema relacional
+  - Ciclo de vida -> Redis
+  - Snapshot completo redundado -> Mongo 
+
+
+---
+
 <img src="img/diagrama_componentes.png" width="90%"/>
 
 ---
 
-<img src="img/diagrama_componentes.png" width="90%"/>
+<img src="img/diagrama_componentes.png" width="90%"/>
+
+---

+ 6 - 0
docker/start_redis.sh

@@ -0,0 +1,6 @@
+
+docker run -d \
+  --name bidflow-redis \
+  -p 6379:6379 \
+  redis:7 \
+  redis-server --notify-keyspace-events Ex

+ 15 - 0
pom.xml

@@ -50,6 +50,16 @@
 		</dependency>
 
 		<dependency>
+			<groupId>io.github.openfeign</groupId>
+			<artifactId>feign-jackson</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>com.fasterxml.jackson.datatype</groupId>
+			<artifactId>jackson-datatype-jsr310</artifactId>
+		</dependency>
+
+		<dependency>
 			<groupId>org.springframework.cloud</groupId>
 			<artifactId>spring-cloud-starter-loadbalancer</artifactId>
 		</dependency>
@@ -66,6 +76,11 @@
 
 		<dependency>
 			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
 			<artifactId>spring-boot-devtools</artifactId>
 			<scope>runtime</scope>
 			<optional>true</optional>

+ 2 - 0
src/main/java/bidflow/auction/api/ApiApplication.java

@@ -3,9 +3,11 @@ package bidflow.auction.api;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.scheduling.annotation.EnableScheduling;
 
 @SpringBootApplication
 @EnableFeignClients
+@EnableScheduling
 public class ApiApplication {
 
 	public static void main(String[] args) {

+ 30 - 2
src/main/java/bidflow/auction/api/clients/CatalogFeignClient.java

@@ -1,5 +1,33 @@
 package bidflow.auction.api.clients;
 
-public class CatalogFeignClient {
-    
+import java.net.URI;
+import java.util.Map;
+
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+
+import bidflow.auction.api.config.FeignConfig;
+import bidflow.auction.api.domain.Item;
+import bidflow.auction.api.dto.CategoryDTO;
+import bidflow.auction.api.dto.ItemResponse;
+import bidflow.auction.api.dto.SellerDTO;
+
+@FeignClient(name = "catalogClient", 
+             url = "${catalog.url}",
+             configuration = FeignConfig.class)
+public interface CatalogFeignClient {
+
+    @GetMapping("/items")
+    ItemResponse getItems();
+
+    @GetMapping
+    SellerDTO getSellerByUrl(URI uri);
+
+    @GetMapping
+    CategoryDTO getCategoryByUrl(URI uri);
+
+    @GetMapping
+    Item getItemByUrl(URI uri);
+
+
 }

+ 18 - 0
src/main/java/bidflow/auction/api/clients/PersistenceFeignClient.java

@@ -0,0 +1,18 @@
+package bidflow.auction.api.clients;
+
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.*;
+
+import bidflow.auction.api.domain.Auction;
+
+import java.util.List;
+
+@FeignClient(name = "persistenceClient", url = "${persistence.url}")
+public interface PersistenceFeignClient {
+
+    @PostMapping("/auctions")
+    Auction save(@RequestBody Auction auction);
+
+    @GetMapping("/auctions")
+    List<Auction> getAll();
+}

+ 25 - 0
src/main/java/bidflow/auction/api/clients/UsersFeignClient.java

@@ -0,0 +1,25 @@
+package bidflow.auction.api.clients;
+
+
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+
+import bidflow.auction.api.config.FeignConfig;
+import bidflow.auction.api.domain.User;
+import bidflow.auction.api.dto.UserResponse;
+
+@FeignClient(name = "usersClient", 
+             url = "${users.url}",
+             configuration = FeignConfig.class)
+public interface UsersFeignClient {
+
+    @GetMapping("/users")
+    UserResponse getUsers();
+
+    
+    @GetMapping("/users/{id}")
+    User getUserById(@PathVariable("id") String id);
+
+
+}

+ 20 - 0
src/main/java/bidflow/auction/api/config/FeignConfig.java

@@ -0,0 +1,20 @@
+package bidflow.auction.api.config;
+
+import feign.codec.Decoder;
+import feign.jackson.JacksonDecoder;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+
+@Configuration
+public class FeignConfig {
+
+    @Bean
+    public Decoder feignDecoder(ObjectMapper objectMapper) {
+        return new JacksonDecoder(objectMapper);
+    }
+
+}

+ 21 - 0
src/main/java/bidflow/auction/api/config/JacksonConfig.java

@@ -0,0 +1,21 @@
+package bidflow.auction.api.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+
+@Configuration
+public class JacksonConfig {
+
+    @Bean
+    public ObjectMapper objectMapper() {
+
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.registerModule(new JavaTimeModule());
+
+        return mapper;
+    }
+
+}

+ 29 - 0
src/main/java/bidflow/auction/api/config/RedisConfig.java

@@ -0,0 +1,29 @@
+package bidflow.auction.api.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
+import org.springframework.data.redis.core.ReactiveRedisTemplate;
+import org.springframework.data.redis.serializer.*;
+
+import bidflow.auction.api.domain.AuctionState;
+
+@Configuration
+public class RedisConfig {
+
+    @Bean
+    public ReactiveRedisTemplate<String, AuctionState> reactiveRedisTemplate(ReactiveRedisConnectionFactory factory) {
+
+        RedisSerializer<String> keySerializer = new StringRedisSerializer();
+
+        RedisSerializer<AuctionState> valueSerializer =
+                new JacksonJsonRedisSerializer<>(AuctionState.class);
+
+        RedisSerializationContext<String, AuctionState> context =
+                RedisSerializationContext.<String, AuctionState>newSerializationContext(keySerializer)
+                        .value(valueSerializer)
+                        .build();
+
+        return new ReactiveRedisTemplate<>(factory, context);
+    }
+}

+ 83 - 0
src/main/java/bidflow/auction/api/controller/AuctionController.java

@@ -0,0 +1,83 @@
+package bidflow.auction.api.controller;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.server.ResponseStatusException;
+
+import bidflow.auction.api.domain.AuctionState;
+import bidflow.auction.api.dto.BidRequest;
+import bidflow.auction.api.dto.CreateAuctionRequest;
+import bidflow.auction.api.dto.ItemDTO;
+import bidflow.auction.api.service.AuctionService;
+import bidflow.auction.api.service.AuctionStateService;
+import bidflow.auction.api.service.BidService;
+import reactor.core.publisher.Mono;
+
+@RestController
+public class AuctionController {
+    
+    @Autowired
+    private AuctionService auctionService;
+
+    @Autowired
+    private BidService bidService;
+
+    @Autowired
+    private AuctionStateService auctionStateService;
+
+    
+    @GetMapping("/items")
+    public Mono<List<ItemDTO>> getItems() {
+        return auctionService.getItems();
+    }
+
+    @PostMapping("/bids")
+    public Mono<AuctionState> placeBid(@RequestBody BidRequest request) {
+
+        return bidService.placeBid(
+                request.getItemId(),
+                request.getUserId(),
+                request.getAmount()
+        );
+    }
+
+
+    @GetMapping("/bids/{itemId}")
+    public Mono<AuctionState> getAuctionState(@PathVariable String itemId) {
+        return auctionStateService.get(itemId);
+    }
+    
+
+    @PostMapping("/auctions")
+    public Mono<?> createAuction(@RequestBody CreateAuctionRequest request) {
+
+        return auctionService.createAuction(
+                request.getItemId(),
+                request.getStartingPrice()
+        );
+    }
+
+
+    @GetMapping("/auctions/{itemId}")
+    public Mono<AuctionState> getAuction(@PathVariable String itemId){
+        
+        return auctionStateService.get(itemId)
+            .switchIfEmpty(Mono.error(
+                    new ResponseStatusException(
+                            HttpStatus.NOT_FOUND,
+                            "Auction not found or expired"
+                    )
+            ));
+
+    }
+
+
+
+}

+ 23 - 0
src/main/java/bidflow/auction/api/controller/TestController.java

@@ -0,0 +1,23 @@
+package bidflow.auction.api.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import bidflow.auction.api.clients.CatalogFeignClient;
+import bidflow.auction.api.dto.ItemResponse;
+
+@RestController
+@RequestMapping("/test")
+public class TestController {
+
+    @Autowired
+    private CatalogFeignClient catalogFeignClient;
+
+    @GetMapping("/catalog")
+    public ItemResponse getCatalogItems() {
+        return catalogFeignClient.getItems();
+    }
+}
+

+ 120 - 0
src/main/java/bidflow/auction/api/domain/Auction.java

@@ -0,0 +1,120 @@
+package bidflow.auction.api.domain;
+
+import java.time.Instant;
+import java.util.List;
+
+public class Auction {
+
+    private String auctionId;
+    private String itemId;
+    private String itemName;
+    private String categoryName;
+    private String sellerId;
+    private String sellerUsername;
+    private String winnerUserId;
+    private String winnerUsername;
+    private Instant startTime;
+    private Instant endTime;
+    private Double finalPrice;
+
+    private List<Bid> bids;
+
+    public String getAuctionId() {
+        return auctionId;
+    }
+
+    public void setAuctionId(String auctionId) {
+        this.auctionId = auctionId;
+    }
+
+    public String getItemId() {
+        return itemId;
+    }
+
+    public void setItemId(String itemId) {
+        this.itemId = itemId;
+    }
+
+    public String getSellerId() {
+        return sellerId;
+    }
+
+    public void setSellerId(String sellerId) {
+        this.sellerId = sellerId;
+    }
+
+    public Double getFinalPrice() {
+        return finalPrice;
+    }
+
+    public void setFinalPrice(Double finalPrice) {
+        this.finalPrice = finalPrice;
+    }
+
+    public List<Bid> getBids() {
+        return bids;
+    }
+
+    public void setBids(List<Bid> bids) {
+        this.bids = bids;
+    }
+
+    public Instant getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Instant startTime) {
+        this.startTime = startTime;
+    }
+
+    public Instant getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(Instant endTime) {
+        this.endTime = endTime;
+    }
+
+    public String getWinnerUserId() {
+        return winnerUserId;
+    }
+
+    public void setWinnerUserId(String winnerUserId) {
+        this.winnerUserId = winnerUserId;
+    }
+
+    public String getItemName() {
+        return itemName;
+    }
+
+    public void setItemName(String itemName) {
+        this.itemName = itemName;
+    }
+
+    public String getCategoryName() {
+        return categoryName;
+    }
+
+    public void setCategoryName(String categoryName) {
+        this.categoryName = categoryName;
+    }
+
+    public String getSellerUsername() {
+        return sellerUsername;
+    }
+
+    public void setSellerUsername(String sellerUsername) {
+        this.sellerUsername = sellerUsername;
+    }
+
+    public String getWinnerUsername() {
+        return winnerUsername;
+    }
+
+    public void setWinnerUsername(String winnerUsername) {
+        this.winnerUsername = winnerUsername;
+    }
+
+    
+    
+}

+ 54 - 0
src/main/java/bidflow/auction/api/domain/AuctionState.java

@@ -0,0 +1,54 @@
+package bidflow.auction.api.domain;
+
+import java.time.Instant;
+import java.util.List;
+
+public class AuctionState {
+    
+    private String itemId;
+    private Double currentPrice;
+    private String winnerUserId;
+    private Instant startTime;
+    private Instant endTime;
+    private List<Bid> bids;
+
+    public String getItemId() {
+        return itemId;
+    }
+    public void setItemId(String itemId) {
+        this.itemId = itemId;
+    }
+    public Double getCurrentPrice() {
+        return currentPrice;
+    }
+    public void setCurrentPrice(Double currentPrice) {
+        this.currentPrice = currentPrice;
+    }
+    public String getWinnerUserId() {
+        return winnerUserId;
+    }
+    public void setWinnerUserId(String winnerUserId) {
+        this.winnerUserId = winnerUserId;
+    }
+    public List<Bid> getBids() {
+        return bids;
+    }
+    public void setBids(List<Bid> bids) {
+        this.bids = bids;
+    }
+    public Instant getStartTime() {
+        return startTime;
+    }
+    public void setStartTime(Instant startTime) {
+        this.startTime = startTime;
+    }
+    public Instant getEndTime() {
+        return endTime;
+    }
+    public void setEndTime(Instant endTime) {
+        this.endTime = endTime;
+    }
+    
+
+}
+

+ 32 - 0
src/main/java/bidflow/auction/api/domain/Bid.java

@@ -0,0 +1,32 @@
+package bidflow.auction.api.domain;
+
+import java.time.Instant;
+
+public class Bid {
+    
+    private String userId;
+    private Double amount;
+    private Instant timestamp;
+    
+    public String getUserId() {
+        return userId;
+    }
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+    public Double getAmount() {
+        return amount;
+    }
+    public void setAmount(Double amount) {
+        this.amount = amount;
+    }
+    public Instant getTimestamp() {
+        return timestamp;
+    }
+    public void setTimestamp(Instant timestamp) {
+        this.timestamp = timestamp;
+    }
+
+
+}
+

+ 85 - 0
src/main/java/bidflow/auction/api/domain/Item.java

@@ -0,0 +1,85 @@
+package bidflow.auction.api.domain;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Item {
+    private String id;
+    private String name;
+    private Double startingPrice;
+
+    @JsonProperty("_links")
+    private Links links;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Double getStartingPrice() {
+        return startingPrice;
+    }
+
+    public void setStartingPrice(Double startingPrice) {
+        this.startingPrice = startingPrice;
+    }
+
+    public Links getLinks() {
+        return links;
+    }
+
+    public void setLinks(Links links) {
+        this.links = links;
+    }
+
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class Links {
+        private Link seller;
+        private Link category;
+
+        public Link getSeller() {
+            return seller;
+        }
+
+        public Link getCategory() {
+            return category;
+        }
+
+        public void setSeller(Link seller) {
+            this.seller = seller;
+        }
+
+        public void setCategory(Link category) {
+            this.category = category;
+        }
+
+    }
+
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class Link {
+        private String href;
+
+        public String getHref() {
+            return href;
+        }
+
+        public void setHref(String href) {
+            this.href = href;
+        }
+    }
+
+    
+}
+

+ 27 - 0
src/main/java/bidflow/auction/api/domain/User.java

@@ -0,0 +1,27 @@
+package bidflow.auction.api.domain;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class User {
+
+    private String id;
+    private String username;
+
+    public String getId() { 
+        return id; 
+    }
+    
+    public String getUsername() { 
+        return username; 
+    }
+
+    public void setId(String id) { 
+        this.id = id; 
+    }
+
+    public void setUsername(String username) { 
+        this.username = username; 
+    }
+    
+}

+ 32 - 0
src/main/java/bidflow/auction/api/dto/BidRequest.java

@@ -0,0 +1,32 @@
+package bidflow.auction.api.dto;
+
+public class BidRequest {
+
+    private String itemId;
+    private String userId;
+    private Double amount;
+
+    public String getItemId() {
+        return itemId;
+    }
+
+    public void setItemId(String itemId) {
+        this.itemId = itemId;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public Double getAmount() {
+        return amount;
+    }
+
+    public void setAmount(Double amount) {
+        this.amount = amount;
+    }
+}

+ 5 - 12
src/main/java/bidflow/auction/api/dto/Item.java → src/main/java/bidflow/auction/api/dto/CategoryDTO.java

@@ -1,9 +1,12 @@
 package bidflow.auction.api.dto;
 
-public class Item {
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class CategoryDTO {
+
     private String id;
     private String name;
-    private Double startingPrice;
 
     public String getId() {
         return id;
@@ -20,14 +23,4 @@ public class Item {
     public void setName(String name) {
         this.name = name;
     }
-
-    public Double getStartingPrice() {
-        return startingPrice;
-    }
-
-    public void setStartingPrice(Double startingPrice) {
-        this.startingPrice = startingPrice;
-    }
-    
 }
-

+ 22 - 0
src/main/java/bidflow/auction/api/dto/CreateAuctionRequest.java

@@ -0,0 +1,22 @@
+package bidflow.auction.api.dto;
+
+public class CreateAuctionRequest {
+
+    private String itemId;
+    private Double startingPrice;
+    
+    public String getItemId() {
+        return itemId;
+    }
+    public void setItemId(String itemId) {
+        this.itemId = itemId;
+    }
+    public Double getStartingPrice() {
+        return startingPrice;
+    }
+    public void setStartingPrice(Double startingPrice) {
+        this.startingPrice = startingPrice;
+    }
+
+    
+}

+ 55 - 0
src/main/java/bidflow/auction/api/dto/ItemDTO.java

@@ -0,0 +1,55 @@
+package bidflow.auction.api.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ItemDTO {
+
+    private String id;
+    private String name;
+    private Double startingPrice;
+
+    private SellerDTO seller;
+    private CategoryDTO category;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Double getStartingPrice() {
+        return startingPrice;
+    }
+
+    public void setStartingPrice(Double startingPrice) {
+        this.startingPrice = startingPrice;
+    }
+
+    public SellerDTO getSeller() {
+        return seller;
+    }
+
+    public void setSeller(SellerDTO seller) {
+        this.seller = seller;
+    }
+
+    public CategoryDTO getCategory() {
+        return category;
+    }
+
+    public void setCategory(CategoryDTO category) {
+        this.category = category;
+    }
+   
+}

+ 22 - 9
src/main/java/bidflow/auction/api/dto/ItemResponse.java

@@ -2,26 +2,39 @@ package bidflow.auction.api.dto;
 
 import java.util.List;
 
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import bidflow.auction.api.domain.Item;
+
+
+@JsonIgnoreProperties(ignoreUnknown = true)
 public class ItemResponse {
-    private Embedded _embedded;
+    
+    @JsonProperty("_embedded")
+    private Embedded embedded;
 
-    public Embedded get_embedded() { 
-        return _embedded; 
+    public Embedded getEmbedded() {
+        return embedded;
     }
 
-    public void set_embedded(Embedded _embedded) { 
-        this._embedded = _embedded; 
+    public void setEmbedded(Embedded embedded) {
+        this.embedded = embedded;
     }
 
+    @JsonIgnoreProperties(ignoreUnknown = true)
     public static class Embedded {
+
+        @JsonProperty("items")
         private List<Item> items;
 
-        public List<Item> getItems() { 
-            return items; 
+        public List<Item> getItems() {
+            return items;
         }
 
-        public void setItems(List<Item> items) { 
-            this.items = items; 
+        public void setItems(List<Item> items) {
+            this.items = items;
         }
     }
+
 }

+ 41 - 0
src/main/java/bidflow/auction/api/dto/SellerDTO.java

@@ -0,0 +1,41 @@
+package bidflow.auction.api.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class SellerDTO {
+
+    private String id;
+    private String userId;
+    private Double rating;
+    private String username;
+
+    
+    public String getId() {
+        return id;
+    }
+    public void setId(String id) {
+        this.id = id;
+    }
+    public String getUserId() {
+        return userId;
+    }
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+    public Double getRating() {
+        return rating;
+    }
+    public void setRating(Double rating) {
+        this.rating = rating;
+    }
+    public String getUsername() {
+        return username;
+    }
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    
+    
+}

+ 25 - 0
src/main/java/bidflow/auction/api/dto/UserResponse.java

@@ -0,0 +1,25 @@
+package bidflow.auction.api.dto;
+
+import java.util.List;
+
+import bidflow.auction.api.domain.User;
+
+public class UserResponse {
+
+    private Embedded _embedded;
+
+    public Embedded get_embedded() { 
+        return _embedded; 
+    }
+
+    public void set_embedded(Embedded _embedded) { 
+        this._embedded = _embedded; 
+    }
+
+    public static class Embedded {
+        private List<User> users;
+
+        public List<User> getUsers() { return users; }
+        public void setUsers(List<User> users) { this.users = users; }
+    }
+}

+ 38 - 0
src/main/java/bidflow/auction/api/listener/AuctionScheduler.java

@@ -0,0 +1,38 @@
+package bidflow.auction.api.listener;
+
+import bidflow.auction.api.domain.AuctionState;
+import bidflow.auction.api.service.AuctionService;
+import bidflow.auction.api.service.AuctionStateService;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Flux;
+
+import java.time.Instant;
+
+@Component
+public class AuctionScheduler {
+
+    private final AuctionStateService auctionStateService;
+    private final AuctionService auctionService;
+
+    public AuctionScheduler(
+            AuctionStateService auctionStateService,
+            AuctionService auctionService
+    ) {
+        this.auctionStateService = auctionStateService;
+        this.auctionService = auctionService;
+    }
+
+    @Scheduled(fixedRateString = "${auction.scheduler.fixed-rate-ms}") 
+    public void checkExpiredAuctions() {
+
+        Flux<AuctionState> all = auctionStateService.getAll();
+
+        all.filter(state -> state.getEndTime() != null && state.getEndTime().isBefore(Instant.now()))
+           .flatMap(state ->
+               auctionService.persistAuction(state)
+                   .then(auctionStateService.delete(state.getItemId()))
+           )
+           .subscribe();
+    }
+}

+ 217 - 0
src/main/java/bidflow/auction/api/service/AuctionService.java

@@ -0,0 +1,217 @@
+package bidflow.auction.api.service;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.time.Duration;
+import java.time.Instant;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+import org.springframework.web.server.ResponseStatusException;
+
+import bidflow.auction.api.clients.CatalogFeignClient;
+import bidflow.auction.api.clients.PersistenceFeignClient;
+import bidflow.auction.api.clients.UsersFeignClient;
+import bidflow.auction.api.domain.Auction;
+import bidflow.auction.api.domain.AuctionState;
+import bidflow.auction.api.domain.Item;
+import bidflow.auction.api.domain.User;
+import bidflow.auction.api.dto.CategoryDTO;
+import bidflow.auction.api.dto.ItemDTO;
+import bidflow.auction.api.dto.SellerDTO;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.core.scheduler.Schedulers;
+
+
+@Service
+public class AuctionService {
+
+    @Autowired
+    private CatalogFeignClient catalogFeignClient;   
+
+    @Autowired
+    private UsersFeignClient usersFeignClient;   
+
+    @Autowired
+    private PersistenceFeignClient persistenceFeignClient;  
+
+    @Autowired
+    private AuctionStateService auctionStateService;
+
+
+    @Value("${auction.ttl.minutes}")
+    private Integer auctionTtlMinutes;
+
+
+    @Value("${catalog.url}")
+    private String catalogBaseUrl;
+
+
+    public Mono<List<ItemDTO>> getItems() {
+
+        return getItemsFromCatalog()
+            .flatMap(items ->
+                    Flux.fromIterable(items)
+                            .flatMap(this::enrichItem) // paralelo
+                            .collectList()
+            );
+          
+    }
+
+    private Mono<List<Item>> getItemsFromCatalog() {
+        return Mono.fromCallable(() ->
+                catalogFeignClient.getItems()
+                        .getEmbedded()
+                        .getItems()
+        ).subscribeOn(Schedulers.boundedElastic());
+    }
+
+    private Mono<ItemDTO> enrichItem(Item item) {
+
+        String sellerUrl = item.getLinks().getSeller().getHref();
+        String categoryUrl = item.getLinks().getCategory().getHref();
+
+        Mono<SellerDTO> sellerMono = getSeller(sellerUrl);
+        Mono<CategoryDTO> categoryMono = getCategory(categoryUrl);
+
+        return sellerMono.flatMap(seller ->
+                getUser(seller.getUserId())
+                        .map(user -> {
+                            seller.setUsername(user.getUsername());
+                            return seller;
+                        })
+        ).zipWith(categoryMono)
+        .map(tuple -> mapToDTO(item, tuple.getT1(), tuple.getT2()));
+    }
+
+    private Mono<SellerDTO> getSeller(String url) {
+
+        return Mono.fromCallable(() ->
+                catalogFeignClient.getSellerByUrl(URI.create(url))
+        ).subscribeOn(Schedulers.boundedElastic());
+
+    }
+
+    private Mono<CategoryDTO> getCategory(String url) {
+        return Mono.fromCallable(() ->
+                catalogFeignClient.getCategoryByUrl(URI.create(url))
+        ).subscribeOn(Schedulers.boundedElastic());
+    }
+
+    private ItemDTO mapToDTO(Item item, SellerDTO seller, CategoryDTO category) {
+
+        ItemDTO dto = new ItemDTO();
+        dto.setId(item.getId());
+        dto.setName(item.getName());
+        dto.setStartingPrice(item.getStartingPrice());
+        dto.setSeller(seller);
+        dto.setCategory(category);
+
+        return dto;
+    }
+
+
+    private Mono<User> getUser(String userId) {
+
+        return Mono.fromCallable(() ->
+                usersFeignClient.getUserById(userId)
+        ).subscribeOn(Schedulers.boundedElastic());
+    }
+
+
+    public List<Item> getItemsImperative() {
+        return catalogFeignClient
+                .getItems()
+                .getEmbedded()
+                .getItems();
+    }
+
+
+    public Mono<?> createAuction(String itemId, Double startingPrice) {
+
+        return auctionStateService.get(itemId)
+                .hasElement()
+                .flatMap(exists -> {
+                    if (exists) {
+                        return Mono.error(new ResponseStatusException(
+                                HttpStatus.CONFLICT,
+                                "Auction already exists"
+                        ));
+                    }
+
+                    AuctionState state = new AuctionState();
+                    state.setItemId(itemId);
+                    state.setCurrentPrice(startingPrice);
+                    state.setBids(new ArrayList<>());
+                    state.setStartTime(Instant.now());
+                    state.setEndTime(Instant.now().plus(Duration.ofMinutes(auctionTtlMinutes))); 
+
+                    return auctionStateService.save(state).then();
+                });
+
+    }
+
+public Mono<Void> persistAuction(AuctionState state) {
+
+        return Mono.fromCallable(() ->
+                catalogFeignClient.getItemByUrl(
+                    URI.create(catalogBaseUrl + "/items/" + state.getItemId())
+                )
+        )
+        .subscribeOn(Schedulers.boundedElastic())
+        .flatMap(this::enrichItem)
+        .flatMap(item -> {
+            if (state.getWinnerUserId() != null) {
+                return getUser(state.getWinnerUserId())
+                    .map(winner ->
+                        buildAuctionFromItemDTO(state, item, winner)
+                    );
+            } else {
+                return Mono.just(
+                    buildAuctionFromItemDTO(state, item, null)
+                );
+            }
+        })
+        .flatMap(auction ->
+            Mono.fromRunnable(() ->
+                persistenceFeignClient.save(auction)
+            ).subscribeOn(Schedulers.boundedElastic())
+        )
+        .then();
+}
+
+    private Auction buildAuctionFromItemDTO(AuctionState state, ItemDTO item, User winner) {
+
+        Auction auction = new Auction();
+
+        auction.setAuctionId(UUID.randomUUID().toString());
+        auction.setItemId(state.getItemId());
+
+        auction.setItemName(item.getName());
+        auction.setCategoryName(item.getCategory().getName());
+
+        auction.setSellerId(item.getSeller().getUserId());
+        auction.setSellerUsername(item.getSeller().getUsername());
+
+        auction.setWinnerUserId(state.getWinnerUserId());
+        auction.setWinnerUsername(
+                winner != null ? winner.getUsername() : null
+        );
+
+        auction.setStartTime(state.getStartTime());
+        auction.setEndTime(state.getEndTime());
+        auction.setFinalPrice(state.getCurrentPrice());
+
+        auction.setBids(state.getBids());
+
+        return auction;
+    }
+
+
+}

+ 42 - 0
src/main/java/bidflow/auction/api/service/AuctionStateService.java

@@ -0,0 +1,42 @@
+package bidflow.auction.api.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.ReactiveRedisTemplate;
+import org.springframework.stereotype.Service;
+
+import bidflow.auction.api.domain.AuctionState;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+@Service
+public class AuctionStateService {
+
+    @Autowired
+    private ReactiveRedisTemplate<String, AuctionState> redisTemplate;
+
+    private String buildKey(String itemId) {
+        return "auction:" + itemId;
+    }
+
+    public Flux<AuctionState> getAll(){
+        return redisTemplate.keys("auction:*")
+            .flatMap(redisTemplate.opsForValue()::get);
+    }
+
+    public Mono<AuctionState> get(String itemId) {
+        return redisTemplate.opsForValue()
+                .get(buildKey(itemId));
+    }
+
+    public Mono<Boolean> save(AuctionState state) {
+        return redisTemplate.opsForValue()
+                .set(buildKey(state.getItemId()), state);
+    }
+    
+    public Mono<Boolean> delete(String itemId) {
+        return redisTemplate.delete(buildKey(itemId))
+                .map(count -> count > 0);
+    }
+
+
+}

+ 54 - 0
src/main/java/bidflow/auction/api/service/BidService.java

@@ -0,0 +1,54 @@
+package bidflow.auction.api.service;
+
+import java.time.Instant;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+import org.springframework.web.server.ResponseStatusException;
+
+import bidflow.auction.api.domain.AuctionState;
+import bidflow.auction.api.domain.Bid;
+import reactor.core.publisher.Mono;
+
+@Service
+public class BidService {
+
+    @Autowired
+    private AuctionStateService auctionStateService;
+
+    public Mono<AuctionState> placeBid(String itemId, String userId, Double amount) {
+
+        return auctionStateService.get(itemId)
+                .switchIfEmpty(
+                        Mono.error(new ResponseStatusException(
+                                HttpStatus.NOT_FOUND,
+                                "No active auction"
+                        ))
+                )
+                .flatMap(state -> {
+
+                        if (amount <= state.getCurrentPrice()) {
+                        return Mono.error(new ResponseStatusException(
+                                HttpStatus.CONFLICT,
+                                "Bid too low"
+                        ));
+                        }
+
+                        Bid bid = new Bid();
+                        bid.setUserId(userId);
+                        bid.setAmount(amount);
+                        bid.setTimestamp(Instant.now());
+
+                        state.setCurrentPrice(amount);
+                        state.setWinnerUserId(userId);
+                        state.getBids().add(bid);
+
+                        return auctionStateService
+                                .save(state)
+                                .thenReturn(state);
+
+                });
+        }
+
+}

+ 25 - 15
src/main/resources/META-INF/additional-spring-configuration-metadata.json

@@ -1,17 +1,27 @@
 {"properties": [
-    {
-        "name": "persistence.url",
-        "type": "java.lang.String",
-        "description": "URL para acceder al servicio de persistencia (bidflow.auction.persistence)"
-    },
-    {
-        "name": "users.url",
-        "type": "java.lang.String",
-        "description": "URL para acceder al servicio de usuarios (bidflow.users)"
-    },
-    {
-        "name": "catalog.url",
-        "type": "java.lang.String",
-        "description": "URL para acceder al servicio de catálogo (bidflow.auction.catalog)"
-    }
+  {
+    "name": "persistence.url",
+    "type": "java.lang.String",
+    "description": "URL para acceder al servicio de persistencia (bidflow.auction.persistence)"
+  },
+  {
+    "name": "users.url",
+    "type": "java.lang.String",
+    "description": "URL para acceder al servicio de usuarios (bidflow.users)"
+  },
+  {
+    "name": "catalog.url",
+    "type": "java.lang.String",
+    "description": "URL para acceder al servicio de catálogo (bidflow.auction.catalog)"
+  },
+  {
+    "name": "auction.ttl.minutes",
+    "type": "java.lang.String",
+    "description": "Duración de las subastas, en minutos"
+  },
+  {
+    "name": "auction.scheduler.fixed-rate-ms",
+    "type": "java.lang.String",
+    "description": "Intervalo para identificar subastas finalizadas, en milisegundos"
+  }
 ]}

+ 13 - 1
src/main/resources/application.properties

@@ -1,7 +1,19 @@
 spring.application.name=api
 
+server.port=8081
+spring.web.error.include-stacktrace=never
+spring.web.error.include-message=always
+spring.web.error.include-binding-errors=never
+
 catalog.url=http://localhost:8082/api
 users.url=http://localhost:8083/api
-persistence.url=http://localhost:8081
+persistence.url=http://localhost:8084
 
 spring.cloud.discovery.enabled=false
+
+spring.data.redis.host=localhost
+spring.data.redis.port=6379
+#spring.data.redis.timeout=5000
+
+auction.ttl.minutes=3
+auction.scheduler.fixed-rate-ms=30000