SmartOrder — Part 4: Inside the Inventory Service
Source: Dev.to
Ownership and boundaries
Inventory Service owns the stock state for each item — available, reserved, out of stock, discontinued. It does not manage order lifecycle or pricing. Boundaries are strict:
- No cross‑service joins
- No shared schemas
- No synchronous calls
It consumes OrderCreated events and reacts, enabling decoupled, eventually consistent microservices — a cornerstone of SmartOrder architecture.
The domain model and commons integration
@Entity
@Table
@Data
@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
@Jacksonized
@EntityListeners(AuditingEntityListener.class)
public class Inventory implements it.portus84.business.commons.model.Entity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
private String description;
@NotNull
@Builder.Default
@Enumerated(EnumType.STRING)
private InventoryStatus status = InventoryStatus.PENDING;
@CreatedDate
@Setter(AccessLevel.NONE)
@Column(nullable = false, updatable = false)
private LocalDateTime createdDate;
@LastModifiedDate
@Setter(AccessLevel.NONE)
@Column(nullable = false)
private LocalDateTime lastModifiedDate;
}
InventoryStatusdrives the saga.@CreatedDate/@LastModifiedDateleverage commons‑business auditing.
The repository is intentionally thin:
public interface InventoryRepository extends JpaRepository {}
Polyglot persistence
- Inventory Service uses Spring Data JPA with H2/Postgres.
- Order Service uses MongoDB.
Why the mix?
| Requirement | Choice |
|---|---|
| Atomic stock‑level updates | JPA transaction semantics |
| Reservations (reserve → commit) | Transactional guarantees |
| Auditing & timestamps | JPA auditing (commons integration) |
Commons modules provide base entities, mapper interfaces, and helpers, ensuring consistent patterns across services.
Event‑driven consumption
@Bean
public Consumer orderCreatedConsumer() {
return event -> orderConfirmationPublisher.send(event);
}
spring:
cloud:
function:
definition: orderCreatedConsumer
stream:
function:
bindings:
orderCreatedConsumer-in-0: consumeOrderCreated
bindings:
consumeOrderCreated:
destination: pending-orders
group: inventory-group
inventory-groupguarantees single processing per event across instances.- The consumer is stateless and purely event‑driven.
Commons provide shared DTOs and binding constants:
@UtilityClass
public class BindingNames {
public static final String PUBLISH_ORDER_CONFIRMED = "publishOrderConfirmed";
public static final String PUBLISH_ORDER_OUT_OF_STOCK = "publishOrderOutOfStock";
}
Availability check and conditional event publishing
@Slf4j
@Component
@RequiredArgsConstructor
public class OrderConfirmationPublisher {
private final StreamBridge streamBridge;
private final InventoryService inventoryService;
public void send(OrderCreatedEvent event) {
boolean available = inventoryService.checkAvailability(event.getOrderId());
if (available) {
streamBridge.send(BindingNames.PUBLISH_ORDER_CONFIRMED,
new OrderConfirmedEvent(event.getOrderId()));
} else {
streamBridge.send(BindingNames.PUBLISH_ORDER_OUT_OF_STOCK,
new OrderOutOfStockEvent(event.getOrderId(),
"Insufficient stock"));
}
}
}
- Decision logic is imperative but fully decoupled via events.
- Shared events‑model from commons ensures consistent serialization.
StreamBridgeenables dynamic selection of output bindings.
HATEOAS in REST responses
@Override
public ResponseEntity> getInventoryById(UUID id) {
return inventoryService.findById(id)
.map(inventoryMapper::toDTO)
.map(hateoasHelper::toEntityModel)
.map(ResponseEntity::ok)
.orElseThrow(() -> new InventoryNotFoundException(id));
}
- HATEOAS links generated via
hateoasHelperguide clients dynamically. - Supports PATCH / UPDATE operations without hard‑coding URIs.
Commons provide shared mapper interfaces, page mappers, and HATEOAS helpers.
Event‑driven workflow: solved challenges
- Order Service publishes
OrderCreated. - Inventory Service consumes it.
OrderConfirmationPublisherdecides based on stock.- Either
OrderConfirmedEventorOrderOutOfStockEventis published. - Order Service updates its state accordingly.
Solved challenges
| Challenge | Solution |
|---|---|
| Event‑driven stock reservation | No distributed transactions; eventual consistency via events |
| Atomic availability checks | JPA transaction semantics in Inventory Service |
| Boilerplate reduction | Shared commons infrastructure (entities, mappers, DTOs, helpers) |
TODO: checkAvailability
@Override
public boolean checkAvailability(String orderId) {
// TODO: validate each
}
Implementation will need to fetch the order lines, sum required quantities, compare with current stock, and possibly reserve the items within a single transaction.
Product item against actual stock
return true;
Placeholder always returns
true
Next step for developers
Integrate product line items from OrderCreatedEvent and validate quantities per stock record.
Note: This TODO is not part of this article’s scope, but it is essential for full inventory correctness.
Bootstrap and test data (developer hint)
@AutoConfiguration
@ConditionalOnClass(Instancio.class)
public class TestDataLoaderConfiguration {
@Bean
CommandLineRunner loadTestData(InventoryService service) {
return args -> service.saveAll(
Instancio.ofList(Inventory.class)
.size(5)
.ignoreFields()
.create()
);
}
}
- Instancio generates seed data for local development.
- Ensures consistent setup without touching production.
What’s next: commons modules
Focus will be on services/commons:
Shared business logic, DTOs, event definitions, and helpers used across order-service and inventory-service.
Includes
- Entity base classes
- MapStruct mappers
- HATEOAS helpers
- Test data loaders
Commons are key to scaling SmartOrder without duplicating boilerplate.
Repository: →