From c67ebd7a5f4783d2c92876b0c5d9a9891e2a3cf4 Mon Sep 17 00:00:00 2001 From: matst80 Date: Tue, 4 Nov 2025 12:48:41 +0100 Subject: [PATCH] initial inventory service --- cmd/inventory/main.go | 41 +++++++++++ go.mod | 2 + go.sum | 8 +++ pkg/actor/mutation_registry_test.go | 4 +- pkg/inventory/memory_service.go | 47 ++++++++++++ pkg/inventory/redis_service.go | 107 ++++++++++++++++++++++++++++ pkg/inventory/types.go | 30 ++++++++ 7 files changed, 237 insertions(+), 2 deletions(-) create mode 100644 cmd/inventory/main.go create mode 100644 pkg/inventory/memory_service.go create mode 100644 pkg/inventory/redis_service.go create mode 100644 pkg/inventory/types.go diff --git a/cmd/inventory/main.go b/cmd/inventory/main.go new file mode 100644 index 0000000..ad97b91 --- /dev/null +++ b/cmd/inventory/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "context" + "fmt" + + "github.com/redis/go-redis/v9" + "github.com/redis/go-redis/v9/maintnotifications" +) + +func main() { + var ctx = context.Background() + rdb := redis.NewClient(&redis.Options{ + Addr: "10.10.3.18:6379", + Password: "slaskredis", // no password set + DB: 0, // use default DB + MaintNotificationsConfig: &maintnotifications.Config{ + Mode: maintnotifications.ModeDisabled, + }, + }) + + err := rdb.Set(ctx, "key", "value", 0).Err() + if err != nil { + panic(err) + } + + val, err := rdb.Get(ctx, "key").Result() + if err != nil { + panic(err) + } + fmt.Println("key", val) + + val2, err := rdb.Get(ctx, "key2").Result() + if err == redis.Nil { + fmt.Println("key2 does not exist") + } else if err != nil { + panic(err) + } else { + fmt.Println("key2", val2) + } +} diff --git a/go.mod b/go.mod index 1f3ba0f..1d419b1 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/matst80/slask-finder v0.0.0-20251023104024-f788e5a51d68 github.com/prometheus/client_golang v1.23.2 github.com/rabbitmq/amqp091-go v1.10.0 + github.com/redis/go-redis/v9 v9.16.0 google.golang.org/grpc v1.76.0 google.golang.org/protobuf v1.36.10 k8s.io/api v0.34.1 @@ -21,6 +22,7 @@ require ( github.com/bits-and-blooms/bitset v1.24.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect diff --git a/go.sum b/go.sum index 154587e..abe6863 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,10 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.24.1 h1:hqnfFbjjk3pxGa5E9Ho3hjoU7odtUuNmJ9Ao+Bo8s1c= github.com/bits-and-blooms/bitset v1.24.1/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -13,6 +17,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= @@ -168,6 +174,8 @@ github.com/prometheus/procfs v0.18.0 h1:2QTA9cKdznfYJz7EDaa7IiJobHuV7E1WzeBwcrhk github.com/prometheus/procfs v0.18.0/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw= github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o= +github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4= +github.com/redis/go-redis/v9 v9.16.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= diff --git a/pkg/actor/mutation_registry_test.go b/pkg/actor/mutation_registry_test.go index 58e134e..36602f1 100644 --- a/pkg/actor/mutation_registry_test.go +++ b/pkg/actor/mutation_registry_test.go @@ -21,8 +21,8 @@ func TestRegisteredMutationBasics(t *testing.T) { func(state *cartState, msg *messages.AddItem) error { state.calls++ // copy to avoid external mutation side-effects (not strictly necessary for the test) - cp := *msg - state.lastAdded = &cp + cp := msg + state.lastAdded = cp return nil }, func() *messages.AddItem { return &messages.AddItem{} }, diff --git a/pkg/inventory/memory_service.go b/pkg/inventory/memory_service.go new file mode 100644 index 0000000..d3f6a70 --- /dev/null +++ b/pkg/inventory/memory_service.go @@ -0,0 +1,47 @@ +package inventory + +import ( + "errors" + "sync" +) + +type MemoryInventoryService struct { + warehouses map[LocationID]*Warehouse + mu sync.RWMutex +} + +func NewMemoryInventoryService() *MemoryInventoryService { + return &MemoryInventoryService{ + warehouses: make(map[LocationID]*Warehouse), + } +} + +func (s *MemoryInventoryService) AddWarehouse(warehouse *Warehouse) { + s.mu.Lock() + defer s.mu.Unlock() + s.warehouses[warehouse.ID] = warehouse +} + +func (s *MemoryInventoryService) GetInventory(sku SKU, locationID LocationID) (*InventoryItem, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + warehouse, ok := s.warehouses[locationID] + if !ok { + return nil, errors.New("warehouse not found") + } + + for _, item := range warehouse.Inventory { + if item.SKU == sku { + return &item, nil + } + } + return nil, errors.New("sku not found in warehouse") +} + +func (s *MemoryInventoryService) ReserveInventory(req ...ReserveRequest) error { + // We'll implement the reservation logic using Lua script execution here + + // For now, let's just return an error indicating it's not implemented + return errors.New("reservation not implemented yet") +} diff --git a/pkg/inventory/redis_service.go b/pkg/inventory/redis_service.go new file mode 100644 index 0000000..0d8e944 --- /dev/null +++ b/pkg/inventory/redis_service.go @@ -0,0 +1,107 @@ +package inventory + +import ( + "context" + "encoding/json" + "errors" + + "github.com/redis/go-redis/v9" +) + +type RedisInventoryService struct { + client *redis.Client + ctx context.Context + luaScripts map[string]*redis.Script +} + +func NewRedisInventoryService(client *redis.Client, ctx context.Context) (*RedisInventoryService, error) { + rdb := client + + // Ping Redis to check connection + _, err := rdb.Ping(ctx).Result() + if err != nil { + return nil, err + } + + return &RedisInventoryService{ + client: rdb, + ctx: ctx, + luaScripts: make(map[string]*redis.Script), + }, nil +} + +func (s *RedisInventoryService) LoadLuaScript(key string) error { + // Get the script from Redis + script, err := s.client.Get(s.ctx, key).Result() + if err != nil { + return err + } + + // Load the script into the luaScripts cache + s.luaScripts[key] = redis.NewScript(script) + return nil +} + +func (s *RedisInventoryService) AddWarehouse(warehouse *Warehouse) error { + // Convert warehouse to Redis-friendly format + data := map[string]interface{}{ + "id": string(warehouse.ID), + "name": warehouse.Name, + "inventory": warehouse.Inventory, + } + + // Store in Redis with a key pattern like "warehouse:" + key := "warehouse:" + string(warehouse.ID) + _, err := s.client.HMSet(s.ctx, key, data).Result() + return err +} + +func (s *RedisInventoryService) GetInventory(sku SKU, locationID LocationID) (*InventoryItem, error) { + // Get the warehouse from Redis + key := "warehouse:" + string(locationID) + result, err := s.client.HGetAll(s.ctx, key).Result() + if err != nil { + return nil, err + } + + // Parse the inventory items + var inventoryItems []InventoryItem + for _, itemData := range result { + var item InventoryItem + if err := json.Unmarshal([]byte(itemData), &item); err == nil { + inventoryItems = append(inventoryItems, item) + } + } + + // Find the requested SKU + for _, item := range inventoryItems { + if item.SKU == sku { + return &item, nil + } + } + + return nil, errors.New("sku not found in warehouse") +} + +func (s *RedisInventoryService) ReserveInventory(req ReserveRequest) error { + return nil + // Get the Lua script from Redis + // key := "lua:reserve_inventory" + // script, err := s.client.Get(s.ctx, key).Result() + // if err != nil { + // return err + // } + + // luaScript := redis.NewScript(script) + + // // Prepare arguments for the Lua script + // args := []interface{}{ + // string(req.LocationID), + // string(req.SKU), + // req.Quantity, + // } + + // // Execute the Lua script + // cmd := s.client.Eval(s.ctx, luaScript, len(args), args...) + //return err +} diff --git a/pkg/inventory/types.go b/pkg/inventory/types.go new file mode 100644 index 0000000..80ea8be --- /dev/null +++ b/pkg/inventory/types.go @@ -0,0 +1,30 @@ +package inventory + +import "time" + +type SKU string + +type LocationID string + +type InventoryItem struct { + SKU SKU + Quantity int + LastUpdate time.Time +} + +type Warehouse struct { + ID LocationID + Name string + Inventory []InventoryItem +} + +type InventoryService interface { + GetInventory(sku SKU, locationID LocationID) (uint32, error) + ReserveInventory(req ...ReserveRequest) error +} + +type ReserveRequest struct { + SKU SKU + LocationID LocationID + Quantity uint32 +}