Skip to content

Commit 4e00cac

Browse files
authored
Adding SideEffect example (temporalio#95)
* Adding SideEffect example Signed-off-by: Tihomir Surdilovic <[email protected]> * fix test - setting same workflowfactoryoptions as in example Signed-off-by: Tihomir Surdilovic <[email protected]> * updates per review Signed-off-by: Tihomir Surdilovic <[email protected]> * updated per review Signed-off-by: Tihomir Surdilovic <[email protected]> * typo Signed-off-by: Tihomir Surdilovic <[email protected]> * updates per comments Signed-off-by: Tihomir Surdilovic <[email protected]> * formatting Signed-off-by: Tihomir Surdilovic <[email protected]>
1 parent 6a03256 commit 4e00cac

3 files changed

Lines changed: 344 additions & 14 deletions

File tree

README.md

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -81,22 +81,24 @@ Don't forget to check unit tests found under [src/test/java](https://github.com/
8181
Each Hello World sample demonstrates one feature of the SDK in a single file. Note that single file format is
8282
used for sample brevity and is not something we recommend for real applications.
8383

84-
* **[HelloActivity](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloActivity.java)**: a single activity workflow
85-
* **[HelloActivityRetry](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloActivityRetry.java)**: how to retry an activity
86-
* **[HelloActivityExclusiveChoice](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloActivityExclusiveChoice.java)**: how to execute activities based on dynamic input
87-
* **[HelloAsync](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloAsync.java)**: how to call activities asynchronously and wait for them using Promises
88-
* **[HelloAsyncActivityCompletion](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloAsyncActivityCompletion.java)**: an asynchronous activity implementation
89-
* **[HelloAsyncLambda](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloAsyncLambda.java)**: how to run part of a workflow asynchronously in a separate task (thread)
90-
* **[HelloCancellationScope](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloCancellationScope.java)**: how to explicitly cancel parts of a workflow
91-
* **[HelloChild](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloChild.java)**: a child workflow
92-
* **[HelloCron](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloCron.java)**: a workflow that is executed according to a cron schedule
93-
* **[HelloPeriodic](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloPeriodic.java)**: a workflow that executes some logic periodically
94-
* **[HelloException](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloException.java)**: exception propagation and wrapping
95-
* **[HelloPolymorphicActivity](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloPolymorphicActivity.java)**: activities that extend a common interface
96-
* **[HelloQuery](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloQuery.java)**: demonstrates how to query a state of a single workflow
97-
* **[HelloSignal](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloSignal.java)**: sending and handling a signal
84+
* **[HelloActivity](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloActivity.java)**: Single activity workflow
85+
* **[HelloActivityRetry](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloActivityRetry.java)**: How to retry an activity
86+
* **[HelloActivityExclusiveChoice](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloActivityExclusiveChoice.java)**: How to execute activities based on dynamic input
87+
* **[HelloAsync](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloAsync.java)**: How to call activities asynchronously and wait for them using Promises
88+
* **[HelloAsyncActivityCompletion](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloAsyncActivityCompletion.java)**: Asynchronous activity implementation
89+
* **[HelloAsyncLambda](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloAsyncLambda.java)**: How to run part of a workflow asynchronously in a separate task (thread)
90+
* **[HelloCancellationScope](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloCancellationScope.java)**: How to explicitly cancel parts of a workflow
91+
* **[HelloChild](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloChild.java)**: Child workflow
92+
* **[HelloCron](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloCron.java)**: Workflow that is executed according to a cron schedule
93+
* **[HelloPeriodic](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloPeriodic.java)**: Workflow that executes some logic periodically
94+
* **[HelloException](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloException.java)**: Exception propagation and wrapping
95+
* **[HelloPolymorphicActivity](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloPolymorphicActivity.java)**: Activities that extend a common interface
96+
* **[HelloQuery](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloQuery.java)**: Demonstrates how to query a state of a single workflow
97+
* **[HelloSignal](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloSignal.java)**: Sending and handling a signal
9898
* **[HelloSaga](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloSaga.java)**: SAGA pattern support
9999
* **[HelloSearchAttributes](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloSearchAttributes.java)**: Custom search attributes that can be used to find workflows using predicates
100+
* **[HelloSideEffect](https://github.com/temporalio/samples-java/blob/master/src/main/java/io/temporal/samples/hello/HelloSideEffect.java)**: Demonstrates the use of workflow SideEffect
101+
100102

101103
To run the hello world samples:
102104

@@ -116,6 +118,7 @@ used for sample brevity and is not something we recommend for real applications.
116118
./gradlew -q execute -PmainClass=io.temporal.samples.hello.HelloSaga
117119
./gradlew -q execute -PmainClass=io.temporal.samples.hello.HelloSignal
118120
./gradlew -q execute -PmainClass=io.temporal.samples.hello.HelloSearchAttributes
121+
./gradlew -q execute -PmainClass=io.temporal.samples.hello.HelloSideEffect
119122

120123
### File Processing
121124
[FileProcessing](https://github.com/temporalio/samples-java/tree/master/src/main/java/io/temporal/samples/fileprocessing)
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
/*
2+
* Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved
3+
*
4+
* Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5+
*
6+
* Modifications copyright (C) 2017 Uber Technologies, Inc.
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
9+
* use this file except in compliance with the License. A copy of the License is
10+
* located at
11+
*
12+
* http://aws.amazon.com/apache2.0
13+
*
14+
* or in the "license" file accompanying this file. This file is distributed on
15+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
16+
* express or implied. See the License for the specific language governing
17+
* permissions and limitations under the License.
18+
*/
19+
20+
package io.temporal.samples.hello;
21+
22+
import io.temporal.activity.ActivityInterface;
23+
import io.temporal.activity.ActivityOptions;
24+
import io.temporal.client.WorkflowClient;
25+
import io.temporal.client.WorkflowOptions;
26+
import io.temporal.serviceclient.WorkflowServiceStubs;
27+
import io.temporal.worker.Worker;
28+
import io.temporal.worker.WorkerFactory;
29+
import io.temporal.workflow.QueryMethod;
30+
import io.temporal.workflow.Workflow;
31+
import io.temporal.workflow.WorkflowInterface;
32+
import io.temporal.workflow.WorkflowMethod;
33+
import java.security.SecureRandom;
34+
import java.time.Duration;
35+
import java.util.Random;
36+
import java.util.UUID;
37+
38+
/**
39+
* Sample Temporal workflow that shows use of workflow SideEffect.
40+
*
41+
* <p>Workflow methods must be deterministic. In order to execute non-deterministic code, such as
42+
* random number generation as shown in this example, you should use Workflow.SideEffect.
43+
* Workflow.SideEffect is typically used for very quick-running operations, where as Workflow
44+
* Activities or Local Activities, which can also execute non-deterministic code, are meant for more
45+
* expensive operations.
46+
*
47+
* <p>Note: you should not use SideEffect function to modify the workflow state. For that you should
48+
* only use the SideEffect's return value!
49+
*
50+
* <p>To execute this example a locally running Temporal service instance is required. You can
51+
* follow instructions on how to set up your Temporal service here:
52+
* https://github.com/temporalio/temporal/blob/master/README.md#download-and-start-temporal-server-locally
53+
*/
54+
public class HelloSideEffect {
55+
56+
// Define the task queue name
57+
static final String TASK_QUEUE = "HelloSideEffectTaskQueue";
58+
59+
// Define our workflow unique id
60+
static final String WORKFLOW_ID = "HelloSideEffectTaskWorkflow";
61+
62+
/**
63+
* Define the Workflow Interface. It must contain one method annotated with @WorkflowMethod.
64+
*
65+
* <p>Workflow code includes core processing logic. It shouldn't contain any heavyweight
66+
* computations, non-deterministic code, network calls, database operations, etc. All those things
67+
* should be handled by Activities.
68+
*
69+
* @see io.temporal.workflow.WorkflowInterface
70+
* @see io.temporal.workflow.WorkflowMethod
71+
*/
72+
@WorkflowInterface
73+
public interface SideEffectWorkflow {
74+
75+
/**
76+
* This method is executed when the workflow is started. The workflow completes when the
77+
* workflow method finishes execution.
78+
*/
79+
@WorkflowMethod
80+
String execute();
81+
82+
@QueryMethod
83+
String getResult();
84+
}
85+
86+
/**
87+
* Define the Activity Interface. Activities are building blocks of any temporal workflow and
88+
* contain any business logic that could perform long running computation, network calls, etc.
89+
*
90+
* <p>Annotating activity methods with @ActivityMethod is optional
91+
*
92+
* @see io.temporal.activity.ActivityInterface
93+
* @see io.temporal.activity.ActivityMethod
94+
*/
95+
@ActivityInterface
96+
public interface SideEffectActivities {
97+
98+
// Define your activity methods which can be called during workflow execution
99+
String sayHello(String greeting);
100+
101+
String sayGoodBye(String greeting);
102+
}
103+
104+
// Define the workflow implementation which implements our execute workflow method.
105+
public static class SideEffectWorkflowImpl implements SideEffectWorkflow {
106+
107+
/**
108+
* Define the SideEffectActivities stub. Activity stubs are proxies for activity invocations
109+
* that are executed outside of the workflow thread on the activity worker, that can be on a
110+
* different host. Temporal is going to dispatch the activity results back to the workflow and
111+
* unblock the stub as soon as activity is completed on the activity worker.
112+
*
113+
* <p>Let's take a look at each {@link ActivityOptions} defined: The "setStartToCloseTimeout"
114+
* option sets maximum time of a single Activity execution attempt. For this example it is set
115+
* to 2 seconds.
116+
*/
117+
private final SideEffectActivities activities =
118+
Workflow.newActivityStub(
119+
SideEffectActivities.class,
120+
ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(2)).build());
121+
122+
int randomInt, sideEffectsRandomInt;
123+
UUID randomUUID;
124+
String result;
125+
126+
@Override
127+
public String execute() {
128+
129+
// Replay-safe way to create random number using Workflow.newRandom
130+
randomInt = Workflow.newRandom().nextInt();
131+
132+
// Replay-safe way to create random uuid
133+
randomUUID = Workflow.randomUUID();
134+
135+
/*
136+
* Random number using side effects. Note that this value is recorded in workflow history.
137+
* On replay the same value is returned so determinism is guaranteed.
138+
*/
139+
sideEffectsRandomInt =
140+
Workflow.sideEffect(
141+
int.class,
142+
() -> {
143+
Random random = new SecureRandom();
144+
return random.nextInt();
145+
});
146+
147+
/**
148+
* Since our randoms are all created safely (using SideEffects or Workflow deterministic
149+
* methods) the workflow result should be same as the queries ran after workflow completion.
150+
* In the case we did not use safe methods, the queries could have a different result.
151+
*/
152+
if ((randomUUID.version() + randomInt + sideEffectsRandomInt) % 2 == 0) {
153+
result = activities.sayHello("World");
154+
} else {
155+
result = activities.sayGoodBye("World!");
156+
}
157+
return result;
158+
}
159+
160+
@Override
161+
public String getResult() {
162+
return result;
163+
}
164+
}
165+
166+
/** Simple activity implementation. */
167+
static class SideEffectActivitiesImpl implements SideEffectActivities {
168+
@Override
169+
public String sayHello(String greeting) {
170+
return "Hello " + greeting;
171+
}
172+
173+
@Override
174+
public String sayGoodBye(String greeting) {
175+
return "Goodbye " + greeting;
176+
}
177+
}
178+
179+
/**
180+
* With our Workflow and Activities defined, we can now start execution. The main method starts
181+
* the worker and then the workflow.
182+
*/
183+
public static void main(String[] args) {
184+
185+
// Define the workflow service.
186+
WorkflowServiceStubs service = WorkflowServiceStubs.newInstance();
187+
188+
/*
189+
* Define the workflow client. It is a Temporal service client used to start, signal, and query
190+
* workflows
191+
*/
192+
WorkflowClient client = WorkflowClient.newInstance(service);
193+
194+
/*
195+
* Define the workflow factory. It is used to create workflow workers for a specific task queue.
196+
*/
197+
WorkerFactory factory = WorkerFactory.newInstance(client);
198+
199+
/*
200+
* Define the workflow worker. Workflow workers listen to a defined task queue and process
201+
* workflows and activities.
202+
*/
203+
Worker worker = factory.newWorker(TASK_QUEUE);
204+
205+
/*
206+
* Register our workflow implementation with the worker.
207+
* Workflow implementations must be known to the worker at runtime in
208+
* order to dispatch workflow tasks.
209+
*/
210+
worker.registerWorkflowImplementationTypes(SideEffectWorkflowImpl.class);
211+
212+
/*
213+
Register our workflow activity implementation with the worker. Since workflow activities are
214+
stateless and thread-safe, we need to register a shared instance.
215+
*/
216+
worker.registerActivitiesImplementations(new SideEffectActivitiesImpl());
217+
218+
/*
219+
* Start all the workers registered for a specific task queue.
220+
* The started workers then start polling for workflows and activities.
221+
*/
222+
factory.start();
223+
224+
// Create the workflow client stub. It is used to start our workflow execution.
225+
SideEffectWorkflow workflow =
226+
client.newWorkflowStub(
227+
SideEffectWorkflow.class,
228+
WorkflowOptions.newBuilder()
229+
.setWorkflowId(WORKFLOW_ID)
230+
.setTaskQueue(TASK_QUEUE)
231+
.build());
232+
233+
/*
234+
* Execute our workflow and wait for it to complete. The call to our start method is
235+
* synchronous.
236+
*
237+
* See {@link io.temporal.samples.hello.HelloSignal} for an example of starting workflow
238+
* without waiting synchronously for its result.
239+
*/
240+
String result = workflow.execute();
241+
242+
// Print workflow result
243+
System.out.println(result);
244+
245+
// Note that query should return the exact same result
246+
System.out.println(workflow.getResult());
247+
248+
System.exit(0);
249+
}
250+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved
3+
*
4+
* Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5+
*
6+
* Modifications copyright (C) 2017 Uber Technologies, Inc.
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
9+
* use this file except in compliance with the License. A copy of the License is
10+
* located at
11+
*
12+
* http://aws.amazon.com/apache2.0
13+
*
14+
* or in the "license" file accompanying this file. This file is distributed on
15+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
16+
* express or implied. See the License for the specific language governing
17+
* permissions and limitations under the License.
18+
*/
19+
20+
package io.temporal.samples.hello;
21+
22+
import static io.temporal.samples.hello.HelloActivity.TASK_QUEUE;
23+
import static org.junit.Assert.assertEquals;
24+
25+
import io.temporal.client.WorkflowClient;
26+
import io.temporal.client.WorkflowOptions;
27+
import io.temporal.testing.TestEnvironmentOptions;
28+
import io.temporal.testing.TestWorkflowEnvironment;
29+
import io.temporal.worker.Worker;
30+
import io.temporal.worker.WorkerFactoryOptions;
31+
import java.time.Duration;
32+
import org.junit.After;
33+
import org.junit.Before;
34+
import org.junit.Test;
35+
36+
public class HelloSideEffectTest {
37+
private TestWorkflowEnvironment testEnv;
38+
private Worker worker;
39+
private WorkflowClient client;
40+
41+
@Before
42+
public void setUp() {
43+
TestEnvironmentOptions testEnvironmentOptions =
44+
TestEnvironmentOptions.newBuilder()
45+
.setWorkerFactoryOptions(
46+
WorkerFactoryOptions.newBuilder()
47+
.setWorkflowHostLocalTaskQueueScheduleToStartTimeout(Duration.ZERO)
48+
.build())
49+
.build();
50+
51+
testEnv = TestWorkflowEnvironment.newInstance(testEnvironmentOptions);
52+
worker = testEnv.newWorker(TASK_QUEUE);
53+
worker.registerWorkflowImplementationTypes(HelloSideEffect.SideEffectWorkflowImpl.class);
54+
client = testEnv.getWorkflowClient();
55+
}
56+
57+
@After
58+
public void tearDown() {
59+
testEnv.close();
60+
}
61+
62+
@Test
63+
public void testSideffectsWorkflow() {
64+
worker.registerActivitiesImplementations(new HelloSideEffect.SideEffectActivitiesImpl());
65+
testEnv.start();
66+
67+
// Get a workflow stub using the same task queue the worker uses.
68+
HelloSideEffect.SideEffectWorkflow workflow =
69+
client.newWorkflowStub(
70+
HelloSideEffect.SideEffectWorkflow.class,
71+
WorkflowOptions.newBuilder().setTaskQueue(TASK_QUEUE).build());
72+
// Execute a workflow waiting for it to complete.
73+
String result = workflow.execute();
74+
// make sure the result is same as the query result after workflow completion
75+
assertEquals(result, workflow.getResult());
76+
}
77+
}

0 commit comments

Comments
 (0)