Architecture, NServiceBus

Modelling Long Running Processes: Choreography versus Orchestration

In the previous article we’ve seen some examples of long running processes. The purpose of this blog post is to show how to model long running processes by using choreography or orchestration.

Requirement

To better understand the differences between these two approaches, let’s take a long running process and implement it with both. Since we already talked about the Order Fulfillment enterprise process in the last post, let’s use that.

Order Fulfillment

When a customer places an order, we need to approve it, charge the customer’s credit card, pack the order and ship it.

Choreography

Let’s first implement this requirement with choreography. Choreography is all about distributed decision making. When something important happens in a service (or bounded context), the service will publish an event. Other services can subscribe to that event and make decisions based on it.

Choreography

In our example, Sales would handle the PlaceOrder command. After approving the order, it will publish the OrderPlaced event. Finance and Inventory both subscribe to this event. When Finance receives it, it will charge the customer and then publish an OrderCharged event. Inventory will pack the order and publish an OrderPacked event. When Shipping receives both OrderCharged and OrderPacked, it will ship the order and publish the OrderShipped event.

Choreography - Order Fulfillment

Benefits

Easy to Extend

Choreography - easy to extend

Since there is no central component making all the decisions, it’s easy to extend the flow. A new requirement – activate promotions for orders bigger than 100$ – would be easy to implement. The Promotions service needs to subscribe to the OrderPlaced event and that’s it. We don’ need to change any of the existing code. So, in this case, the architecture complies with the Open/Closed Principle.

Low Coupling

The components in this architecture are loosely coupled. The services only couple to the event’s definition, which should be pretty slim.

Temporally Decoupled

Events also bring another benefit: services are temporally decoupled. If the Promotions service is down when Sales publishes the OrderPlaced event, no problem. Events will accumulate in a queue. When Promotions comes back up, it will start processing the backlog of events. This makes the system more resilient, making sure that you can still process requests and even fulfill orders, even if a service is down. Events help you work around some of the fallacies of distributed systems.

Drawbacks

Harder to monitor

Choreography - monitoring

It’s harder to monitor choreography based solutions, since there isn’t a central processing unit that controls the process. If you want to find out the status for order 123, you need to look in all four services to see if the order is approved, charged, packed or shipped. Of course, there are ways around this. You can implement a long running process that monitors your long running process. This monitoring process can subscribe to the OrderPlaced, OrderCharged, OrderPacked and OrderShipped events and store the state of the process. This way, you can look in a single place if you want to find out the status of an order. The Monitoring long running process in the sample code does just that. This is also a good way to test in production. You can keep track of the state of a long running process and raise alerts when a process does not complete within a reasonable time frame. This can be a powerful metric. If after a new deploy you notice that many order are not shipped, you might have introduced a bug.

Allows a single process

Choreography - allows a single process

But, the main disadvantage of choreography is that it allows a single process. What if, for enterprise customers, you would like to charge the order only after it is shipped? And other customer types require the order to be charged only after it is packed? This means that Shipping will need to subscribe to the OrderPlaced event and Finance would subscribe to the OrderShipped and OrderPacked events. In order to implement these two rather simple requirements, you will need to change all four services so that they are aware of customer segments. This clearly doesn’t adhere to the Open/Closed Principle and introduces a lot of accidental complexity.

Can increase coupling

In order to implement the two requirements described above, you would also need to introduce more coupling between services. Basically, every service would need to know about every event in the system. So, in some cases, choreography can actually increase coupling.

Tips

With these trade-offs in mind, here are some tips on when to use choreography:

  • Use choreography when the process is stable and you don’t need to reorder the processing steps. On the flip side, don’t use choreography if it increases coupling.
  • Use choreography as the preferred integration style between services. The boundaries between services should be more stable. By using choreography, you are improving the autonomy of your services.
  • Use events to break an enterprise flow into subflows. In this example, we’ve used the OrderPlaced, OrderCharged, OrderPacked and OrderShipped event to break the Order Fulfillment process into subflows. Each service will know how to react to these events and what decisions to make. This way, we keep services autonomous and encapsulated. In the next blog post we’ll see some examples of how to implement these subflows.
  • Don’t use events when you really have request/response. If Sales expects to receive an OrderPlacedProcessedByFinance, then you have request/response, not events.

Now let’s have a look at the alternative.

Orchestration

Orchestration means that the decision making is centralized. We have a central component – the orchestrator – that tells the other services what to do.

Orchestration - Generic

In our example, we would need to introduce a new service – the Order service – that will act as the orchestrator. It will handle the PlaceOrder command and then interact with the other services. It would tell Sales to approve the order, Finance to charge the order, Inventory to pack the order and Shipping to ship it.

Orchestration - Order Fulfillment

Benefits

Easier to change

Orchestration is many times easier to understand and change. The order fulfillment process is in a single service so it’s easier to follow.

Easier to monitor

Orchestration - monitoring

Since we have a central component that controls the process, orchestration based solutions are easier to monitor. If you want to find out the status of order 123, you can look in the Order service.

Allows many flows

Orchestration - reorder steps

If we would need to reorder the processing steps based on the customer segment, then orchestration would be a good solution. We would only need to update the Order service to call the other services in a different order. Sales, Finance, Inventory and Shipping would not change at all. This way, we don’t introduce accidental complexity or coupling.

Drawbacks

The “God” Service

Although there is no coupling between Sales, Finance, Inventory and Shipping, the Order service is coupled to all of them. This isn’t an issue by itself. But you run the risk of the Order service becoming a “God” service. This means that it will contain all the business logic, while the other collaborators are just CRUD services.

Bottleneck

Any central processing unit runs the risk of becoming a performance bottleneck. Since all interactions go through the Order service, you must ensure that it can handle the load.

Tips

With these in mind, let’s see when should we use orchestration:

  • Use orchestration when the order of the processing steps changes often. This will be easier to do, since only the orchestrator will need to change.
  • Use orchestration inside service boundaries. Since coupling inside service boundaries is more acceptable than coupling between services, orchestration is a good option to use inside a service. For example, you can react to an external event (OrderPacked) and start an internal orchestration in Shipping to ship the order.
  • Use orchestration when integrating with 3rd party systems. This is a no-brainer, since that’s how you integrate with 3rd parties: you tell them what to do, then wait for a response. Many times it’s useful to put a gateway in front of a 3rd party system. The gateway will act as an orchestrator and coordinate the integration. For example, if you need to make three API calls to ship an order with a shipping service (e.g. FedEx), this logic should be in the gateway, not in the Shipping service.
  • Don’t centralize all orchestration inside a single service. Orchestration is not a service. Even if Order orchestrates the order fulfillment process, you should not put all business logic inside this service. The other services (Sales, Finance, Inventory, Shipping) should still maintain their autonomy and encapsulate their business logic. For example, the Order service shouldn’t know how the customer is charged, or care about what Payment Provider is used. That’s Finance’s job. The Order service shouldn’t care about the shipping policy. That Shipping’s job. So, if you use orchestration, ensure your orchestrator doesn’t become a “God” service.

Conclusion

Like every decision in software engineering, it’s all about trade-offs. Choreography and orchestration both have their strengths and weaknesses. This article describes some tips on when to choose one over the other. If you want to find out more about this subject, here are some useful resources:

In the next blog post in this series we’ll see where to store the state of a long running process.