(Reminder that the completed component code is on GitLab. The completed state is different from what we’re working through here, but the files are at least all in pplace.)
In our last installment, we finished with a test that will tell us if our handler is writing the correct type of message as part of its execution.
The handler currently isn’t. In fact, it isn’t writing any message at all. Let’s change that. Back to lib/composite_component/handlers/commands.rb
, where we’ll see a handler come into being. Our first step is to declare that this handler actually handles something:
# Leaving out the module lines to save some space
class Commands
include Messaging::Handle
include Messages::Commands # <- Added the commands
dependency :write, Messaging::Postgres::Write
handle Build do |build| # <- The Build command handler
end
end
This adds a handle
call to the class definition. When we include
d Messaging::Handle
, we picked up this handle
method that lets us declare that we handle a particular message type. In this case, it’s the Build
command. We named this class Commands
, so it stands to reason that the message we handle in here would be commands.
We also include
Messages::Commands
. This makes it so that we don’t have write handle Messages::Commands::Build
. I’m not one to reduce keystrokes just to reduce keystrokes because chasing that end sometimes makes things less clear. I don’t think this makes things less clear.
A handle
call takes both the message class we’re handling and a block that implements what we do when handling that class’s message type. That block’s only parameter is an instance of that message class, and we typically name that variable the snake_case version of the class’s name.
The test assertion will still fail at this point because our handler still isn’t doing anything. Remember that we’re writing the simplest code that makes the test pass:
class Commands
include Messaging::Handle
include Messages::Commands
dependency :write, Messaging::Postgres::Write
handle Build do |build|
initiated = Initiated.new
end
end
The simplest way to have a handler write a particular message is to just instantiate a message of that type. However, this time we’ll get that NameError
we didn’t get last time. This code will actually execute:
$ ruby test/automated/handle_commands/build/initiated.rb
ruby 3.2.1 (2023-02-08 revision 31819e82c8) [arm64-darwin21]
TEST_BENCH_DETAIL: nil
Handle Commands
Build
Initiated
/Users/ethangarofolo/source/such-software/utah-microservices-meetup/do-expensive-things-once/composite-component/lib/composite_component/handlers/commands.rb:31:in `block in <class:Commands>': uninitialized constant CompositeComponent::Handlers::Commands::Initiated (NameError)
If you read the error message carefully, you’ll notice the uninitialized constant CompositeComponent::Handlers::Commands::Initiated
. It’s looking for an Initiated
constant under Handlers::Commands
. That’s because the class definition we’ve written doesn’t have that constant and neither does either module that we include
. We haven’t written this message definition.
Let’s make a new file in lib/composite_component/messages/events/initiated.rb
:
module CompositeComponent
module Messages
module Events
class Initiated
end
end
end
end
That structure mimics the Build
command’s structure, so no surprises.
We need to get this loaded into our program, so we go to the component’s loader file at lib/composite_component.rb
and add it:
require "eventide/postgres"
require "composite_component/messages/commands/build"
require "composite_component/messages/events/initiated" # <- Add it here
require "composite_component/handlers/commands"
The test would still fail at this point with the same error because we haven’t include
d our events into the handler class. Let’s fix that back in lib/composite_component/handlers/commands.rb
:
class Commands
include Messaging::Handle
include Messages::Commands
include Messages::Events
dependency :write, Messaging::Postgres::Write
handle Build do |build|
initiated = Initiated.new
end
end
Run the tests, and you’ll see we’re back to the assertion failing because we’re still not writing the event. Let’s write it:
dependency :write, Messaging::Postgres::Write
handle Build do |build|
initiated = Initiated.new
write.(initiated, "somestream")
end
We make use of the write
dependency
to actually write this message. It takes first the message we’re going to write and then the name of the stream we’re writing it to.
If “somestream”
seems odd to you as a stream name, good, you’re developing that sensibility. Remember that we’re writing the simplest code possible to get the test to pass. Our test currently only cares whether or not we’re writing an Initiated
event. It doesn’t yet require us to write it to the correct place.
Let’s try running the test:
$ ruby test/automated/handle_commands/build/initiated.rb [0/0]
ruby 3.2.1 (2023-02-08 revision 31819e82c8) [arm64-darwin21]
TEST_BENCH_DETAIL: nil
Handle Commands
Build
Initiated
/Users/ethangarofolo/source/such-software/utah-microservices-meetup/do-expensive-things-once/composite-component/gems/ruby/3.2.0/gems/evt-transform-2.0.0.0/lib/transform/transform.rb:12:in `transformer_reflection': CompositeComponent::Messages::Events::Initiated doesn't have a Transform namespace that implements the transformation protocol (Transform::Error)
CompositeComponent::Messages::Events::Initiated doesn't have a Transform namespace that implements the transformation protocol
. Lets you know that you haven’t implemented the transformation protocol. Okay, see you tomorrow!
As you get more familiar with the tools that Eventide offers, this error message will make deeper sense. It isn’t quite at “monads are monoids in the category of endofunctors” level, but we don’t need to dive into the transformation protocol right now. It is leveraged to serialize and deserialize messages to/from Message DB.
If we revisit our message class, we’ll see that it’s just a barebones Ruby class. We’re missing a critical line. Let’s add it in lib/composite_component/messages/events/initiated.rb
:
module CompositeComponent
module Messages
module Events
class Initiated
include Messaging::Message # <- I'm a real message!
end
end
end
end
Now our class is a message class. Consequently, if we run the test:
$ ruby test/automated/handle_commands/build/initiated.rb
ruby 3.2.1 (2023-02-08 revision 31819e82c8) [arm64-darwin21]
TEST_BENCH_DETAIL: nil
Handle Commands
Build
Initiated
Wrote an Initiated event
It comes up all green. Or at least it did in my console. That’ll probably get lost over email. Congrats, you have a handler that writes a message. To some nonsensical stream, to be sure, but it writes one nonetheless.
Getting it to go to the right place is our next step.