From 89ff0709f5f40f290167839e26d36ea81a60869b Mon Sep 17 00:00:00 2001 From: lukaszreszke Date: Tue, 6 Aug 2024 12:28:35 +0200 Subject: [PATCH 01/11] Encapsulate state --- ecommerce/ordering/lib/ordering/order.rb | 44 +++++++++++++++++------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/ecommerce/ordering/lib/ordering/order.rb b/ecommerce/ordering/lib/ordering/order.rb index bd8244bb..01bd76fe 100644 --- a/ecommerce/ordering/lib/ordering/order.rb +++ b/ecommerce/ordering/lib/ordering/order.rb @@ -9,13 +9,13 @@ class Order def initialize(id) @id = id - @state = :draft + @state = State.new(:draft) @basket = Basket.new end def submit(order_number) - raise OrderHasExpired if @state.equal?(:expired) - raise AlreadySubmitted unless @state.equal?(:draft) + raise OrderHasExpired if @state.expired? + raise AlreadySubmitted unless @state.draft? apply OrderSubmitted.new( data: { order_id: @id, @@ -26,7 +26,7 @@ def submit(order_number) end def accept - raise InvalidState unless @state.equal?(:submitted) + raise InvalidState unless @state.submitted? apply OrderPlaced.new( data: { order_id: @id, @@ -37,7 +37,7 @@ def accept end def reject - raise InvalidState unless @state.equal?(:submitted) + raise InvalidState unless @state.submitted? apply OrderRejected.new( data: { order_id: @id @@ -46,12 +46,12 @@ def reject end def expire - raise AlreadySubmitted unless @state.equal?(:draft) + raise AlreadySubmitted unless @state.draft? apply OrderExpired.new(data: { order_id: @id }) end def add_item(product_id) - raise AlreadySubmitted unless @state.equal?(:draft) + raise AlreadySubmitted unless @state.draft? apply ItemAddedToBasket.new( data: { order_id: @id, @@ -61,16 +61,16 @@ def add_item(product_id) end def remove_item(product_id) - raise AlreadySubmitted unless @state.equal?(:draft) + raise AlreadySubmitted unless @state.draft? apply ItemRemovedFromBasket.new(data: { order_id: @id, product_id: product_id }) end on OrderPlaced do |event| - @state = :accepted + @state = State.new(:accepted) end on OrderExpired do |event| - @state = :expired + @state = State.new(:expired) end on ItemAddedToBasket do |event| @@ -83,11 +83,11 @@ def remove_item(product_id) on OrderSubmitted do |event| @order_number = event.data[:order_number] - @state = :submitted + @state = State.new(:submitted) end on OrderRejected do |event| - @state = :draft + @state = State.new(:draft) end class Basket @@ -113,4 +113,24 @@ def quantity(product_id) end end end + + class State + ALLOWED_STATES = %i(draft submitted expired accepted) + + def initialize(state = :draft) + @state = state + end + + def draft? + @state.equal?(:draft) + end + + def submitted? + @state.equal?(:submitted) + end + + def expired? + @state.equal?(:expired) + end + end end From 438b224eb88a30cfbda97443f887551fdfee81a6 Mon Sep 17 00:00:00 2001 From: lukaszreszke Date: Tue, 6 Aug 2024 12:30:27 +0200 Subject: [PATCH 02/11] Refactor --- ecommerce/ordering/lib/ordering/order.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/ecommerce/ordering/lib/ordering/order.rb b/ecommerce/ordering/lib/ordering/order.rb index 01bd76fe..7b60611f 100644 --- a/ecommerce/ordering/lib/ordering/order.rb +++ b/ecommerce/ordering/lib/ordering/order.rb @@ -66,11 +66,11 @@ def remove_item(product_id) end on OrderPlaced do |event| - @state = State.new(:accepted) + @state = @state.transition_to(:accepted) end on OrderExpired do |event| - @state = State.new(:expired) + @state = @state.transition_to(:expired) end on ItemAddedToBasket do |event| @@ -83,11 +83,11 @@ def remove_item(product_id) on OrderSubmitted do |event| @order_number = event.data[:order_number] - @state = State.new(:submitted) + @state = @state.transition_to(:submitted) end on OrderRejected do |event| - @state = State.new(:draft) + @state = State.new end class Basket @@ -132,5 +132,10 @@ def submitted? def expired? @state.equal?(:expired) end + + def transition_to(state) + raise InvalidState unless ALLOWED_STATES.include?(state) + State.new(state) + end end end From 1f64ccd00fb0565f038ef2f10f64bf8398ba83c8 Mon Sep 17 00:00:00 2001 From: lukaszreszke Date: Tue, 6 Aug 2024 12:31:09 +0200 Subject: [PATCH 03/11] Allowed transitions --- ecommerce/ordering/lib/ordering/order.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ecommerce/ordering/lib/ordering/order.rb b/ecommerce/ordering/lib/ordering/order.rb index 7b60611f..6986b5a2 100644 --- a/ecommerce/ordering/lib/ordering/order.rb +++ b/ecommerce/ordering/lib/ordering/order.rb @@ -116,6 +116,12 @@ def quantity(product_id) class State ALLOWED_STATES = %i(draft submitted expired accepted) + ALLOWED_TRANSITIONS = { + draft: %i(submitted expired), + submitted: %i(accepted rejected), + expired: %i(draft) + } + def initialize(state = :draft) @state = state @@ -135,6 +141,7 @@ def expired? def transition_to(state) raise InvalidState unless ALLOWED_STATES.include?(state) + raise InvalidState unless ALLOWED_TRANSITIONS[@state].include?(state) State.new(state) end end From 4070fc96f66218d249b8dc12572a2467918e584f Mon Sep 17 00:00:00 2001 From: lukaszreszke Date: Tue, 6 Aug 2024 12:33:03 +0200 Subject: [PATCH 04/11] Remove empty line --- ecommerce/ordering/lib/ordering/order.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/ecommerce/ordering/lib/ordering/order.rb b/ecommerce/ordering/lib/ordering/order.rb index 6986b5a2..bdc1b949 100644 --- a/ecommerce/ordering/lib/ordering/order.rb +++ b/ecommerce/ordering/lib/ordering/order.rb @@ -122,7 +122,6 @@ class State expired: %i(draft) } - def initialize(state = :draft) @state = state end From 58344906295a537162c3f7d0d143bc9f15c2a446 Mon Sep 17 00:00:00 2001 From: lukaszreszke Date: Tue, 6 Aug 2024 12:57:53 +0200 Subject: [PATCH 05/11] add eql? and hash methods --- ecommerce/ordering/lib/ordering/order.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ecommerce/ordering/lib/ordering/order.rb b/ecommerce/ordering/lib/ordering/order.rb index bdc1b949..1d29024f 100644 --- a/ecommerce/ordering/lib/ordering/order.rb +++ b/ecommerce/ordering/lib/ordering/order.rb @@ -143,5 +143,15 @@ def transition_to(state) raise InvalidState unless ALLOWED_TRANSITIONS[@state].include?(state) State.new(state) end + + def eql?(other) + other.instance_of?(State) && other.state.equal?(@state) + end + + def hash + State.hash ^ @state.hash + end + + alias == eql? end end From 217a506c257a86cfc71a2b4061886760c0565f33 Mon Sep 17 00:00:00 2001 From: lukaszreszke Date: Tue, 6 Aug 2024 13:07:48 +0200 Subject: [PATCH 06/11] refactor --- ecommerce/ordering/lib/ordering/order.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ecommerce/ordering/lib/ordering/order.rb b/ecommerce/ordering/lib/ordering/order.rb index 1d29024f..f0223491 100644 --- a/ecommerce/ordering/lib/ordering/order.rb +++ b/ecommerce/ordering/lib/ordering/order.rb @@ -9,7 +9,7 @@ class Order def initialize(id) @id = id - @state = State.new(:draft) + @state = State.new @basket = Basket.new end From 4164face5d35f00e043c9bd902eaf7cd0dfd2e04 Mon Sep 17 00:00:00 2001 From: lukaszreszke Date: Tue, 6 Aug 2024 17:07:49 +0200 Subject: [PATCH 07/11] Move State class into Order --- ecommerce/ordering/lib/ordering/order.rb | 64 ++++++++++++------------ 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/ecommerce/ordering/lib/ordering/order.rb b/ecommerce/ordering/lib/ordering/order.rb index f0223491..171def6b 100644 --- a/ecommerce/ordering/lib/ordering/order.rb +++ b/ecommerce/ordering/lib/ordering/order.rb @@ -112,46 +112,46 @@ def quantity(product_id) order_lines[product_id] end end - end - class State - ALLOWED_STATES = %i(draft submitted expired accepted) - ALLOWED_TRANSITIONS = { - draft: %i(submitted expired), - submitted: %i(accepted rejected), - expired: %i(draft) - } + class State + ALLOWED_STATES = %i(draft submitted expired accepted) + ALLOWED_TRANSITIONS = { + draft: %i(submitted expired), + submitted: %i(accepted rejected), + expired: %i(draft) + } - def initialize(state = :draft) - @state = state - end + def initialize(state = :draft) + @state = state + end - def draft? - @state.equal?(:draft) - end + def draft? + @state.equal?(:draft) + end - def submitted? - @state.equal?(:submitted) - end + def submitted? + @state.equal?(:submitted) + end - def expired? - @state.equal?(:expired) - end + def expired? + @state.equal?(:expired) + end - def transition_to(state) - raise InvalidState unless ALLOWED_STATES.include?(state) - raise InvalidState unless ALLOWED_TRANSITIONS[@state].include?(state) - State.new(state) - end + def transition_to(state) + raise InvalidState unless ALLOWED_STATES.include?(state) + raise InvalidState unless ALLOWED_TRANSITIONS[@state].include?(state) + State.new(state) + end - def eql?(other) - other.instance_of?(State) && other.state.equal?(@state) - end + def eql?(other) + other.instance_of?(State) && other.state.equal?(@state) + end - def hash - State.hash ^ @state.hash - end + def hash + State.hash ^ @state.hash + end - alias == eql? + alias == eql? + end end end From b7dc1f5df3d6fb54f9a7bbffe63195be864adb79 Mon Sep 17 00:00:00 2001 From: lukaszreszke Date: Tue, 6 Aug 2024 17:09:09 +0200 Subject: [PATCH 08/11] Currently this VO is not used for any comparison --- ecommerce/ordering/lib/ordering/order.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/ecommerce/ordering/lib/ordering/order.rb b/ecommerce/ordering/lib/ordering/order.rb index 171def6b..a3313e02 100644 --- a/ecommerce/ordering/lib/ordering/order.rb +++ b/ecommerce/ordering/lib/ordering/order.rb @@ -142,16 +142,6 @@ def transition_to(state) raise InvalidState unless ALLOWED_TRANSITIONS[@state].include?(state) State.new(state) end - - def eql?(other) - other.instance_of?(State) && other.state.equal?(@state) - end - - def hash - State.hash ^ @state.hash - end - - alias == eql? end end end From 73adf444c708ca22e9afff2531ca5530c7683749 Mon Sep 17 00:00:00 2001 From: lukaszreszke Date: Tue, 6 Aug 2024 17:10:40 +0200 Subject: [PATCH 09/11] Remove transition_to method The `transition_to` method was only called in the `on` DSL. The problem is that the transition_to method raises the exception when state is either not allowed or it is not valid transition. Exceptions shouldn't be raised when building state of the aggregate. Past is past. --- ecommerce/ordering/lib/ordering/order.rb | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/ecommerce/ordering/lib/ordering/order.rb b/ecommerce/ordering/lib/ordering/order.rb index a3313e02..37221c71 100644 --- a/ecommerce/ordering/lib/ordering/order.rb +++ b/ecommerce/ordering/lib/ordering/order.rb @@ -66,11 +66,11 @@ def remove_item(product_id) end on OrderPlaced do |event| - @state = @state.transition_to(:accepted) + @state = State.new(:accepted) end on OrderExpired do |event| - @state = @state.transition_to(:expired) + @state = State.new(:expired) end on ItemAddedToBasket do |event| @@ -83,7 +83,7 @@ def remove_item(product_id) on OrderSubmitted do |event| @order_number = event.data[:order_number] - @state = @state.transition_to(:submitted) + @state = State.new(:submitted) end on OrderRejected do |event| @@ -136,12 +136,6 @@ def submitted? def expired? @state.equal?(:expired) end - - def transition_to(state) - raise InvalidState unless ALLOWED_STATES.include?(state) - raise InvalidState unless ALLOWED_TRANSITIONS[@state].include?(state) - State.new(state) - end end end end From 3077db02e049ccec4a018c7e718905ac8a038269 Mon Sep 17 00:00:00 2001 From: lukaszreszke Date: Tue, 6 Aug 2024 17:12:49 +0200 Subject: [PATCH 10/11] Remove unused --- ecommerce/ordering/lib/ordering/order.rb | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ecommerce/ordering/lib/ordering/order.rb b/ecommerce/ordering/lib/ordering/order.rb index 37221c71..ea8b7ab0 100644 --- a/ecommerce/ordering/lib/ordering/order.rb +++ b/ecommerce/ordering/lib/ordering/order.rb @@ -114,13 +114,6 @@ def quantity(product_id) end class State - ALLOWED_STATES = %i(draft submitted expired accepted) - ALLOWED_TRANSITIONS = { - draft: %i(submitted expired), - submitted: %i(accepted rejected), - expired: %i(draft) - } - def initialize(state = :draft) @state = state end From d844c35f629f87435b2ce63dabab1558e8b2ab41 Mon Sep 17 00:00:00 2001 From: lukaszreszke Date: Tue, 6 Aug 2024 17:23:51 +0200 Subject: [PATCH 11/11] States as methods Accepted didn't get it's own method as it doesn't seem like this "accepted" information is used in any way --- ecommerce/ordering/lib/ordering/order.rb | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/ecommerce/ordering/lib/ordering/order.rb b/ecommerce/ordering/lib/ordering/order.rb index ea8b7ab0..48c5b364 100644 --- a/ecommerce/ordering/lib/ordering/order.rb +++ b/ecommerce/ordering/lib/ordering/order.rb @@ -9,7 +9,7 @@ class Order def initialize(id) @id = id - @state = State.new + @state = State.draft @basket = Basket.new end @@ -70,7 +70,7 @@ def remove_item(product_id) end on OrderExpired do |event| - @state = State.new(:expired) + @state = State.expired end on ItemAddedToBasket do |event| @@ -83,11 +83,11 @@ def remove_item(product_id) on OrderSubmitted do |event| @order_number = event.data[:order_number] - @state = State.new(:submitted) + @state = State.submitted end on OrderRejected do |event| - @state = State.new + @state = State.draft end class Basket @@ -114,7 +114,19 @@ def quantity(product_id) end class State - def initialize(state = :draft) + def self.draft + new(:draft) + end + + def self.submitted + new(:submitted) + end + + def self.expired + new(:expired) + end + + def initialize(state) @state = state end