Skip to content

Adding Choreography Pattern: #1328 #1907

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions choreography/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
---
layout: pattern
title: Choreography
folder: choreography
permalink: /patterns/choreography/
categories: Microservices
language: en
tags:
- Microservices
---

## Intent

The `choreography` pattern is a way to organize microservices which need to work together. It is usually contrasted with
the `orchestration` pattern. Using either of these patterns allows us to create `sagas` which can create more complex
experiences than individual microservices can themselves.

## Explanation

The `choreography` pattern organizes microservices by having them emit `events` to a central bus when they have
completed their work. That central bus will forward the events to other microservices, which will decide how best to
proceed based on the contents of the event.

This is in contrast with an `orchestrator`, which controls the entire flow. The orchestrator will call each microservice
in turn, and make sure that is waits for all the relevant information before it proceeds with downstream requests.

A `saga` is the term for a series of microservice transactions that take place in an organized way. They encapsulate a
number of steps in a larger operation, such as delivering packages from a warehouse to a consumer.

### Real world example

They are named for their real-world counterparts.

> Choreographers teach groups of people how to dance, but the choreographer does not need to be present for the dance
> to be performed. The dancers can react to events, such as the beats in a song, and still perform in unison.

> Orchestras are groups of musicians conducted by a conductor. The conductor needs to be present for the musicians to
> be able to perform in unison. He will not move forward to the next part of the song until all the musicians are ready.

In plain words

> The choreography pattern allows microservices to organize themselves, by providing a central messaging bus for them to communicate

> The orchestrator pattern has a service that manages other microservices, by calling on them when it needs them.

Wikipedia says

