Handling another component's events

We’ve been looking at how funds-transfer component leverages account-component to make transfers. As part of that flow, funds-transfer-component handles Withdrawn and Deposited events from account-component.

The basic flow of handlers

You can break a handler into four steps:

  1. Figure out which entity you’re working on
  2. Project the current state of that entity
  3. Check to see if you’ve already processed the current message
  4. If you haven’t, process the message and record that you’ve done so by writing an event

Figuring out the entity when it isn’t your event

funds-transfer-component has a challenge. When you’re handling an event from another component that isn’t aware of your workflow, how do you know which entity you’re working on? When funds-transfer-component handles its own Initiated event, you have something like:

handle Initiated do |initiated|
  funds_transfer_id = initiated.funds_transfer_id

  funds_transfer, version = store.fetch(
    funds_transfer_id,
    include: :version
  )
  # ...
end

The problem? account-component’s events don’t contain a funds_transfer_id. If they did, you’d have to go add new data properties every time you wanted to do something new with account-component. Which means you’d have to wait for account-component’s team to be available to make those changes. Goodbye, Flow.

Are we just out of luck? Yes, time to go play Minecraft.

Stream names and correlation_stream_name

j/k. No, do you remember the correlation_stream_name property? What did funds-transfer-componentuse as the value there? Something of the form fundsTransfer-123. We chose that on purpose.

Do you remember the pieces of a stream name? Everything to the left of the first dash is the category and everything to the right of the first dash is the… 🥁… identifier The identifier of what? The identifier of the entity that you’re working on.

Which means that your correlation_stream_name value is of the form fundsTransfer-<funds_transfer_id>.

So, splitting that stream name at the first dash and taking the second part gives you your fundsTransferId:

handle ::Account::Client::Messages::Events::Withdrawn do |account_withdrawn|
  correlation_stream_name =
    account_withdrawn.metadata.correlation_stream_name

  funds_transfer_id = Messaging::StreamName.get_id(
    correlation_stream_name
  )

  funds_transfer, version = store.fetch(
    funds_transfer_id,
    include: :version
  )
  # ...
end

Consuming only the events you care about

The last step is to make sure that funds-transfer-component only handles account-component messages it cares about. The other day we mentioned the [get_category_messages function in Message DB](https://github.com/message-db/message-db/blob/master/database/functions/get-category-messages.sql).

How to leverage that in your system will depend on the technology you use, but in Eventide you leverage it when you start your consumber:

Consumers::Account::Events.start(
  'account',
  correlation: 'fundsTransfer'
)

Notice the correlation option we pass. The consumer will only retrieve messages whose metadata.correlation_stream_name is in the fundsTransfer category.


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.