So, you’re building a system around business processes and workflows. Great! But where does the code go that has to orchestrate all this?

This is a common question I’ve received, especially from members of my channel on our private Discord. Let’s dive into the guidelines on process managers, bounded contexts, and clever ways to collocate workflow steps.
YouTube
Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.
Understanding Workflows
Often, smaller workflows chain together to create larger workflows. Picture this: we have three different boundaries—sales, shipping, and billing. When an order is placed, sales publishes an “order placed” event.

Once the customer is charged, billing publishes an “order billed” event.

However, shipping can’t proceed until it knows that both events have occurred. Why? Because in asynchronous messaging, events may come in out of order!

We need to make sure both Order Placed and Order Billed events happened before shipping the order.
Code Example: Workflow in Action
Now, let’s move to our code example using C# with NServiceBus. Don’t worry if you aren’t in the .NET space; the principles will be clear regardless of the code’s appearance. In the sales boundary, we have a place order handler that publishes the “order placed” event.
On the billing side, we have a similar setup that publishes the “order billed” event.
Here’s how it all connects: in the shipping boundary, we want to know that both events have occurred to ship the order. This is where our shipping policy comes into play. It states that if an order is placed and billed, we can ship the order. The order can come in any sequence; we’re just handling both events and capturing their state.
Guidelines for Commands and Events
So, where does the code live that deals with this workflow? It exists within the boundary that needs to take action. When communicating between boundaries, use events. For example, when the order is placed, that event is consumed by billing and shipping. When the order is billed, shipping consumes that event as well. We’re not telling shipping to do something; we’re just publishing events and letting the workflow be there.
Inside a boundary, you can use either commands or events. However, here’s a guideline: Generally, avoid crossing boundaries with commands.

If billing sends a command to shipping to ship the order, it creates a tighter coupling. With events, as the publisher, you have no idea who the consumers are. You’re decoupling the workflow to a degree, but you’re still tied to the event schema and the location where the event is published.
Decoupling Through Commands and Events
It’s about understanding the implications of using commands versus events. Commands invoke behavior, and the consumer must respond. Events simply define that something happened, allowing flexibility in how different boundaries react to it.

Complexity vs. Simplicity
You might think this adds unnecessary complexity, and perhaps in your context, a simple RPC call or function call in a monolith could suffice. However, in larger systems, managing coupling becomes natural because that’s how the business works and organizes itself. The examples are simplified to illustrate the flow without diving into complex domain intricacies.
Join CodeOpinon!
Developer-level members of my Patreon or YouTube channel get access to a private Discord server to chat with other developers about Software Architecture and Design and access to source code for any working demo application I post on my blog or YouTube. Check out my Patreon or YouTube Membership for more info.