Skip to content

Commit b08f525

Browse files
authored
Merge pull request #20 from mfateev/bookingsaga
Added bookingsaga sample
2 parents 102b4c3 + 8a6b6df commit b08f525

File tree

8 files changed

+377
-0
lines changed

8 files changed

+377
-0
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ of details about the execution history.
8080
Each sample has specific requirements for running it. The following sections contain information about
8181
how to run each of the samples after you've built them using the preceding instructions.
8282

83+
Don't forget to check unit tests found under src/test/java!
84+
8385
### Hello World
8486

8587
To run the hello world samples:
@@ -106,4 +108,15 @@ execute together, we recommend that you run more than one instance of this worke
106108
The second command starts workflows. Each invocation starts a new workflow execution.
107109

108110
./gradlew -q execute -PmainClass=com.uber.cadence.samples.fileprocessing.FileProcessingStarter
111+
112+
### Trip Booking
113+
114+
Cadence implementation of the [Camunda BPMN trip booking example](https://github.com/berndruecker/trip-booking-saga-java)
115+
116+
Demonstrates Cadence approach to SAGA.
117+
118+
To run:
119+
120+
./gradlew -q execute -PmainClass=com.uber.cadence.samples.bookingsaga.TripBookingSaga
121+
109122

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
## Saga example: trip booking
2+
3+
Cadence implementation of the [Camunda BPMN trip booking example](https://github.com/berndruecker/trip-booking-saga-java)
4+
5+
Demonstrates Cadence approach to SAGA.
6+
7+
Don't forget to check TripBookingWorkflowTest in src/test/java/com/uber/cadence/samples/bookingsaga.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Modifications copyright (C) 2017 Uber Technologies, Inc.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
7+
* use this file except in compliance with the License. A copy of the License is
8+
* located at
9+
*
10+
* http://aws.amazon.com/apache2.0
11+
*
12+
* or in the "license" file accompanying this file. This file is distributed on
13+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
14+
* express or implied. See the License for the specific language governing
15+
* permissions and limitations under the License.
16+
*/
17+
18+
package com.uber.cadence.samples.bookingsaga;
19+
20+
public interface TripBookingActivities {
21+
22+
/**
23+
* @param name customer name
24+
* @return reservationID
25+
*/
26+
String reserveCar(String name);
27+
28+
/**
29+
* @param name customer name
30+
* @return reservationID
31+
*/
32+
String bookFlight(String name);
33+
34+
/**
35+
* @param name customer name
36+
* @return reservationID
37+
*/
38+
String bookHotel(String name);
39+
40+
/**
41+
* @param name customer name
42+
* @param reservationID id returned by bookFlight
43+
* @return cancellationConfirmationID
44+
*/
45+
String cancelFlight(String reservationID, String name);
46+
47+
/**
48+
* @param name customer name
49+
* @param reservationID id returned by bookHotel
50+
* @return cancellationConfirmationID
51+
*/
52+
String cancelHotel(String reservationID, String name);
53+
54+
/**
55+
* @param name customer name
56+
* @param reservationID id returned by reserveCar
57+
* @return cancellationConfirmationID
58+
*/
59+
String cancelCar(String reservationID, String name);
60+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Modifications copyright (C) 2017 Uber Technologies, Inc.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
7+
* use this file except in compliance with the License. A copy of the License is
8+
* located at
9+
*
10+
* http://aws.amazon.com/apache2.0
11+
*
12+
* or in the "license" file accompanying this file. This file is distributed on
13+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
14+
* express or implied. See the License for the specific language governing
15+
* permissions and limitations under the License.
16+
*/
17+
18+
package com.uber.cadence.samples.bookingsaga;
19+
20+
import java.util.UUID;
21+
22+
public class TripBookingActivitiesImpl implements TripBookingActivities {
23+
@Override
24+
public String reserveCar(String name) {
25+
System.out.println("reserve car for '" + name + "'");
26+
return UUID.randomUUID().toString();
27+
}
28+
29+
@Override
30+
public String bookFlight(String name) {
31+
System.out.println("failing to book flight for '" + name + "'");
32+
throw new RuntimeException("Flight booking did not work");
33+
}
34+
35+
@Override
36+
public String bookHotel(String name) {
37+
System.out.println("booking hotel for '" + name + "'");
38+
return UUID.randomUUID().toString();
39+
}
40+
41+
@Override
42+
public String cancelFlight(String reservationID, String name) {
43+
System.out.println("cancelling flight reservation '" + reservationID + "' for '" + name + "'");
44+
return UUID.randomUUID().toString();
45+
}
46+
47+
@Override
48+
public String cancelHotel(String reservationID, String name) {
49+
System.out.println("cancelling hotel reservation '" + reservationID + "' for '" + name + "'");
50+
return UUID.randomUUID().toString();
51+
}
52+
53+
@Override
54+
public String cancelCar(String reservationID, String name) {
55+
System.out.println("cancelling car reservation '" + reservationID + "' for '" + name + "'");
56+
return UUID.randomUUID().toString();
57+
}
58+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Modifications copyright (C) 2017 Uber Technologies, Inc.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
7+
* use this file except in compliance with the License. A copy of the License is
8+
* located at
9+
*
10+
* http://aws.amazon.com/apache2.0
11+
*
12+
* or in the "license" file accompanying this file. This file is distributed on
13+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
14+
* express or implied. See the License for the specific language governing
15+
* permissions and limitations under the License.
16+
*/
17+
18+
package com.uber.cadence.samples.bookingsaga;
19+
20+
import static com.uber.cadence.samples.common.SampleConstants.DOMAIN;
21+
22+
import com.uber.cadence.client.WorkflowClient;
23+
import com.uber.cadence.client.WorkflowException;
24+
import com.uber.cadence.worker.Worker;
25+
26+
public class TripBookingSaga {
27+
28+
static final String TASK_LIST = "TripBooking";
29+
30+
public static void main(String[] args) {
31+
32+
// Get worker to poll the common task list.
33+
Worker.Factory factory = new Worker.Factory(DOMAIN);
34+
final Worker workerForCommonTaskList = factory.newWorker(TASK_LIST);
35+
workerForCommonTaskList.registerWorkflowImplementationTypes(TripBookingWorkflowImpl.class);
36+
TripBookingActivities tripBookingActivities = new TripBookingActivitiesImpl();
37+
workerForCommonTaskList.registerActivitiesImplementations(tripBookingActivities);
38+
39+
// Start all workers created by this factory.
40+
factory.start();
41+
System.out.println("Worker started for task list: " + TASK_LIST);
42+
43+
WorkflowClient workflowClient = WorkflowClient.newInstance(DOMAIN);
44+
45+
// now we can start running instances of our saga - its state will be persisted
46+
TripBookingWorkflow trip1 = workflowClient.newWorkflowStub(TripBookingWorkflow.class);
47+
try {
48+
trip1.bookTrip("trip1");
49+
} catch (WorkflowException e) {
50+
// Expected
51+
e.printStackTrace();
52+
}
53+
54+
try {
55+
TripBookingWorkflow trip2 = workflowClient.newWorkflowStub(TripBookingWorkflow.class);
56+
trip2.bookTrip("trip2");
57+
} catch (WorkflowException e) {
58+
e.printStackTrace();
59+
}
60+
61+
System.exit(0);
62+
}
63+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Modifications copyright (C) 2017 Uber Technologies, Inc.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
7+
* use this file except in compliance with the License. A copy of the License is
8+
* located at
9+
*
10+
* http://aws.amazon.com/apache2.0
11+
*
12+
* or in the "license" file accompanying this file. This file is distributed on
13+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
14+
* express or implied. See the License for the specific language governing
15+
* permissions and limitations under the License.
16+
*/
17+
18+
package com.uber.cadence.samples.bookingsaga;
19+
20+
import static com.uber.cadence.samples.bookingsaga.TripBookingSaga.TASK_LIST;
21+
22+
import com.uber.cadence.workflow.WorkflowMethod;
23+
24+
public interface TripBookingWorkflow {
25+
26+
@WorkflowMethod(executionStartToCloseTimeoutSeconds = 3600, taskList = TASK_LIST)
27+
void bookTrip(String name);
28+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Modifications copyright (C) 2017 Uber Technologies, Inc.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
7+
* use this file except in compliance with the License. A copy of the License is
8+
* located at
9+
*
10+
* http://aws.amazon.com/apache2.0
11+
*
12+
* or in the "license" file accompanying this file. This file is distributed on
13+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
14+
* express or implied. See the License for the specific language governing
15+
* permissions and limitations under the License.
16+
*/
17+
18+
package com.uber.cadence.samples.bookingsaga;
19+
20+
import com.uber.cadence.activity.ActivityOptions;
21+
import com.uber.cadence.workflow.ActivityException;
22+
import com.uber.cadence.workflow.Saga;
23+
import com.uber.cadence.workflow.Workflow;
24+
import java.time.Duration;
25+
26+
public class TripBookingWorkflowImpl implements TripBookingWorkflow {
27+
28+
private final ActivityOptions options =
29+
new ActivityOptions.Builder().setScheduleToCloseTimeout(Duration.ofHours(1)).build();
30+
private final TripBookingActivities activities =
31+
Workflow.newActivityStub(TripBookingActivities.class, options);
32+
33+
@Override
34+
public void bookTrip(String name) {
35+
// Configure SAGA to run compensation activities in parallel
36+
Saga.Options sagaOptions = new Saga.Options.Builder().setParallelCompensation(true).build();
37+
Saga saga = new Saga(sagaOptions);
38+
try {
39+
String carReservationID = activities.reserveCar(name);
40+
saga.addCompensation(activities::cancelCar, carReservationID, name);
41+
42+
String hotelReservationID = activities.bookHotel(name);
43+
saga.addCompensation(activities::cancelHotel, hotelReservationID, name);
44+
45+
String flightReservationID = activities.bookFlight(name);
46+
saga.addCompensation(activities::cancelFlight, flightReservationID, name);
47+
} catch (ActivityException e) {
48+
saga.compensate();
49+
throw e;
50+
}
51+
}
52+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Modifications copyright (C) 2017 Uber Technologies, Inc.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
7+
* use this file except in compliance with the License. A copy of the License is
8+
* located at
9+
*
10+
* http://aws.amazon.com/apache2.0
11+
*
12+
* or in the "license" file accompanying this file. This file is distributed on
13+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
14+
* express or implied. See the License for the specific language governing
15+
* permissions and limitations under the License.
16+
*/
17+
18+
package com.uber.cadence.samples.bookingsaga;
19+
20+
import static org.junit.Assert.assertEquals;
21+
import static org.junit.Assert.fail;
22+
import static org.mockito.Matchers.eq;
23+
import static org.mockito.Mockito.mock;
24+
import static org.mockito.Mockito.verify;
25+
import static org.mockito.Mockito.when;
26+
27+
import com.uber.cadence.client.WorkflowClient;
28+
import com.uber.cadence.client.WorkflowException;
29+
import com.uber.cadence.testing.TestWorkflowEnvironment;
30+
import com.uber.cadence.worker.Worker;
31+
import org.junit.After;
32+
import org.junit.Before;
33+
import org.junit.Test;
34+
35+
public class TripBookingWorkflowTest {
36+
37+
private TestWorkflowEnvironment testEnv;
38+
private Worker worker;
39+
private WorkflowClient workflowClient;
40+
41+
@Before
42+
public void setUp() {
43+
testEnv = TestWorkflowEnvironment.newInstance();
44+
worker = testEnv.newWorker(TripBookingSaga.TASK_LIST);
45+
worker.registerWorkflowImplementationTypes(TripBookingWorkflowImpl.class);
46+
47+
workflowClient = testEnv.newWorkflowClient();
48+
}
49+
50+
@After
51+
public void tearDown() {
52+
testEnv.close();
53+
}
54+
55+
/**
56+
* Not very useful test that validates that the default activities cause workflow to fail. See
57+
* other tests on using mocked activities to test SAGA logic.
58+
*/
59+
@Test
60+
public void testTripBookingFails() {
61+
worker.registerActivitiesImplementations(new TripBookingActivitiesImpl());
62+
testEnv.start();
63+
64+
TripBookingWorkflow workflow = workflowClient.newWorkflowStub(TripBookingWorkflow.class);
65+
try {
66+
workflow.bookTrip("trip1");
67+
fail("unreachable");
68+
} catch (WorkflowException e) {
69+
assertEquals("Flight booking did not work", e.getCause().getCause().getMessage());
70+
}
71+
}
72+
73+
/** Unit test workflow logic using mocked activities. */
74+
@Test
75+
public void testSAGA() {
76+
TripBookingActivities activities = mock(TripBookingActivities.class);
77+
when(activities.bookHotel("trip1")).thenReturn("HotelBookingID1");
78+
when(activities.reserveCar("trip1")).thenReturn("CarBookingID1");
79+
when(activities.bookFlight("trip1"))
80+
.thenThrow(new RuntimeException("Flight booking did not work"));
81+
worker.registerActivitiesImplementations(activities);
82+
83+
testEnv.start();
84+
85+
TripBookingWorkflow workflow = workflowClient.newWorkflowStub(TripBookingWorkflow.class);
86+
try {
87+
workflow.bookTrip("trip1");
88+
fail("unreachable");
89+
} catch (WorkflowException e) {
90+
assertEquals("Flight booking did not work", e.getCause().getCause().getMessage());
91+
}
92+
93+
verify(activities).cancelHotel(eq("HotelBookingID1"), eq("trip1"));
94+
verify(activities).cancelCar(eq("CarBookingID1"), eq("trip1"));
95+
}
96+
}

0 commit comments

Comments
 (0)