diff --git a/README.md b/README.md index 77c00a4e..1df205ae 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ See the README.md file in each main sample directory for cut/paste Gradle comman - [**HelloChild**](/core/src/main/java/io/temporal/samples/hello/HelloChild.java): Demonstrates how to execute a simple Child Workflow. - [**HelloCron**](/core/src/main/java/io/temporal/samples/hello/HelloCron.java): Demonstrates how to execute a Workflow according to a cron schedule. - [**HelloDynamic**](/core/src/main/java/io/temporal/samples/hello/HelloDynamic.java): Demonstrates how to use `DynamicWorkflow` and `DynamicActivity` interfaces. + - [**HelloEagerWorkflowStart**](/core/src/main/java/io/temporal/samples/hello/HelloEagerWorkflowStart.java): Demonstrates the use of a eager workflow start. - [**HelloPeriodic**](/core/src/main/java/io/temporal/samples/hello/HelloPeriodic.java): Demonstrates the use of the Continue-As-New feature. - [**HelloException**](/core/src/main/java/io/temporal/samples/hello/HelloException.java): Demonstrates how to handle exception propagation and wrapping. - [**HelloLocalActivity**](/core/src/main/java/io/temporal/samples/hello/HelloLocalActivity.java): Demonstrates the use of a [Local Activity](https://docs.temporal.io/docs/jargon/mesh/#local-activity). diff --git a/core/src/main/java/io/temporal/samples/hello/HelloEagerWorkflowStart.java b/core/src/main/java/io/temporal/samples/hello/HelloEagerWorkflowStart.java new file mode 100644 index 00000000..c901753a --- /dev/null +++ b/core/src/main/java/io/temporal/samples/hello/HelloEagerWorkflowStart.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved + * + * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not + * use this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package io.temporal.samples.hello; + +import io.temporal.activity.ActivityInterface; +import io.temporal.activity.ActivityMethod; +import io.temporal.activity.ActivityOptions; +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; +import io.temporal.workflow.Workflow; +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; +import java.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Sample Temporal Workflow Definition that starts eagerly and executes a single Local Activity. + * Important elements of eager starting are: the client starting the workflow and the worker + * executing it need to be shared, worker options needs to have .setDisableEagerExecution(false) + * set, the activity is recommended to be a local activity for best performance + */ +public class HelloEagerWorkflowStart { + + // Define the task queue name + static final String TASK_QUEUE = "HelloEagerWorkflowStartTaskQueue"; + + // Define our workflow unique id + static final String WORKFLOW_ID = "HelloEagerWorkflowStartWorkflow"; + + /** + * The Workflow Definition's Interface must contain one method annotated with @WorkflowMethod. + * + *

Workflow Definitions should not contain any heavyweight computations, non-deterministic + * code, network calls, database operations, etc. Those things should be handled by the + * Activities. + * + * @see io.temporal.workflow.WorkflowInterface + * @see io.temporal.workflow.WorkflowMethod + */ + @WorkflowInterface + public interface GreetingWorkflow { + + /** + * This is the method that is executed when the Workflow Execution is started. The Workflow + * Execution completes when this method finishes execution. + */ + @WorkflowMethod + String getGreeting(String name); + } + + /** + * This is the Activity Definition's Interface. Activities are building blocks of any Temporal + * Workflow and contain any business logic that could perform long running computation, network + * calls, etc. + * + *

Annotating Activity Definition methods with @ActivityMethod is optional. + * + * @see io.temporal.activity.ActivityInterface + * @see io.temporal.activity.ActivityMethod + */ + @ActivityInterface + public interface GreetingActivities { + + // Define your activity method which can be called during workflow execution + @ActivityMethod(name = "greet") + String composeGreeting(String greeting, String name); + } + + // Define the workflow implementation which implements our getGreeting workflow method. + public static class GreetingWorkflowImpl implements GreetingWorkflow { + + /** + * Define the GreetingActivities stub. Activity stubs are proxies for activity invocations that + * are executed outside of the workflow thread on the activity worker, that can be on a + * different host. Temporal is going to dispatch the activity results back to the workflow and + * unblock the stub as soon as activity is completed on the activity worker. + * + *

In the {@link ActivityOptions} definition the "setStartToCloseTimeout" option sets the + * overall timeout that our workflow is willing to wait for activity to complete. For this + * example it is set to 2 seconds. + */ + private final GreetingActivities activities = + Workflow.newActivityStub( + GreetingActivities.class, + ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(2)).build()); + + @Override + public String getGreeting(String name) { + // This is a blocking call that returns only after the activity has completed. + return activities.composeGreeting("Hello", name); + } + } + + /** Simple activity implementation, that concatenates two strings. */ + static class GreetingLocalActivitiesImpl implements GreetingActivities { + private static final Logger log = LoggerFactory.getLogger(GreetingLocalActivitiesImpl.class); + + @Override + public String composeGreeting(String greeting, String name) { + log.info("Composing greeting..."); + return greeting + " " + name + "!"; + } + } + + /** + * With our Workflow and Activities defined, we can now start execution. The main method starts + * the worker and then the workflow. + */ + public static void main(String[] args) { + + // Get a Workflow service stub. + WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + + /* + * Get a Workflow service client which can be used to start, Signal, and Query Workflow Executions. + */ + WorkflowClient client = WorkflowClient.newInstance(service); + + /* + * Define the workflow factory. It is used to create workflow workers for a specific task queue. + * Here it's important that the worker shares the client for eager execution. + */ + WorkerFactory factory = WorkerFactory.newInstance(client); + + /* + * Define the workflow worker. Workflow workers listen to a defined task queue and process + * workflows and activities. + */ + Worker worker = factory.newWorker(TASK_QUEUE); + + /* + * Register our workflow implementation with the worker. + * Workflow implementations must be known to the worker at runtime in + * order to dispatch workflow tasks. + */ + worker.registerWorkflowImplementationTypes(GreetingWorkflowImpl.class); + + /* + * Register our Activity Types with the Worker. Since Activities are stateless and thread-safe, + * the Activity Type is a shared instance. + */ + worker.registerActivitiesImplementations(new GreetingLocalActivitiesImpl()); + + /* + * Start all the workers registered for a specific task queue. + * The started workers then start polling for workflows and activities. + */ + factory.start(); + + // Create the workflow client stub. It is used to start our workflow execution. + GreetingWorkflow workflow = + client.newWorkflowStub( + GreetingWorkflow.class, + WorkflowOptions.newBuilder() + .setWorkflowId(WORKFLOW_ID) + .setTaskQueue(TASK_QUEUE) + .setDisableEagerExecution(false) // set this to enable eager execution + .build()); + + /* + * Execute our workflow and wait for it to complete. The call to our getGreeting method is + * synchronous. + * + * See {@link io.temporal.samples.hello.HelloSignal} for an example of starting workflow + * without waiting synchronously for its result. + */ + String greeting = workflow.getGreeting("World"); + + // Display workflow execution results + System.out.println(greeting); + System.exit(0); + } +} diff --git a/core/src/main/java/io/temporal/samples/hello/README.md b/core/src/main/java/io/temporal/samples/hello/README.md index 86eba613..41dd1827 100644 --- a/core/src/main/java/io/temporal/samples/hello/README.md +++ b/core/src/main/java/io/temporal/samples/hello/README.md @@ -20,6 +20,7 @@ To run each hello world sample, use one of the following commands: ./gradlew -q execute -PmainClass=io.temporal.samples.hello.HelloChild ./gradlew -q execute -PmainClass=io.temporal.samples.hello.HelloCron ./gradlew -q execute -PmainClass=io.temporal.samples.hello.HelloDynamic +./gradlew -q execute -PmainClass=io.temporal.samples.hello.HelloEagerWorkflowStart ./gradlew -q execute -PmainClass=io.temporal.samples.hello.HelloException ./gradlew -q execute -PmainClass=io.temporal.samples.hello.HelloLocalActivity ./gradlew -q execute -PmainClass=io.temporal.samples.hello.HelloPeriodic diff --git a/core/src/test/java/io/temporal/samples/hello/HelloEagerWorkflowStartTest.java b/core/src/test/java/io/temporal/samples/hello/HelloEagerWorkflowStartTest.java new file mode 100644 index 00000000..563d2382 --- /dev/null +++ b/core/src/test/java/io/temporal/samples/hello/HelloEagerWorkflowStartTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved + * + * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not + * use this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package io.temporal.samples.hello; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +import io.temporal.client.WorkflowOptions; +import io.temporal.samples.hello.HelloEagerWorkflowStart.GreetingActivities; +import io.temporal.samples.hello.HelloEagerWorkflowStart.GreetingLocalActivitiesImpl; +import io.temporal.samples.hello.HelloEagerWorkflowStart.GreetingWorkflow; +import io.temporal.samples.hello.HelloEagerWorkflowStart.GreetingWorkflowImpl; +import io.temporal.testing.TestWorkflowRule; +import org.junit.Rule; +import org.junit.Test; + +/** Unit test for {@link HelloEagerWorkflowStart}. Doesn't use an external Temporal service. */ +public class HelloEagerWorkflowStartTest { + + @Rule + public TestWorkflowRule testWorkflowRule = + TestWorkflowRule.newBuilder() + .setWorkflowTypes(GreetingWorkflowImpl.class) + .setDoNotStart(true) + .build(); + + @Test + public void testActivityImpl() { + testWorkflowRule + .getWorker() + .registerActivitiesImplementations(new GreetingLocalActivitiesImpl()); + testWorkflowRule.getTestEnvironment().start(); + + // Get a workflow stub using the same task queue the worker uses. + GreetingWorkflow workflow = + testWorkflowRule + .getWorkflowClient() + .newWorkflowStub( + GreetingWorkflow.class, + WorkflowOptions.newBuilder().setTaskQueue(testWorkflowRule.getTaskQueue()).build()); + // Execute a workflow waiting for it to complete. + String greeting = workflow.getGreeting("World"); + assertEquals("Hello World!", greeting); + + testWorkflowRule.getTestEnvironment().shutdown(); + } + + @Test + public void testMockedActivity() { + // withoutAnnotations() is required to stop Mockito from copying + // method-level annotations from the GreetingActivities interface + GreetingActivities activities = + mock(GreetingActivities.class, withSettings().withoutAnnotations()); + when(activities.composeGreeting("Hello", "World")).thenReturn("Hello World!"); + testWorkflowRule.getWorker().registerActivitiesImplementations(activities); + testWorkflowRule.getTestEnvironment().start(); + + // Get a workflow stub using the same task queue the worker uses. + GreetingWorkflow workflow = + testWorkflowRule + .getWorkflowClient() + .newWorkflowStub( + GreetingWorkflow.class, + WorkflowOptions.newBuilder().setTaskQueue(testWorkflowRule.getTaskQueue()).build()); + // Execute a workflow waiting for it to complete. + String greeting = workflow.getGreeting("World"); + assertEquals("Hello World!", greeting); + + testWorkflowRule.getTestEnvironment().shutdown(); + } +}