Help us measure site usage?

With your permission, we use self-hosted analytics to understand page usage. It is optional and does not affect the Service. Learn more.

RubyadvancedbackendAI generated

Event-Sourced Inventory

Build an inventory system in Rails where stock levels are never simply overwritten: every change (goods received, items sold, returns, damage, stock counts) is recorded as an event that can't be changed, and the current stock is worked out from those events. This design, called event sourcing, gives you a complete audit history for free: you can see exactly why a number is what it is, and what the stock was on any past date. To stay fast, the current stock is kept in a projection table updated from the events, which can be rebuilt from scratch at any time. You'll learn event sourcing and projections, optimistic concurrency with stream versions, enforcing business rules, idempotency, and testing with Rails.

Estimate
~15.5h
Steps
7
Completed by
0
Proposed by
codeseed.app

Rails · RubyGems

Project roadmap

  1. 01

    Design the event model

    ~2.5h

    Write DESIGN.md: one event stream per product and warehouse; event types (StockReceived, StockSold, StockReturned, StockDamaged and StockCounted, which sets an absolute quantity) with their data; the rule that stock can never go negative; the events table (stream id, version, type, data as JSON, metadata such as user and reason, idempotency key and created at) with a unique index on stream id plus version; and the projections (current stock, and a movements history). Done when: DESIGN.md covers events, rules and projections.

  2. 02

    Store events safely

    ~2.5h

    Create the events table (with no updates or deletes allowed, enforced in the model and with a database trigger). Write an EventStore class with append(stream, events, expected_version) that inserts events with consecutive versions in a transaction, and raises a conflict error when another process already wrote that version (caught from the unique index). Reject repeated idempotency keys by returning the earlier result. Done when: two simultaneous appends at the same version result in one success and one conflict.

  3. 03

    Enforce business rules

    ~2h

    Write an InventoryItem class that rebuilds its state (quantity and version) by replaying a stream's events, and command methods (receive, sell, return, record damage, count) that check the rules, such as not selling more than is in stock, and produce new events. A command service loads the item, runs the command and appends with the expected version, retrying once on a conflict. Done when: selling more than the stock is refused, and valid commands append events.

  4. 04

    Build projections

    ~2.5h

    Build projection tables: current stock per product and warehouse, and stock movements. A projector processes events in order, keeping the last processed event position, and updates the tables idempotently, so processing an event twice changes nothing. Add a rake task that rebuilds all projections from scratch. Done when: the projections match a full replay of the events, including after a rebuild.

  5. 05

    Expose the API

    ~2h

    Add API endpoints for the commands (POST /inventory/receive, /sell and so on, each with an Idempotency-Key header), GET /stock (from the projection), GET /stock/:product/history (the events with their users and reasons) and GET /stock/:product/at?time= (stock at a past moment, by replaying events up to that time). Return 409 on concurrency conflicts and 422 on rule violations. Done when: every endpoint works, and history explains every change.

  6. 06

    Add snapshots and monitoring

    ~1.5h

    For streams with many events, store a snapshot of the state every 100 events and replay only newer events. Log each command with its result, and expose the projector's lag (how many events it's behind). Done when: loading a long stream uses the snapshot, and the lag is visible.

  7. 07

    Test it

    ~2.5h

    Write RSpec tests: each command's events and rules, concurrency conflicts with threads, idempotency keys, projection updates and a full rebuild giving identical results, stock at a past time, and snapshots giving the same state as a full replay. Done when: all tests pass.

Ready to build this?

Get a GitHub repo and start building. Your AI reviewer checks each step as you go.

~15.5h · 7 steps

Tech stack

RailsRubyGems