diff --git a/rails_application/app/models/inventory/product.rb b/rails_application/app/models/inventory/product.rb new file mode 100644 index 00000000..5204693b --- /dev/null +++ b/rails_application/app/models/inventory/product.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Inventory + class Product + include AggregateRoot + + private attr_reader :id + + def initialize(id) + @id = id + @stock_level = 0 + end + + def supply(quantity) + apply(StockLevelIncreased.new(data: { id:, quantity: })) + end + + def withdraw(quantity) + raise "Not enough stock" if @stock_level < quantity + apply(StockLevelDecreased.new(data: { id:, quantity: })) + end + + on StockLevelIncreased do |event| + @stock_level += event.data[:quantity] + end + + on StockLevelDecreased do |event| + @stock_level -= event.data[:quantity] + end + end +end diff --git a/rails_application/app/models/inventory/stock_level_decreased.rb b/rails_application/app/models/inventory/stock_level_decreased.rb new file mode 100644 index 00000000..4725c831 --- /dev/null +++ b/rails_application/app/models/inventory/stock_level_decreased.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +module Inventory + StockLevelDecreased = Class.new(Infra::Event) +end + diff --git a/rails_application/app/models/inventory/stock_level_increased.rb b/rails_application/app/models/inventory/stock_level_increased.rb new file mode 100644 index 00000000..0b62a69e --- /dev/null +++ b/rails_application/app/models/inventory/stock_level_increased.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module Inventory + StockLevelIncreased = Class.new(Infra::Event) +end diff --git a/rails_application/test/models/inventory/product_test.rb b/rails_application/test/models/inventory/product_test.rb new file mode 100644 index 00000000..77b025f5 --- /dev/null +++ b/rails_application/test/models/inventory/product_test.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'test_helper' + +module Inventory + class ProductTest < Infra::InMemoryTest + def test_supply + product_id = 1024 + + assert_events(stream_name(product_id), StockLevelIncreased.new(data: { id: product_id, quantity: 10 })) do + with_aggregate(product_id) do |product| + product.supply(10) + end + end + end + + def test_withdraw + product_id = 1024 + + assert_events(stream_name(product_id), + StockLevelIncreased.new(data: { id: product_id, quantity: 10 }), + StockLevelDecreased.new(data: { id: product_id, quantity: 5 }) + ) do + with_aggregate(product_id) do |product| + product.supply(10) + product.withdraw(5) + end + end + end + + def test_withdraw_when_not_enough_stock_is_not_allowed + product_id = 1024 + + assert_nothing_published_within do + assert_raises("Not enough stock") do + with_aggregate(product_id) do |product| + product.withdraw(10) + end + end + end + end + + private + + def stream_name(product_id) + "Inventory::Product$#{product_id}" + end + + def with_aggregate(product_id) + Infra::AggregateRootRepository.new(event_store).with_aggregate(Product, product_id) do |product| + yield product + end + end + end +end \ No newline at end of file