-
Notifications
You must be signed in to change notification settings - Fork 121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make AggregateRoot#unpublished_events public #108
Conversation
Because it is necessary for testing and that's the public interface for exposing generated domain events.
I don't like it but I don't have a better idea so +1 for this |
@@ -26,13 +26,13 @@ def store(stream_name = loaded_from_stream_name, event_store: default_event_stor | |||
@unpublished_events = nil | |||
end | |||
|
|||
private | |||
attr_reader :loaded_from_stream_name | |||
|
|||
def unpublished_events | |||
@unpublished_events ||= [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be safer to return a clone of this list for the public accessor? I'm worried that debugging of situations when someone mutates this object will be difficult.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, agree... I would keep original method private. Maybe returning iterator function will be better and much safer. And will not require always calling clone even for internal usage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for testing you can always use metaprograming:
require 'rspec/expectations'
require_relative 'utils'
RSpec::Matchers.define :publish do |expected_events|
match do |aggregate|
@stripped_actual = as_string(aggregate.__send__("unpublished_events"))
@stripped_expected = as_string(expected_events)
expect(@stripped_actual).to eq @stripped_expected
end
failure_message do |aggregate|
message = "Expected aggregate:\n\n#{aggregate.inspect}\n\nto " \
"publish events:\n\n#{@stripped_actual}\n\nwould match:\n\n#{@stripped_expected}"
message += "\n\nDiff: #{differ.diff_as_string(@stripped_actual, @stripped_expected)}"
message
end
def differ
RSpec::Support::Differ.new(
color: RSpec::Matchers.configuration.color?
)
end
end
Its not perfect but I'm fine with using little bit of "magic". Exposing this method to the public will open way of overusing it or just misuse of aggregate. I know from past experiences that people even thinking about using aggregate root state as public and skip making read models, I don't think that giving here to much freedom is a best way to deal with testing problem
It would
lovely idea
that sounds like a bad design to me. If the purpose of an aggregate is to protect rules and generate events then those events are public. Public in a sense that someone needs to consume them and publish. Unfortunately those safe options from production point of view does not solve the original issue that much. If you use not possible on cloned collection or iterator
not convinient
workaroundI use the solution we had for #109 right now.
therefore fixing this is not that important for me. However it seems that @andrzejsliwa is using Perhaps def unpublished_events
@unpublished_events ||= []
@unpublished_events.each
end is good enough to be safe and support those usecases. I would go for that. |
def unpublished_events
@unpublished_events ||= []
@unpublished_events.each
end
👍 |
Because it is necessary for testing and that's the public interface for
exposing generated domain events.