Files
go-redis-inventory/pkg/inventory/adapter.go
T
2026-07-25 18:12:26 +02:00

78 lines
3.2 KiB
Go

package inventory
import (
"context"
"errors"
platforminv "git.k6n.net/mats/platform/inventory"
)
// ReservationPolicyAdapter wraps a RedisCartReservationService to satisfy the
// platform/inventory.ReservationPolicy interface. It translates between the
// platform's value types (SKU, LocationID, CartID, CartReserveRequest,
// InventoryReference) and the Redis-level types used internally by
// go-redis-inventory, so that callers (cart, checkout, backoffice, the
// inventory service itself) can depend on the seam and remain agnostic to the
// storage substrate — see docs/inventory-shape.md §"Next: inventory service as
// single writer".
//
// The error translation below means callers can `errors.Is(err,
// platforminv.ErrInsufficientStock)` regardless of whether the underlying
// storage is Redis Lua (today), an actor grain (the deep rewrite), or
// something else (the test adapter in platform/inventory/memory.go).
type ReservationPolicyAdapter struct {
inner *RedisCartReservationService
}
// NewReservationPolicyAdapter creates an adapter that satisfies
// platform/inventory.ReservationPolicy by delegating to the Redis-backed
// reservation service.
func NewReservationPolicyAdapter(inner *RedisCartReservationService) *ReservationPolicyAdapter {
return &ReservationPolicyAdapter{inner: inner}
}
// Compile-time assertion that ReservationPolicyAdapter satisfies the
// platform/inventory.ReservationPolicy interface. If a method is added or
// renamed on either side, this stops compiling.
var _ platforminv.ReservationPolicy = (*ReservationPolicyAdapter)(nil)
func (a *ReservationPolicyAdapter) Reserve(ctx context.Context, req platforminv.CartReserveRequest) error {
return translateReservationError(a.inner.Reserve(ctx, CartReserveRequest{
InventoryReference: &InventoryReference{
SKU: SKU(req.SKU),
LocationID: LocationID(req.LocationID),
},
CartID: CartID(req.CartID),
Quantity: req.Quantity,
TTL: req.TTL,
}))
}
func (a *ReservationPolicyAdapter) Release(ctx context.Context, sku platforminv.SKU, locationID platforminv.LocationID, cartID platforminv.CartID) error {
return a.inner.Release(ctx, SKU(sku), LocationID(locationID), CartID(cartID))
}
func (a *ReservationPolicyAdapter) Available(ctx context.Context, sku platforminv.SKU, locationID platforminv.LocationID) (int64, error) {
return a.inner.Available(ctx, SKU(sku), LocationID(locationID))
}
// translateReservationError maps the Redis-storage-specific sentinel to the
// platform-level sentinel so consumers can errors.Is against one set of errors
// regardless of the underlying ReservationPolicy implementation. Pass-through
// for unknown errors — those carry enough context on their own.
//
// Trade-off: the original Redis sentinel is replaced, not wrapped, so callers
// that need to discriminate "Redis ran out of stock" from "Lua script timed
// out" must look inside the seam rather than outside. Per the platform seam
// contract that's the right cut: storage internals stay encapsulated; callers
// branch on the cross-module vocabulary.
func translateReservationError(err error) error {
if err == nil {
return nil
}
if errors.Is(err, ErrInsufficientInventory) {
return platforminv.ErrInsufficientStock
}
return err
}