TDD in Eventide: What makes handlers Handlers and messages Messages?

Yesterday we started into our handler test for the Initiated event, and I gushed a little too much about how in Eventide you can just instantiate things and call methods on them.

And call methods we will today! Remember you can find all the final code in this repo.

We left the test off in this state:

context "Initiated" do
  handler = Handlers::Commands.new
end

We’re going to end up actuating our handler with a Build event, so we could go ahead and make one here:

context "Initiated" do
  handler = Handlers::Commands.new
  
  build = Messages::Commands::Build.new
end

Worth calling out, the automated_init.rb file we require at the top of our test file require_relatives a test_init.rb. This file has a line include CompositeComponent which includes our component’s top-level namespace. That’s how we’re able to use modules under CompositeComponent like Handlers and Messages without listing the full namespace like CompositeComponent::Handlers::Commands. Not needed, just convention.

Anyway, we want to keep our code organized, and we’re going to have messages. Some of them will be commands, hence the Messages::Commands namespacing. In this test we’re handling the Build event. If we ran the test now, we’ll get an error because none among Messages, Commands, or Build exists:

$ 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
      test/automated/handle_commands/build/initiated.rb:8:in `block (3 levels) in <main>': uninitialized constant Messages (NameError)

So we need to define our message class in lib/composite_component/messages/commands/build.rb, with just enough code to get the test to pass:

module CompositeComponent
  module Messages
    module Commands
      class Build
      end
    end
  end
end

If we run the test here, it passes again:

$ 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

You might be thinking that the command looks pretty empty right now. What data does it hold? Doesn’t matter for where we are in the test right now. There is nothing in the test that requires the message to have any attributes, so we’re not going to add them yet.

We are, however, going to actuate the handler now. Remember me gushing about instantiating things and calling methods on them? Here’s where we do that:

context "Initiated" do
  handler = Handlers::Commands.new
  
  build = Messages::Commands::Build.new

  handler.(build)
end

Mmmm. So simple. No hyper-specialized class that our test has to inherit from. Instantiate and call. Incidentally, if you’re not familiar with Ruby, handler.() is equivalent to calling a call method on handler or handler.call(). I did not have fun googling that when I first learned it.

Running the test, we’ll get a NoMethodError because handler has no method call:

$ 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
      test/automated/handle_commands/build/initiated.rb:17:in `block (3 levels) in <main>': undefined method `call' for #<CompositeComponent::Handlers::Commands:0x00000001062e97b0> (NoMethodError)

Thus far, it’s just a PORO, or Plain Old Ruby Object. Let’s make it a Handler in lib/composite_component/handlers/commands.rb:

module CompositeComponent
  module Handlers
    class Commands
      include Messaging::Handle
    end
  end
end

Messaging::Handle comes from Eventide, and includeing it turns our class into a Handler. We didn’t need it to be a Handler until this point in the test because this is the first time we’ve called it.

However, if we run the test again, we’ll get a NoMethodError because our Build instance has no message_name method on it:

$ 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
      /path/to/component/composite-component/gems/ruby/3.2.0/gems/evt-messaging-2.7.0.1/lib/messaging/handle.rb:110:in `handler_method_name': undefined method `message_name' for #<CompositeComponent::Messages::Commands::Build:0x00000001043956c0> (NoMethodError)

It’s also currently just a PORO and not yet a Message. Let’s turn it into one in lib/composite_component/messages/commands/build.rb (some lines omitted for space):

module Commands
  class Build
    include Messaging::Message
  end
end

Messaging::Message is also from Eventide, and it includes the methods that will let the rest of Eventide recognize a class as a Message. Now if we run the test, we’re back to passing:

$ 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

We need to wrap it up here today. We didn’t get as far as I thought we would. So be it.

You may wonder why I take the space to show all the test runs. The goal with this series is to get familiar with the TDD flow when working with Eventide and what the incremental steps are. Also, when you’re new to a framework, one of the first learning curves is making sense of error messages. With this methodical approach, you see what the error messages you’re likely to encounter are and how to fix them. As a side benefit, we’re also learning what the framework code is and why it’s important, so as you look at examples of Eventide components, you know what the pieces are.

My predictions aren’t great, so it may be tomorrow that we get to test controls, or it may be the day after. Whichever day it is, I hope the rest of whatever day you’re reading this on goes swimmingly.


Like this message? I send out a short email each day to help software development leaders build organizations the deliver value. Join us!


Get the book!

Ready to learn how to build an autonomous, event-sourced microservices-based system? Practical Microservices is the hands-on guidance you've been looking for.

Roll up your sleeves and get ready to build Video Tutorials, the next-gen web-based learning platform. You'll build it as a collection of loosely-coupled autonomous services, developing a message store interface along the way.

When you're done, you'll be ready to contribute to microservices-based projects.

In ebook or in print.