inventory-service is a Quarkus service that owns all stock data: quantities on hand, soft and hard reservations, location-level tracking, purchase orders, and inter-location transfers. Storage is PostgreSQL with store_id as a discriminator column for multi-tenant isolation.
Multiple Locations#
A store can have any number of locations. The locations table records each one with location_id, store_id, name, and type (warehouse, retail outlet, fulfilment centre, virtual). Stock is tracked per variant per location.
Stock Model#
Inventory state is held across two complementary structures.
stock_levels#
stock_levels is the current materialized view of stock for each (variant_id, location_id, store_id) combination. Columns:
| Column | Type | Notes |
|---|---|---|
on_hand | INT | Physical units present |
soft_reserved | INT | Held by active carts (TTL-bound) |
hard_reserved | INT | Committed by in-progress checkouts |
incoming | INT | Expected from confirmed purchase orders |
Available to sell is a derived value: on_hand - soft_reserved - hard_reserved. This is the figure returned to callers and used for add-to-cart checks.
stock_movements#
stock_movements is an append-only ledger. Every stock change — regardless of source — writes an immutable row before stock_levels is updated. This provides a full audit trail and the ability to recompute stock_levels from scratch.
Supported movement types:
| Type | Trigger |
|---|---|
SOFT_RESERVATION | Cart add-to-cart |
SOFT_RESERVATION_EXPIRED | TTL expiry of a soft reservation |
HARD_RESERVATION | Checkout commits a reservation |
SOFT_RELEASED | Soft reservation manually released |
HARD_RELEASED | Hard reservation released on checkout failure or cancellation |
ORDER_CONSUMED | order.placed event consumed — stock deducted |
ADJUSTMENT_IN | Manual positive stock adjustment |
ADJUSTMENT_OUT | Manual negative stock adjustment |
TRANSFER_IN | Stock received at a location from a transfer |
TRANSFER_OUT | Stock sent from a location to a transfer |
PO_RECEIVED | Purchase order marked RECEIVED |
ERP_SYNC | Authoritative sync from external ERP |
Reservation Lifecycle#
Reservations move through a defined lifecycle. Soft and hard reservations are held at the store’s aggregate pool level, not pinned to a specific location — location assignment is a fulfilment concern handled downstream by the order or fulfilment service.
Add to Cart#
cart-service calls PlaceSoftReservation. On success: soft_reserved++, a SOFT_RESERVATION movement is written, and a reservation_id is returned with an attached TTL. If available_to_sell is insufficient and the variant’s backorder policy is DENY, the service returns INSUFFICIENT_STOCK.
Checkout#
checkout-service calls UpgradeToHardReservation. On success: soft_reserved--, hard_reserved++, and a HARD_RESERVATION movement is written.
Order Placed#
inventory-service consumes the order.placed Kafka event. On receipt: hard_reserved--, on_hand--, and an ORDER_CONSUMED movement is written.
Soft Reservation TTL Expiry#
inventory-service manages TTL autonomously. When a soft reservation expires: soft_reserved--, a SOFT_RESERVATION_EXPIRED movement is written, and inventory.reservation_expired is published to Kafka for cart-service to act on.
Checkout Failure or Order Cancellation#
checkout-service or order-service calls ReleaseReservations. The service reverses the appropriate counts and writes SOFT_RELEASED or HARD_RELEASED movements.
Backorder Policies#
variant_inventory_settings stores per-variant configuration for how the service should behave when available_to_sell reaches zero.
| Policy | Behaviour |
|---|---|
DENY | Default. Add-to-cart is rejected when available_to_sell = 0. |
ALLOW | Units can be added beyond on_hand. No expected date is provided to the customer. |
PREORDER | Like ALLOW but an expected availability date is surfaced to the customer. |
The table also stores low_stock_threshold INT, which triggers the inventory.low_stock event when on_hand drops below it.
Purchase Orders#
purchase_orders and po_line_items track incoming stock from external suppliers.
Purchase order statuses: DRAFT → CONFIRMED → RECEIVED. When a purchase order is marked RECEIVED, inventory-service increments on_hand for each line item and writes a PO_RECEIVED movement.
incoming on stock_levels reflects the sum of quantities on all CONFIRMED purchase orders for that variant at that location.
Inter-Location Transfers#
stock_transfers and transfer_line_items model movement of existing stock between a store’s locations.
On processing a transfer: the source location’s on_hand is decremented and a TRANSFER_OUT movement is written; the destination location’s on_hand is incremented and a TRANSFER_IN movement is written.
gRPC Interface#
These methods are exposed to internal callers only.
| Method | Called By | Purpose |
|---|---|---|
PlaceSoftReservation(variantId, storeId, quantity, ttlSeconds) | cart-service | Reserve stock for a cart; returns reservationId or INSUFFICIENT_STOCK |
UpgradeToHardReservation(reservationId, orderId) | checkout-service | Commit a soft reservation to a hard reservation |
ReleaseReservations(reservationIds[]) | cart-service, checkout-service, order-service | Release soft or hard reservations on failure or cancellation |
GetStockLevels(variantIds[], storeId) | catalog-service | Return current stock levels for display at render time |
Kafka Consumed#
| Topic | Action |
|---|---|
order.placed | Consume hard reservations → write ORDER_CONSUMED movements, decrement on_hand |
order.cancelled | Release hard reservations → write HARD_RELEASED movements |
Kafka Events Published#
| Topic | Trigger |
|---|---|
inventory.stock_updated | Any stock level change |
inventory.low_stock | on_hand drops below low_stock_threshold |
inventory.out_of_stock | available_to_sell reaches 0 |
inventory.back_in_stock | available_to_sell returns above 0 after being 0 |
inventory.reservation_expired | Soft reservation TTL expiry; consumed by cart-service to clean up the cart |
Vault Configuration#
| Key path | Purpose |
|---|---|
ss3/kv/inventory-service/db.url | PostgreSQL JDBC URL |
ss3/kv/inventory-service/db.username | Database username |
ss3/kv/inventory-service/db.password | Database password |
ss3/kv/shared/ | Shared Kafka bootstrap and OTel config |