Inventory Service: Stock Control and JPA with PostgreSQL
The Inventory Service (servicio-inventario) manages product stock levels using PostgreSQL, exposing two REST endpoints for querying and decrementing stock.
Use this file to discover all available pages before exploring further.
The Inventory Service is the authoritative source of stock data for the InnovaTech platform. It maintains a record of available stock for every product and exposes two focused endpoints: one to query current stock levels and one to atomically decrement stock when a sale is confirmed. It is designed to be called by orchestration services — primarily servicio-ventapos — not by frontend clients directly.
servicio-inventario is a domain entity service backed by PostgreSQL, running on port 8081. It uses Spring Data JPA with Hibernate for ORM-based persistence and registers itself with the Eureka Server for service discovery. The service is built on Spring Boot 3.1.2 with Spring Cloud 2022.0.4 and uses Lombok to reduce boilerplate in the data model and service layer.
Port
8081 — direct service address
Database
PostgreSQL — innovatech_db on port 5433
Spring Boot
3.1.2 / Spring Cloud 2022.0.4
Service Discovery
Registered with Eureka at http://localhost:8761/eureka/
The service connects to PostgreSQL using Spring Data JPA with Hibernate’s update DDL mode, which automatically creates or alters the inventario table to match the entity model on startup. SQL logging is enabled to aid development-time debugging.
The service persists a single entity, Inventario, in the inventario table. Each row represents the stock state for one product, identified by a string productoId (matching the product identifiers used in servicio-catalogo).
The controller exposes two endpoints under the base path /api/inventario. Both are intended for machine-to-machine calls from servicio-ventapos or other orchestrators.
package com.innovatech.inventario.controller;import com.innovatech.inventario.service.InventarioService;import lombok.RequiredArgsConstructor;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.*;@RestController@RequestMapping("/api/inventario")@RequiredArgsConstructorpublic class InventarioController { private final InventarioService inventarioService; @GetMapping("/{productoId}/stock") public ResponseEntity<Integer> consultarStock(@PathVariable String productoId) { return ResponseEntity.ok(inventarioService.getStock(productoId)); } @PostMapping("/{productoId}/descontar") public ResponseEntity<String> descontarStock( @PathVariable String productoId, @RequestParam Integer cantidad) { boolean exito = inventarioService.descontarStock(productoId, cantidad); if (exito) { return ResponseEntity.ok("Stock descontado exitosamente"); } else { return ResponseEntity.badRequest().body("Stock insuficiente o producto no encontrado"); } }}
The stock decrement is wrapped in a @Transactional method in the service layer. This ensures that the read-check and write form a single atomic database operation — no two concurrent requests can simultaneously read an available stock count and both succeed with a decrement that would together exceed available stock.
package com.innovatech.inventario.service;import com.innovatech.inventario.model.Inventario;import com.innovatech.inventario.repository.InventarioRepository;import lombok.RequiredArgsConstructor;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;@Service@RequiredArgsConstructorpublic class InventarioService { private final InventarioRepository inventarioRepository; public Integer getStock(String productoId) { return inventarioRepository.findByProductoId(productoId) .map(Inventario::getStockDisponible) .orElse(0); } @Transactional public boolean descontarStock(String productoId, Integer cantidad) { Inventario inventario = inventarioRepository.findByProductoId(productoId) .orElseThrow(() -> new RuntimeException("Producto no encontrado en inventario")); if (inventario.getStockDisponible() >= cantidad) { inventario.setStockDisponible(inventario.getStockDisponible() - cantidad); inventarioRepository.save(inventario); return true; } return false; }}
The guard condition inventario.getStockDisponible() >= cantidad prevents overselling: if the requested quantity exceeds available stock, the method returns false without modifying the database, and the controller translates that into a 400 Bad Request response.
There is a path difference between the external API Gateway route and the internal service path. When calling through the gateway, use /api/v1/inventario/**. The gateway forwards the full path as-is to the service on port 8081, where the controller is mapped to /api/inventario/** — without the v1 segment. Ensure that gateway-routed requests do not include path segments that the service’s controller does not expect, or configure a RewritePath filter in the gateway route if paths need to be transformed.
Context
Base Path
Via API Gateway (external)
http://localhost:8080/api/v1/inventario/
Direct to service (internal)
http://localhost:8081/api/inventario/
For example, a stock query through the gateway:
# Via API GatewayGET http://localhost:8080/api/v1/inventario/{productoId}/stock# Direct to service (dev/testing only)GET http://localhost:8081/api/inventario/{productoId}/stock