SmartOrder — Part 4: Inside the Inventory Service

Published: (March 9, 2026 at 05:54 AM EDT)
4 min read
Source: Dev.to

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;
}
  • InventoryStatus drives the saga.
  • @CreatedDate / @LastModifiedDate leverage 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?

RequirementChoice
Atomic stock‑level updatesJPA transaction semantics
Reservations (reserve → commit)Transactional guarantees
Auditing & timestampsJPA 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-group guarantees 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.
  • StreamBridge enables 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 hateoasHelper guide 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

  1. Order Service publishes OrderCreated.
  2. Inventory Service consumes it.
  3. OrderConfirmationPublisher decides based on stock.
  4. Either OrderConfirmedEvent or OrderOutOfStockEvent is published.
  5. Order Service updates its state accordingly.

Solved challenges

ChallengeSolution
Event‑driven stock reservationNo distributed transactions; eventual consistency via events
Atomic availability checksJPA transaction semantics in Inventory Service
Boilerplate reductionShared 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:

0 views
Back to Blog

Related posts

Read more »