> [Service choreography](https://en.wikipedia.org/wiki/Service_choreography) in business computing is a form of service
> composition in which the interaction protocol between several partner services is defined from a global perspective.
> The idea underlying the notion of service choreography can be summarised as follows: "Dancers dance following a global
> scenario without a single point of control"

**Programmatic Example**

In this example, we are ordering a package from a packaging facility. After placing an order, there are 3 steps involved
in getting the package to your house:

- Preparing the package
- Provisioning a delivery drone
- Sending the package out for delivery

Each of these steps has been broken out into a "microservice" which has been approximated by an async call. Each
microservice has a bus that it is able to post to, to notify other microservices of the results of its latest operation.
Broadly, each microservice should have a `SuccessEvent` and a `FailureEvent`.

In the following example, the delivery `saga` can fail in 2 different ways:

- The packaging service can report that the item is not in stock
- The delivery service can report that the requested address is not found

When either of these happens, a failure event specific to that service is broadcasted, and each microservice is given
the opportunity to perform cleanup related to its own data. In the case of the "address not found" failure in the
following scenario, each of the services is given the chance to reverse its transaction when the saga moves into a
failure state.

Program output:

![alt text](./etc/output.png "Output")

## Class diagram

![](https://docs.microsoft.com/en-us/azure/architecture/patterns/_images/choreography-example.png)
Image from https://docs.microsoft.com/en-us/azure/architecture/patterns/choreography
## Applicability

Use the Choreography pattern when:

- You need to organize a set of microservices to perform an operation that has multiple steps
- You need to avoid having a single point of failure for your service
- You need to develop applications at scale

In contrast, you should use the orchestrator pattern when:

- You need to organize a set of microservices to perform an operation that has multiple steps
- Your use case is simple and not subject to change
- You have limited network bandwidth

## Known uses

* It is a supported microservice pattern on Azure

## Credits

* https://techrocking.com/microservices-choreography-event-pattern/
* https://docs.microsoft.com/en-us/azure/architecture/patterns/choreography
* https://bluesoft.com/orchestration-vs-choreography-different-patterns-of-getting-systems-to-work-together/
* https://medium.com/ingeniouslysimple/choreography-vs-orchestration-a6f21cfaccae
* https://dev.to/theagilemonkeys/saga-patterns-by-example-fod
* https://medium.com/ci-t/how-to-chain-azure-functions-c11da1048353
Binary file added choreography/etc/output.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
67 changes: 67 additions & 0 deletions choreography/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?xml version="1.0"?>
<!--

The MIT License
Copyright © 2014-2021 Ilkka Seppälä
Copyright © 2021 Patrick Beagan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

-->
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>choreography</artifactId>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<configuration>
<archive>
<manifest>
<mainClass>com.iluwatar.choreography.App</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
94 changes: 94 additions & 0 deletions choreography/src/main/java/com/iluwatar/choreography/App.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* The MIT License
* Copyright © 2014-2021 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package com.iluwatar.choreography;

import com.iluwatar.choreography.response.Response;
import com.iluwatar.choreography.servicedelivery.DeliveryService;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
* <p>
* The choreography pattern is a way of managing operations in a microservice architecture.
* </p>
* <p>
* When an operation requires multiple microservices to be completed, that is called a "Saga". The
* microservices need to have a coordinator which will keep track of the information returned from
* each service, and which services still owe it information.
* </p>
* There are 2 main ways of creating sagas<ol>
* <li>the orchestrator pattern</li>
* <li>the choreography pattern</li>
* </ol>
* <p>
* They differ in their approach. The orchestrator pattern dictates everything that should happen,
* procedurally. The analogy is that an orchestra only moves forwards when the conductor bids them
* to - they should not try to do anything unless the conductor asks for it. In contrast, the
* choreographer announces to a central queue that events have happened, and trusts that each
* microservice will be able to figure out what to do next based on that knowledge.
* </p>
*/
public class App {

static String INVALID_ADDRESS = "'Middle of Nowhere'";

/**
* Program entry point.
* <p>This starts the delivery process for 3 random addresses.</p>
*
* @param args command line args
*/
public static void main(String[] args) throws ExecutionException, InterruptedException {
MainService mainService = new MainService();
CompletableFuture.allOf(
CompletableFuture.runAsync(() -> {
Response post = mainService.requestDeliveryTo(getSampleAddress());
System.out.println(post.getMessage());
}),
CompletableFuture.runAsync(() -> {
Response post = mainService.requestDeliveryTo(getSampleAddress());
System.out.println(post.getMessage());
}),
CompletableFuture.runAsync(() -> {
Response post = mainService.requestDeliveryTo(getSampleAddress());
System.out.println(post.getMessage());
})
).get();
}

static String getSampleAddress() {
switch (Math.abs(new Random().nextInt()) % 3) {
case 0:
return DeliveryService.WALLABY_WAY;
case 1:
return DeliveryService.BUCKINGHAM;
case 2:
return INVALID_ADDRESS;
default:
return null;
}
}
}

117 changes: 117 additions & 0 deletions choreography/src/main/java/com/iluwatar/choreography/MainService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package com.iluwatar.choreography;

import com.iluwatar.choreography.events.DeliveryFailureEvent;
import com.iluwatar.choreography.events.DeliverySuccessEvent;
import com.iluwatar.choreography.events.DroneEvent;
import com.iluwatar.choreography.events.Event;
import com.iluwatar.choreography.events.PackageEvent;
import com.iluwatar.choreography.events.RequestScheduleDeliveryEvent;
import com.iluwatar.choreography.response.Failure;
import com.iluwatar.choreography.response.OK;
import com.iluwatar.choreography.response.Response;
import com.iluwatar.choreography.servicedelivery.DeliveryService;
import com.iluwatar.choreography.servicedrone.DroneService;
import com.iluwatar.choreography.servicepackage.PackageService;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;

public class MainService {

/*
* These are fake services which will let us see the pattern without too much overhead
* Normally these would be network requests to other live microservices.
*/

/**
* A service for getting a drone.
*/
final DroneService droneService = new DroneService(this);

/**
* A service for sending drones with packages out to delivery.
*/
final DeliveryService deliveryService = new DeliveryService(this);

/**
* A service for creating a package.
*/
final PackageService packageService = new PackageService(this);

/**
* Generates new sagaId values, through which we can track the flow of events through multiple
* microservices.
*/
private final AtomicInteger sagaCounter = new AtomicInteger();

/**
* The method call that kicks off the whole flow. It creates a sagaId, and submits the first
* event.
*
* @param address the address that we will be sending a package to
* @return the response that comes back after the saga completes - either OK or Failure
*/
public Response requestDeliveryTo(String address) {
return post(new RequestScheduleDeliveryEvent(sagaCounter.getAndIncrement(), address));
}

/**
* The message queue.
*
* <p>Events get fed into here, and other services are notified of the event The other services
* are able to submit events back to the queue as well
* </p>
*
* @param event an event that signifies that some piece of work is complete
* @return the result of all the work that the current microservice has completed
*/
public Response post(Event event) {
if (event == null) {
return new Failure("No events to process!");
} else {
CompletableFuture<Response> deferredResponse;
if (event instanceof RequestScheduleDeliveryEvent) {
deferredResponse = CompletableFuture.supplyAsync(
() -> packageService.getPackage((RequestScheduleDeliveryEvent) event));
} else if (event instanceof PackageEvent) {
deferredResponse = CompletableFuture.supplyAsync(
() -> droneService.getDrone((PackageEvent) event));
} else if (event instanceof DroneEvent) {
deferredResponse = CompletableFuture.supplyAsync(
() -> deliveryService.completeDelivery((DroneEvent) event));
} else if (event instanceof DeliverySuccessEvent) {
deferredResponse = CompletableFuture.supplyAsync(
() -> new OK((DeliverySuccessEvent) event));
} else if (event instanceof DeliveryFailureEvent) {
deferredResponse = CompletableFuture.supplyAsync(() -> {
DeliveryFailureEvent failureEvent = (DeliveryFailureEvent) event;
List.of(deliveryService,
droneService,
packageService).forEach(it -> it.onSagaFailure(failureEvent));
return new Failure(failureEvent);
});
} else {
deferredResponse = CompletableFuture.supplyAsync(
() -> new Failure(event.getPrettySagaId() + "Could not handle that type of event!"));
}
return getResponse(deferredResponse);
}
}

/**
* Helper method to await for the future to complete.
*
* @param deferredResponse the response that we are waiting for
* @return the response that was promised
*/
private Response getResponse(CompletableFuture<Response> deferredResponse) {
Response response = null;
try {
response = deferredResponse.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return response;
}
}
Loading