From bc00fe73a63e55672a22dbc91084ceea9677d7ea Mon Sep 17 00:00:00 2001 From: Mason Egger Date: Wed, 27 Mar 2024 10:51:28 -0500 Subject: [PATCH 1/4] init commit --- exercises/async-activity-completion/README.md | 57 +++++++++ .../solution/pom.xml | 109 ++++++++++++++++++ .../AsyncActivityCompletionActivities.java | 10 ++ ...AsyncActivityCompletionActivitiesImpl.java | 59 ++++++++++ .../AsyncActivityCompletionWorker.java | 26 +++++ .../AsyncActivityCompletionWorkflow.java | 14 +++ .../AsyncActivityCompletionWorkflowImpl.java | 38 ++++++ .../CompletionClient.java | 33 ++++++ .../java/asyncactivitycompletion/Starter.java | 29 +++++ .../solution/src/main/resources/logback.xml | 34 ++++++ 10 files changed, 409 insertions(+) create mode 100644 exercises/async-activity-completion/README.md create mode 100644 exercises/async-activity-completion/solution/pom.xml create mode 100644 exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivities.java create mode 100644 exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivitiesImpl.java create mode 100644 exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorker.java create mode 100644 exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflow.java create mode 100644 exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflowImpl.java create mode 100644 exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/CompletionClient.java create mode 100644 exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/Starter.java create mode 100644 exercises/async-activity-completion/solution/src/main/resources/logback.xml diff --git a/exercises/async-activity-completion/README.md b/exercises/async-activity-completion/README.md new file mode 100644 index 0000000..f5bc87a --- /dev/null +++ b/exercises/async-activity-completion/README.md @@ -0,0 +1,57 @@ +# Exercise 3: Asynchronous Activity Completion + +During this exercise, you will: + +- Retrieve a task token from your Activity execution +Throw an `ErrResultPending` to indicate that the Activity is waiting for an external completion. +- Use another Temporal Client to communicate the result of the asynchronous Activity back to the Workflow + +Make your changes to the code in the `practice` subdirectory (look for `TODO` comments that will guide you to where you should make changes to the code). If you need a hint or want to verify your changes, look at the complete version in the `solution` subdirectory. + +## Part A: Retrieving the Task Token + +1. Edit the `workflow.go` file. In the `Activity()` definition, add a call to `activity.GetInfo()` that returns an `activityInfo` object. +2. From that object, extract `activityInfo.TaskToken`. To asynchronously complete an Activity, you need to store this token outside of this Workflow, so that you can call it from another Temporal Client. The most straightforward way to do this is to encode it to hexadecimal or base64 and log it to your terminal, so add a call like `logger.Info("Activity", "taskToken", hex.EncodeToString(taskToken))`. +3. Save the file. + +## Part B: Set Your Activity to Return `ErrResultPending` + +1. Continue editing the same Activity definition in the `workflow.go` file. You need to add a `return` statement that returns a special kind of error, `activity.ErrResultPending`, that will Temporal that the Activity has not failed but will be completed asynchronously. +2. Note that the Workflow's `StartToCloseTimeout` has been lengthened to 300 seconds for this exercise. Activities can still time out if they are running in the background. +3. Save the file. + +## Part C: Configure a Client to send CompleteActivity + +1. Now you can edit the `completionclient/main.go` file to call `CompleteActivity`. The first thing you'll need to do is add some way of supplying the `taskToken` specific to the Activity you are trying to complete at runtime. In a production system, you might store and retrieve the token from a database, but for now, you can configure this Client to accept it as an additional argument by adding `flag` parsing to the `main()` block: + +```go +var taskToken string +flag.StringVar(&taskToken, "tasktoken", "", "Task Token of Activity to Complete") +flag.Parse() +decoded, err := hex.DecodeString(taskToken) +if err != nil { + log.Fatalln("Unable to decode token", err) +} +``` + +2. Next, add the call to `CompleteActivity()`. This function requires a variable to write its result to, so provide something like `var result string`. Then, add a call to `c.CompleteActivity(context.Background(), decoded, result, err)`. Don't forget to add any error handling as needed. +3. Save the file. + +## Part D: Running the Workflow and Completing it Asynchronously + +At this point, you can run your Workflow. As with the Signal Exercise, the Workflow will not return on its own -- in this case, because your Activity is set to complete asynchronously, and will wait to receive `CompleteActivity()`. + +1. In one terminal, navigate to the `worker` subdirectory and run `go run main.go`. +2. In another terminal, navigate to the `starter` subdirectory and run `go run main.go`. Your work will produce some logging, eventually including your `taskToken`: + +``` +2024/03/14 15:14:00 INFO Activity Namespace default TaskQueue async WorkerID 22396@Omelas@ ActivityID 5 ActivityType Activity Attempt 1 WorkflowType Workflow WorkflowID async RunID 0c3cb022-042f-4437-b021-a6cf2a4afe1b taskToken 0a2461613733613533322d363337362d346130332d613563342d36626134626437306139623312056173796e631a2430633363623032322d303432662d343433372d623032312d61366366326134616665316220052801320135420841637469766974794a08080110be80401801 +``` + +3. You can now use this token to send a `CompleteActivity()` call from another client. In a third terminal, navigate to the `completeclient` subdirectory and run `go run main.go --tasktoken [tasktoken]`, pasting the token from the previous step. This will cause your Activity to return and your Workflow to successfully complete. The terminal running your Worker process should now show `workflow completed`: + +``` +2024/03/14 15:15:45 INFO Async workflow completed. Namespace default TaskQueue async WorkerID 41996@Omelas@ WorkflowType Workflow WorkflowID async RunID cf865c9f-2487-47a3-84c4-fd0c6da6ee6c Attempt 1 result +``` + +### This is the end of the exercise. diff --git a/exercises/async-activity-completion/solution/pom.xml b/exercises/async-activity-completion/solution/pom.xml new file mode 100644 index 0000000..cd32917 --- /dev/null +++ b/exercises/async-activity-completion/solution/pom.xml @@ -0,0 +1,109 @@ + + + + 4.0.0 + + io.temporal.learn + querying-workflows-solution + 1.0.0-SNAPSHOT + + querying workflows (solution) + https://learn.temporal.io/ + + + UTF-8 + 1.8 + 1.8 + + + + + + io.temporal + temporal-sdk + 1.20.1 + + + + ch.qos.logback + logback-classic + 1.4.8 + + + + org.apache.commons + commons-lang3 + 3.11 + + + + io.temporal + temporal-testing + 1.20.1 + test + + + + + org.junit.jupiter + junit-jupiter + 5.5.2 + test + + + + org.mockito + mockito-junit-jupiter + 5.3.1 + test + + + + + + + + + + maven-clean-plugin + 3.1.0 + + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.8.0 + + + maven-surefire-plugin + 2.22.1 + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + + maven-site-plugin + 3.7.1 + + + maven-project-info-reports-plugin + 3.0.0 + + + + + diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivities.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivities.java new file mode 100644 index 0000000..3e25b00 --- /dev/null +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivities.java @@ -0,0 +1,10 @@ +package asyncactivitycompletion; + +import io.temporal.activity.ActivityInterface; + +@ActivityInterface +public interface AsyncActivityCompletionActivities { + + String activity(String input); + +} diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivitiesImpl.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivitiesImpl.java new file mode 100644 index 0000000..9abe712 --- /dev/null +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivitiesImpl.java @@ -0,0 +1,59 @@ +package asyncactivitycompletion; + + +import io.temporal.activity.Activity; +import io.temporal.activity.ActivityExecutionContext; +import io.temporal.client.ActivityCompletionClient; + + +import java.util.concurrent.ForkJoinPool; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ForkJoinPool; + + +public class AsyncActivityCompletionActivitiesImpl implements AsyncActivityCompletionActivities { + + private static final Logger logger = LoggerFactory.getLogger(AsyncActivityCompletionActivitiesImpl.class); + + private final ActivityCompletionClient completionClient; + + AsyncActivityCompletionActivitiesImpl(ActivityCompletionClient completionClient) { + this.completionClient = completionClient; + } + + @Override + public String activity(String input) { + logger.info("Activity received input: {}", input); + + // Get the activity execution context + ActivityExecutionContext context = Activity.getExecutionContext(); + + // Set a correlation token that can be used to complete the activity asynchronously + byte[] taskToken = context.getTaskToken(); + + logger.info("Task token: " + new String(taskToken)); + + /* + * For the example we will use a {@link java.util.concurrent.ForkJoinPool} to execute our + * activity. In real-life applications this could be any service. The composeGreetingAsync + * method is the one that will actually complete workflow action execution. + */ + ForkJoinPool.commonPool().execute(() -> completeActivity(taskToken, input)); + context.doNotCompleteOnReturn(); + + // Since we have set doNotCompleteOnReturn(), the workflow action method return value is + // ignored. + return "ignored"; + } + + private void completeActivity(byte[] taskToken, String input){ + String result = "Received: " + input; + + // Complete our workflow activity using ActivityCompletionClient + completionClient.complete(taskToken, result); + } + +} diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorker.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorker.java new file mode 100644 index 0000000..b82ee01 --- /dev/null +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorker.java @@ -0,0 +1,26 @@ +package asyncactivitycompletion; + +import io.temporal.client.WorkflowClient; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.client.ActivityCompletionClient; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; + +public class AsyncActivityCompletionWorker { + public static void main(String[] args) { + + WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + WorkflowClient client = WorkflowClient.newInstance(service); + WorkerFactory factory = WorkerFactory.newInstance(client); + + Worker worker = factory.newWorker("async-complete"); + + worker.registerWorkflowImplementationTypes(AsyncActivityCompletionWorkflowImpl.class); + + ActivityCompletionClient completionClient = client.newActivityCompletionClient(); + + worker.registerActivitiesImplementations(new AsyncActivityCompletionActivitiesImpl(completionClient)); + + factory.start(); + } +} diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflow.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflow.java new file mode 100644 index 0000000..29510bc --- /dev/null +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflow.java @@ -0,0 +1,14 @@ +package asyncactivitycompletion; + +import io.temporal.workflow.SignalMethod; +import io.temporal.workflow.QueryMethod; +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; + +@WorkflowInterface +public interface AsyncActivityCompletionWorkflow { + + @WorkflowMethod + String workflow(String input); + +} diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflowImpl.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflowImpl.java new file mode 100644 index 0000000..b864126 --- /dev/null +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflowImpl.java @@ -0,0 +1,38 @@ +package asyncactivitycompletion; + +import java.time.Duration; + +import org.slf4j.Logger; + +import io.temporal.activity.ActivityOptions; +import io.temporal.workflow.Workflow; + + +public class AsyncActivityCompletionWorkflowImpl implements AsyncActivityCompletionWorkflow { + + public static final Logger logger = Workflow.getLogger(AsyncActivityCompletionWorkflowImpl.class); + + private boolean fulfilled = false; + private String currentState; + + private final ActivityOptions options = + ActivityOptions.newBuilder() + .setStartToCloseTimeout(Duration.ofSeconds(5)) + .build(); + + private final AsyncActivityCompletionActivities activities = + Workflow.newActivityStub(AsyncActivityCompletionActivities.class, options); + + @Override + public String workflow(String input) { + + logger.info("Async Activity Workflow started with input: {}", input); + + + String activityResult = activities.activity(input); + + logger.info("Async Activity Workflow completed with result: {}", activityResult); + + return activityResult; + } +} diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/CompletionClient.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/CompletionClient.java new file mode 100644 index 0000000..5b20b5a --- /dev/null +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/CompletionClient.java @@ -0,0 +1,33 @@ +package asyncactivitycompletion; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import io.temporal.client.WorkflowClient; +import io.temporal.serviceclient.WorkflowServiceStubs; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class CompletionClient { + public static void main(String[] args) throws ExecutionException, InterruptedException { + + WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + + WorkflowClient client = WorkflowClient.newInstance(service); + + AsyncActivityCompletionWorkflow workflow = client.newWorkflowStub(AsyncActivityCompletionWorkflow.class, "async-complete-workflow"); + + /* + * Here we use {@link io.temporal.client.WorkflowClient} to execute our workflow asynchronously. + * It gives us back a {@link java.util.concurrent.CompletableFuture}. We can then call its get + * method to block and wait until a result is available. + */ + CompletableFuture result = WorkflowClient.execute(workflow::workflow, "Plain text"); + + // Wait for workflow execution to complete and display its results. + System.out.println("Received result" + result.get()); + + System.exit(0); + } +} diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/Starter.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/Starter.java new file mode 100644 index 0000000..aac2f17 --- /dev/null +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/Starter.java @@ -0,0 +1,29 @@ +package asyncactivitycompletion; + +import java.util.concurrent.CompletableFuture; + +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; + + +public class Starter { + public static void main(String[] args) throws Exception { + + WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + + WorkflowClient client = WorkflowClient.newInstance(service); + + WorkflowOptions options = WorkflowOptions.newBuilder() + .setWorkflowId("async-complete-workflow") + .setTaskQueue("async-complete") + .build(); + + AsyncActivityCompletionWorkflow workflow = client.newWorkflowStub(AsyncActivityCompletionWorkflow.class, options); + + CompletableFuture result = WorkflowClient.execute(workflow::workflow, "Plain text input"); + + System.out.printf("Workflow result: %s\n", result.get()); + System.exit(0); + } +} diff --git a/exercises/async-activity-completion/solution/src/main/resources/logback.xml b/exercises/async-activity-completion/solution/src/main/resources/logback.xml new file mode 100644 index 0000000..8e488b0 --- /dev/null +++ b/exercises/async-activity-completion/solution/src/main/resources/logback.xml @@ -0,0 +1,34 @@ + + + + + + + + + %d{HH:mm:ss.SSS} %-5level - %msg %n + + + + + + + \ No newline at end of file From bff36106e94500e1710bfd98d801b7f65c5f0936 Mon Sep 17 00:00:00 2001 From: Mason Egger Date: Wed, 27 Mar 2024 15:06:07 -0500 Subject: [PATCH 2/4] adding exercise 3 --- .bash_aliases | 3 + .gitignore | 1 + exercises/async-activity-completion/README.md | 84 +++++++++----- .../practice/pom.xml | 109 ++++++++++++++++++ .../AsyncActivityCompletionActivities.java | 10 ++ ...AsyncActivityCompletionActivitiesImpl.java | 43 +++++++ .../AsyncActivityCompletionWorker.java | 24 ++++ .../AsyncActivityCompletionWorkflow.java | 14 +++ .../AsyncActivityCompletionWorkflowImpl.java | 38 ++++++ .../CompletionClient.java | 33 ++++++ .../java/asyncactivitycompletion/Starter.java | 29 +++++ .../practice/src/main/resources/logback.xml | 34 ++++++ .../solution/pom.xml | 4 +- ...AsyncActivityCompletionActivitiesImpl.java | 27 +---- .../AsyncActivityCompletionWorker.java | 4 +- .../AsyncActivityCompletionWorkflowImpl.java | 2 +- .../CompletionClient.java | 19 ++- 17 files changed, 409 insertions(+), 69 deletions(-) create mode 100644 exercises/async-activity-completion/practice/pom.xml create mode 100644 exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivities.java create mode 100644 exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivitiesImpl.java create mode 100644 exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorker.java create mode 100644 exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflow.java create mode 100644 exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflowImpl.java create mode 100644 exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/CompletionClient.java create mode 100644 exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/Starter.java create mode 100644 exercises/async-activity-completion/practice/src/main/resources/logback.xml diff --git a/.bash_aliases b/.bash_aliases index 30e12da..3540e2e 100644 --- a/.bash_aliases +++ b/.bash_aliases @@ -19,5 +19,8 @@ alias ex3s="cd ${GITPOD_REPO_ROOT}/exercises/async-activity-completion/solution" alias ex3w="mvn exec:java -Dexec.mainClass='asyncactivitycompletion.AsyncActivityCompletionWorker'" alias ex3st="mvn exec:java -Dexec.mainClass='asyncactivitycompletion.Starter'" alias ex3sg="mvn exec:java -Dexec.mainClass='asyncactivitycompletion.SignalClient'" +ex3c() { + mvn exec:java -Dexec.mainClass="asyncactivitycompletion.CompletionClient" -Dexec.args="${1}" +} echo "Your workspace is located at: ${GITPOD_REPO_ROOT}" echo "Type the command workspace to return to the workspace directory at any time." diff --git a/.gitignore b/.gitignore index 524f096..80e6b9b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Compiled class file *.class +**target* # Log file *.log diff --git a/exercises/async-activity-completion/README.md b/exercises/async-activity-completion/README.md index f5bc87a..550a2a7 100644 --- a/exercises/async-activity-completion/README.md +++ b/exercises/async-activity-completion/README.md @@ -3,55 +3,79 @@ During this exercise, you will: - Retrieve a task token from your Activity execution -Throw an `ErrResultPending` to indicate that the Activity is waiting for an external completion. +- Set the `doNotCompleteOnReturn()` context to indicate that the Activity is waiting for an external completion. - Use another Temporal Client to communicate the result of the asynchronous Activity back to the Workflow Make your changes to the code in the `practice` subdirectory (look for `TODO` comments that will guide you to where you should make changes to the code). If you need a hint or want to verify your changes, look at the complete version in the `solution` subdirectory. -## Part A: Retrieving the Task Token +### GitPod Environment Shortcuts -1. Edit the `workflow.go` file. In the `Activity()` definition, add a call to `activity.GetInfo()` that returns an `activityInfo` object. -2. From that object, extract `activityInfo.TaskToken`. To asynchronously complete an Activity, you need to store this token outside of this Workflow, so that you can call it from another Temporal Client. The most straightforward way to do this is to encode it to hexadecimal or base64 and log it to your terminal, so add a call like `logger.Info("Activity", "taskToken", hex.EncodeToString(taskToken))`. -3. Save the file. +If you are executing the exercises in the provided GitPod environment, you +can take advantage of certain aliases to aid in navigation and execution of +the code. -## Part B: Set Your Activity to Return `ErrResultPending` +| Command | Action | +| :---------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ex3` | Change to Exercise 3 Practice Directory | +| `ex3s` | Change to Exercise 3 Solution Directory | +| `ex3w` | Execute the Exercise 3 Worker. Must be within the appropriate directory for this to succeed. (either `practice` or `solution`) | +| `ex3st` | Execute the Exercise 3 Starter. Must be within the appropriate directory for this to succeed. (either `practice` or `solution`) | +| `ex3c TASK_TOKEN` | Complete the Exercise 3 Activity, passing in the Task Token. Must be within the appropriate directory for this to succeed. (either `practice` or `solution`) | -1. Continue editing the same Activity definition in the `workflow.go` file. You need to add a `return` statement that returns a special kind of error, `activity.ErrResultPending`, that will Temporal that the Activity has not failed but will be completed asynchronously. -2. Note that the Workflow's `StartToCloseTimeout` has been lengthened to 300 seconds for this exercise. Activities can still time out if they are running in the background. -3. Save the file. +## Part A: Retrieving the Task Token -## Part C: Configure a Client to send CompleteActivity +1. Open the `AsyncActivityCompletionActivitiesImpl.java` file in the `src/main/java/asyncactivitycompletion` subdirectory. +1. In the `activity()` definition, add the line `ActivityExecutionContext context = Activity.getExecutionContext();` to get the current Execution Context of the Activity. +1. Add a call to `getTaskToken()` from the `context` object above and store it in a `byte []` named `taskToken` +1. Uncomment the line below to convert the `taskToken` byte array to Base64. +1. Log the Task Token at `info` level using the `logger` object for later use. You will need to convert this to a new String. +1. Save the file. -1. Now you can edit the `completionclient/main.go` file to call `CompleteActivity`. The first thing you'll need to do is add some way of supplying the `taskToken` specific to the Activity you are trying to complete at runtime. In a production system, you might store and retrieve the token from a database, but for now, you can configure this Client to accept it as an additional argument by adding `flag` parsing to the `main()` block: +## Part B: Set Your Activity to `doNotCompleteOnReturn()` -```go -var taskToken string -flag.StringVar(&taskToken, "tasktoken", "", "Task Token of Activity to Complete") -flag.Parse() -decoded, err := hex.DecodeString(taskToken) -if err != nil { - log.Fatalln("Unable to decode token", err) -} -``` +1. Continue editing the same Activity definition in the `AsyncActivityCompletionImpl.java` file. + 1. Add a call to the `doNotCompleteOnReturn();` method using the `context` object from Part A. This notifies + Temporal that the Activity should not be completed on return and will be completed asynchronously. + 1. Save the file. +1. Open the `AsyncActivityCompletionWorkflowImpl.java` file in the `src/main/java/asyncactivitycompletion` subdirectory. + 1. Observe that the Workflow's `StartToCloseTimeout` has been lengthened to 300 seconds for this exercise. Activities can still time out if they are running in the background. + 1. Close this file without making any changes. -2. Next, add the call to `CompleteActivity()`. This function requires a variable to write its result to, so provide something like `var result string`. Then, add a call to `c.CompleteActivity(context.Background(), decoded, result, err)`. Don't forget to add any error handling as needed. -3. Save the file. +## Part C: Configure a Client to send CompleteActivity + +1. Open the `CompletionClient.java` file in the `src/main/java/asyncactivitycompletion` subdirectory. +1. The first thing you'll need to do is add some way of supplying the `taskToken` specific to the Activity you are trying to complete at runtime. In a production system, you might store and retrieve the token from a database, but for now, you can configure this Client to accept it as a command line arguement. Read in the token from the command line `args[0]` and decode the base 64, storing it in a `byte[]`. Hint, invert the call in `AsyncActivityCompletionActivitiesImpl.java` +1. Add a call to the `doNotCompleteOnReturn();` method using the `context` object from Part A. This notifies Temporal that the Activity should not be completed on return and will be completed asynchronously. +1. Save the file. ## Part D: Running the Workflow and Completing it Asynchronously At this point, you can run your Workflow. As with the Signal Exercise, the Workflow will not return on its own -- in this case, because your Activity is set to complete asynchronously, and will wait to receive `CompleteActivity()`. -1. In one terminal, navigate to the `worker` subdirectory and run `go run main.go`. -2. In another terminal, navigate to the `starter` subdirectory and run `go run main.go`. Your work will produce some logging, eventually including your `taskToken`: +1. In one terminal, navigate to the `practice` subdirectory + 1. If you're in the GitPod environment you can instead run `ex3` + 1. Compile the code using `mvn clean compile` + 1. Run the worker using `mvn exec:java -Dexec.mainClas='asyncactivitycompletion.AsyncActivityCompletionWorker'`. + 1. If you're in the GitPod environment you can instead run `ex3w` +1. In another terminal, navigate to the `practice` subdirectory + 1. If you're in the GitPod environment you can instead run `ex3` + 1. Invoke the Workflow using `mvn exec:java -Dexec.mainClass='asyncactivitycompletion.Starter'` + If you're in the GitPod environment you can instead run `ex3st` +1. Navigate back to the Worker terminal. Your work will produce some logging, eventually including your `taskToken`: ``` -2024/03/14 15:14:00 INFO Activity Namespace default TaskQueue async WorkerID 22396@Omelas@ ActivityID 5 ActivityType Activity Attempt 1 WorkflowType Workflow WorkflowID async RunID 0c3cb022-042f-4437-b021-a6cf2a4afe1b taskToken 0a2461613733613533322d363337362d346130332d613563342d36626134626437306139623312056173796e631a2430633363623032322d303432662d343433372d623032312d61366366326134616665316220052801320135420841637469766974794a08080110be80401801 +12:06:58.679 INFO - Async Activity Workflow started with input: Plain text input +12:06:58.684 INFO - Activity received input: Plain text input +CiQ3NGE5NGEyMC05NjE2LTQ3YWEtOTMwMS01YmQ4MzFlZTE4M2ISF2FzeW5jLWNvbXBsZXRlLXdvcmtmbG93GiQzZWY3YjE0YS1lMzdiLTQ3NGItOTIyMS1iN2UyYjM3MGU0MGMgBSgBMiRhMzMxN2E1My0xMzMwLTM4N2YtYTBiNC1kN2RjMjAzNjAwY2NCCEFjdGl2aXR5SggIARC+gEAYAQ== ``` -3. You can now use this token to send a `CompleteActivity()` call from another client. In a third terminal, navigate to the `completeclient` subdirectory and run `go run main.go --tasktoken [tasktoken]`, pasting the token from the previous step. This will cause your Activity to return and your Workflow to successfully complete. The terminal running your Worker process should now show `workflow completed`: - -``` -2024/03/14 15:15:45 INFO Async workflow completed. Namespace default TaskQueue async WorkerID 41996@Omelas@ WorkflowType Workflow WorkflowID async RunID cf865c9f-2487-47a3-84c4-fd0c6da6ee6c Attempt 1 result -``` +1. You can now use this token to send a `complete()` call from another client. In another terminal, navigate to the `practice` subdirectory + 1. If you're in the GitPod environment you can instead run `ex3` + 1. Run the command `mvn exec:java -Dexec.mainClass='asyncactivitycompletion.CompletionClient' -Dexec.args="TASK_TOKEN"` with your Task Token replacing `TASK_TOKEN` with your Task Token to complete the Activity. + 1. If you're in the GitPod environment you can instead run `ex3c TASK_TOKEN` replacing `TASK_TOKEN` with your Task Token to complete the Activity. + 1. This will cause your Activity to return and your Workflow to successfully complete. The terminal running your Worker process should now show + ``` + 12:07:43.689 INFO - Async Activity Workflow completed with result: Asynchronously completed + ``` ### This is the end of the exercise. diff --git a/exercises/async-activity-completion/practice/pom.xml b/exercises/async-activity-completion/practice/pom.xml new file mode 100644 index 0000000..3e360ad --- /dev/null +++ b/exercises/async-activity-completion/practice/pom.xml @@ -0,0 +1,109 @@ + + + + 4.0.0 + + io.temporal.learn + async-activity-completion-solution + 1.0.0-SNAPSHOT + + async activity completion (solution) + https://learn.temporal.io/ + + + UTF-8 + 1.8 + 1.8 + + + + + + io.temporal + temporal-sdk + 1.20.1 + + + + ch.qos.logback + logback-classic + 1.4.8 + + + + org.apache.commons + commons-lang3 + 3.11 + + + + io.temporal + temporal-testing + 1.20.1 + test + + + + + org.junit.jupiter + junit-jupiter + 5.5.2 + test + + + + org.mockito + mockito-junit-jupiter + 5.3.1 + test + + + + + + + + + + maven-clean-plugin + 3.1.0 + + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.8.0 + + + maven-surefire-plugin + 2.22.1 + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + + maven-site-plugin + 3.7.1 + + + maven-project-info-reports-plugin + 3.0.0 + + + + + diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivities.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivities.java new file mode 100644 index 0000000..3e25b00 --- /dev/null +++ b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivities.java @@ -0,0 +1,10 @@ +package asyncactivitycompletion; + +import io.temporal.activity.ActivityInterface; + +@ActivityInterface +public interface AsyncActivityCompletionActivities { + + String activity(String input); + +} diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivitiesImpl.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivitiesImpl.java new file mode 100644 index 0000000..1a3907d --- /dev/null +++ b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivitiesImpl.java @@ -0,0 +1,43 @@ +package asyncactivitycompletion; + + +import io.temporal.activity.Activity; +import io.temporal.activity.ActivityExecutionContext; +import io.temporal.client.ActivityCompletionClient; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.concurrent.ForkJoinPool; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ForkJoinPool; + + +public class AsyncActivityCompletionActivitiesImpl implements AsyncActivityCompletionActivities { + + private static final Logger logger = LoggerFactory.getLogger(AsyncActivityCompletionActivitiesImpl.class); + + @Override + public String activity(String input) { + logger.info("Activity received input: {}", input); + + + // TODO PART A: + // * Add the code to get the Activity execution context + // * Add the code to get the Task Token from the context + // * Uncomment the code to encode the Task Token in Base64 + // * Log the task token at info level. Hint, you will need to convert the byte[] to a new String + + //byte[] encoded = Base64.getEncoder().encode(taskToken); + + + // TODO Part B: Add a call to the `doNotCompleteOnReturn();` method using + //the `context` object from Part A. This notifies Temporal that the Activity + //should not be completed on return and will be completed asynchronously. + + return "This return value will be ignored."; + } + +} diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorker.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorker.java new file mode 100644 index 0000000..d010558 --- /dev/null +++ b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorker.java @@ -0,0 +1,24 @@ +package asyncactivitycompletion; + +import io.temporal.client.WorkflowClient; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.client.ActivityCompletionClient; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; + +public class AsyncActivityCompletionWorker { + public static void main(String[] args) { + + WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + WorkflowClient client = WorkflowClient.newInstance(service); + WorkerFactory factory = WorkerFactory.newInstance(client); + + Worker worker = factory.newWorker("async-complete"); + + worker.registerWorkflowImplementationTypes(AsyncActivityCompletionWorkflowImpl.class); + + worker.registerActivitiesImplementations(new AsyncActivityCompletionActivitiesImpl()); + + factory.start(); + } +} diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflow.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflow.java new file mode 100644 index 0000000..29510bc --- /dev/null +++ b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflow.java @@ -0,0 +1,14 @@ +package asyncactivitycompletion; + +import io.temporal.workflow.SignalMethod; +import io.temporal.workflow.QueryMethod; +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; + +@WorkflowInterface +public interface AsyncActivityCompletionWorkflow { + + @WorkflowMethod + String workflow(String input); + +} diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflowImpl.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflowImpl.java new file mode 100644 index 0000000..5d79b5e --- /dev/null +++ b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflowImpl.java @@ -0,0 +1,38 @@ +package asyncactivitycompletion; + +import java.time.Duration; + +import org.slf4j.Logger; + +import io.temporal.activity.ActivityOptions; +import io.temporal.workflow.Workflow; + + +public class AsyncActivityCompletionWorkflowImpl implements AsyncActivityCompletionWorkflow { + + public static final Logger logger = Workflow.getLogger(AsyncActivityCompletionWorkflowImpl.class); + + private boolean fulfilled = false; + private String currentState; + + private final ActivityOptions options = + ActivityOptions.newBuilder() + .setStartToCloseTimeout(Duration.ofSeconds(300)) + .build(); + + private final AsyncActivityCompletionActivities activities = + Workflow.newActivityStub(AsyncActivityCompletionActivities.class, options); + + @Override + public String workflow(String input) { + + logger.info("Async Activity Workflow started with input: {}", input); + + + String activityResult = activities.activity(input); + + logger.info("Async Activity Workflow completed with result: {}", activityResult); + + return activityResult; + } +} diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/CompletionClient.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/CompletionClient.java new file mode 100644 index 0000000..1ff7b5f --- /dev/null +++ b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/CompletionClient.java @@ -0,0 +1,33 @@ +package asyncactivitycompletion; + +import java.util.Base64; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import io.temporal.client.WorkflowClient; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.client.ActivityCompletionClient; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class CompletionClient { + public static void main(String[] args) throws ExecutionException, InterruptedException { + + WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + + WorkflowClient client = WorkflowClient.newInstance(service); + + String result = "Asynchronously completed"; + + // TODO Part C: Read in the token from the command line `args[0]` and decode + // the base 64, storing it in a `byte[]`. Hint, invert the call in `AsyncActivityCompletionActivitiesImpl.java` + + ActivityCompletionClient activityCompletionClient = client.newActivityCompletionClient(); + + // TODO Part C: Use the `activityCompletionClient` object above to `complete()` + // the Activity, passing in the Task Token and the result. + + System.exit(0); + } +} diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/Starter.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/Starter.java new file mode 100644 index 0000000..aac2f17 --- /dev/null +++ b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/Starter.java @@ -0,0 +1,29 @@ +package asyncactivitycompletion; + +import java.util.concurrent.CompletableFuture; + +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; + + +public class Starter { + public static void main(String[] args) throws Exception { + + WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + + WorkflowClient client = WorkflowClient.newInstance(service); + + WorkflowOptions options = WorkflowOptions.newBuilder() + .setWorkflowId("async-complete-workflow") + .setTaskQueue("async-complete") + .build(); + + AsyncActivityCompletionWorkflow workflow = client.newWorkflowStub(AsyncActivityCompletionWorkflow.class, options); + + CompletableFuture result = WorkflowClient.execute(workflow::workflow, "Plain text input"); + + System.out.printf("Workflow result: %s\n", result.get()); + System.exit(0); + } +} diff --git a/exercises/async-activity-completion/practice/src/main/resources/logback.xml b/exercises/async-activity-completion/practice/src/main/resources/logback.xml new file mode 100644 index 0000000..8e488b0 --- /dev/null +++ b/exercises/async-activity-completion/practice/src/main/resources/logback.xml @@ -0,0 +1,34 @@ + + + + + + + + + %d{HH:mm:ss.SSS} %-5level - %msg %n + + + + + + + \ No newline at end of file diff --git a/exercises/async-activity-completion/solution/pom.xml b/exercises/async-activity-completion/solution/pom.xml index cd32917..3e360ad 100644 --- a/exercises/async-activity-completion/solution/pom.xml +++ b/exercises/async-activity-completion/solution/pom.xml @@ -5,10 +5,10 @@ 4.0.0 io.temporal.learn - querying-workflows-solution + async-activity-completion-solution 1.0.0-SNAPSHOT - querying workflows (solution) + async activity completion (solution) https://learn.temporal.io/ diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivitiesImpl.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivitiesImpl.java index 9abe712..1ff3a69 100644 --- a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivitiesImpl.java +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivitiesImpl.java @@ -5,7 +5,8 @@ import io.temporal.activity.ActivityExecutionContext; import io.temporal.client.ActivityCompletionClient; - +import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.concurrent.ForkJoinPool; import org.slf4j.Logger; @@ -18,12 +19,6 @@ public class AsyncActivityCompletionActivitiesImpl implements AsyncActivityCompl private static final Logger logger = LoggerFactory.getLogger(AsyncActivityCompletionActivitiesImpl.class); - private final ActivityCompletionClient completionClient; - - AsyncActivityCompletionActivitiesImpl(ActivityCompletionClient completionClient) { - this.completionClient = completionClient; - } - @Override public String activity(String input) { logger.info("Activity received input: {}", input); @@ -34,26 +29,14 @@ public String activity(String input) { // Set a correlation token that can be used to complete the activity asynchronously byte[] taskToken = context.getTaskToken(); - logger.info("Task token: " + new String(taskToken)); + byte[] encoded = Base64.getEncoder().encode(taskToken); + logger.info(new String(encoded)); - /* - * For the example we will use a {@link java.util.concurrent.ForkJoinPool} to execute our - * activity. In real-life applications this could be any service. The composeGreetingAsync - * method is the one that will actually complete workflow action execution. - */ - ForkJoinPool.commonPool().execute(() -> completeActivity(taskToken, input)); context.doNotCompleteOnReturn(); // Since we have set doNotCompleteOnReturn(), the workflow action method return value is // ignored. - return "ignored"; - } - - private void completeActivity(byte[] taskToken, String input){ - String result = "Received: " + input; - - // Complete our workflow activity using ActivityCompletionClient - completionClient.complete(taskToken, result); + return "This return value will be ignored."; } } diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorker.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorker.java index b82ee01..d010558 100644 --- a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorker.java +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorker.java @@ -17,9 +17,7 @@ public static void main(String[] args) { worker.registerWorkflowImplementationTypes(AsyncActivityCompletionWorkflowImpl.class); - ActivityCompletionClient completionClient = client.newActivityCompletionClient(); - - worker.registerActivitiesImplementations(new AsyncActivityCompletionActivitiesImpl(completionClient)); + worker.registerActivitiesImplementations(new AsyncActivityCompletionActivitiesImpl()); factory.start(); } diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflowImpl.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflowImpl.java index b864126..5d79b5e 100644 --- a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflowImpl.java +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflowImpl.java @@ -17,7 +17,7 @@ public class AsyncActivityCompletionWorkflowImpl implements AsyncActivityComplet private final ActivityOptions options = ActivityOptions.newBuilder() - .setStartToCloseTimeout(Duration.ofSeconds(5)) + .setStartToCloseTimeout(Duration.ofSeconds(300)) .build(); private final AsyncActivityCompletionActivities activities = diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/CompletionClient.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/CompletionClient.java index 5b20b5a..a11c796 100644 --- a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/CompletionClient.java +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/CompletionClient.java @@ -1,10 +1,12 @@ package asyncactivitycompletion; +import java.util.Base64; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import io.temporal.client.WorkflowClient; import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.client.ActivityCompletionClient; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -15,18 +17,13 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); WorkflowClient client = WorkflowClient.newInstance(service); + + String result = "Asynchronously completed"; + byte[] taskToken = Base64.getDecoder().decode(args[0]); - AsyncActivityCompletionWorkflow workflow = client.newWorkflowStub(AsyncActivityCompletionWorkflow.class, "async-complete-workflow"); - - /* - * Here we use {@link io.temporal.client.WorkflowClient} to execute our workflow asynchronously. - * It gives us back a {@link java.util.concurrent.CompletableFuture}. We can then call its get - * method to block and wait until a result is available. - */ - CompletableFuture result = WorkflowClient.execute(workflow::workflow, "Plain text"); - - // Wait for workflow execution to complete and display its results. - System.out.println("Received result" + result.get()); + ActivityCompletionClient activityCompletionClient = client.newActivityCompletionClient(); + + activityCompletionClient.complete(taskToken, result); System.exit(0); } From 3990698a66e1f061819597d388cb2e23d3987abd Mon Sep 17 00:00:00 2001 From: Mason Egger Date: Mon, 8 Apr 2024 10:32:31 -0500 Subject: [PATCH 3/4] Updating to use Translation Workflow --- exercises/async-activity-completion/README.md | 62 ++++---- .../practice/pom.xml | 4 +- .../AsyncActivityCompletionActivities.java | 10 -- ...AsyncActivityCompletionActivitiesImpl.java | 43 ----- .../AsyncActivityCompletionWorkflow.java | 14 -- .../AsyncActivityCompletionWorkflowImpl.java | 38 ----- .../java/asyncactivitycompletion/Starter.java | 20 ++- .../TranslationActivities.java | 12 ++ .../TranslationActivitiesImpl.java | 127 +++++++++++++++ .../TranslationWorker.java} | 9 +- .../TranslationWorkflow.java | 14 ++ .../TranslationWorkflowImpl.java | 42 +++++ ...java => VerifyAndCompleteTranslation.java} | 21 ++- .../model/TranslationActivityInput.java | 59 +++++++ .../model/TranslationActivityOutput.java | 21 +++ .../model/TranslationWorkflowInput.java | 31 ++++ .../model/TranslationWorkflowOutput.java | 26 +++ .../TranslationActivitiesTest.java | 61 +++++++ .../TranslationWorkflowMockTest.java | 46 ++++++ .../TranslationWorkflowTest.java | 38 +++++ .../AsyncActivityCompletionActivities.java | 10 -- ...AsyncActivityCompletionActivitiesImpl.java | 42 ----- .../AsyncActivityCompletionWorkflow.java | 14 -- .../AsyncActivityCompletionWorkflowImpl.java | 38 ----- .../java/asyncactivitycompletion/Starter.java | 20 ++- .../TranslationActivities.java | 12 ++ .../TranslationActivitiesImpl.java | 125 +++++++++++++++ .../TranslationWorker.java} | 9 +- .../TranslationWorkflow.java | 14 ++ .../TranslationWorkflowImpl.java | 42 +++++ ...java => VerifyAndCompleteTranslation.java} | 14 +- .../model/TranslationActivityInput.java | 59 +++++++ .../model/TranslationActivityOutput.java | 21 +++ .../model/TranslationWorkflowInput.java | 31 ++++ .../model/TranslationWorkflowOutput.java | 26 +++ .../TranslationActivitiesTest.java | 61 +++++++ .../TranslationWorkflowMockTest.java | 46 ++++++ .../TranslationWorkflowTest.java | 38 +++++ utilities/microservice/pom.xml | 82 ++++++++++ .../java/translationapi/Microservice.java | 149 ++++++++++++++++++ 40 files changed, 1273 insertions(+), 278 deletions(-) delete mode 100644 exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivities.java delete mode 100644 exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivitiesImpl.java delete mode 100644 exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflow.java delete mode 100644 exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflowImpl.java create mode 100644 exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/TranslationActivities.java create mode 100644 exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/TranslationActivitiesImpl.java rename exercises/async-activity-completion/{solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorker.java => practice/src/main/java/asyncactivitycompletion/TranslationWorker.java} (59%) create mode 100644 exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/TranslationWorkflow.java create mode 100644 exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/TranslationWorkflowImpl.java rename exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/{CompletionClient.java => VerifyAndCompleteTranslation.java} (57%) create mode 100644 exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/model/TranslationActivityInput.java create mode 100644 exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/model/TranslationActivityOutput.java create mode 100644 exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/model/TranslationWorkflowInput.java create mode 100644 exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/model/TranslationWorkflowOutput.java create mode 100644 exercises/async-activity-completion/practice/src/test/java/translationworkflow/TranslationActivitiesTest.java create mode 100644 exercises/async-activity-completion/practice/src/test/java/translationworkflow/TranslationWorkflowMockTest.java create mode 100644 exercises/async-activity-completion/practice/src/test/java/translationworkflow/TranslationWorkflowTest.java delete mode 100644 exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivities.java delete mode 100644 exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivitiesImpl.java delete mode 100644 exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflow.java delete mode 100644 exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflowImpl.java create mode 100644 exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/TranslationActivities.java create mode 100644 exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/TranslationActivitiesImpl.java rename exercises/async-activity-completion/{practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorker.java => solution/src/main/java/asyncactivitycompletion/TranslationWorker.java} (59%) create mode 100644 exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/TranslationWorkflow.java create mode 100644 exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/TranslationWorkflowImpl.java rename exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/{CompletionClient.java => VerifyAndCompleteTranslation.java} (71%) create mode 100644 exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/model/TranslationActivityInput.java create mode 100644 exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/model/TranslationActivityOutput.java create mode 100644 exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/model/TranslationWorkflowInput.java create mode 100644 exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/model/TranslationWorkflowOutput.java create mode 100644 exercises/async-activity-completion/solution/src/test/java/translationworkflow/TranslationActivitiesTest.java create mode 100644 exercises/async-activity-completion/solution/src/test/java/translationworkflow/TranslationWorkflowMockTest.java create mode 100644 exercises/async-activity-completion/solution/src/test/java/translationworkflow/TranslationWorkflowTest.java create mode 100644 utilities/microservice/pom.xml create mode 100644 utilities/microservice/src/main/java/translationapi/Microservice.java diff --git a/exercises/async-activity-completion/README.md b/exercises/async-activity-completion/README.md index 550a2a7..0c59376 100644 --- a/exercises/async-activity-completion/README.md +++ b/exercises/async-activity-completion/README.md @@ -1,4 +1,4 @@ -# Exercise 3: Asynchronous Activity Completion +# Exercise 4: Asynchronous Activity Completion During this exercise, you will: @@ -16,16 +16,16 @@ the code. | Command | Action | | :---------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ex3` | Change to Exercise 3 Practice Directory | -| `ex3s` | Change to Exercise 3 Solution Directory | -| `ex3w` | Execute the Exercise 3 Worker. Must be within the appropriate directory for this to succeed. (either `practice` or `solution`) | -| `ex3st` | Execute the Exercise 3 Starter. Must be within the appropriate directory for this to succeed. (either `practice` or `solution`) | -| `ex3c TASK_TOKEN` | Complete the Exercise 3 Activity, passing in the Task Token. Must be within the appropriate directory for this to succeed. (either `practice` or `solution`) | +| `ex4` | Change to Exercise 3 Practice Directory | +| `ex4s` | Change to Exercise 3 Solution Directory | +| `ex4w` | Execute the Exercise 3 Worker. Must be within the appropriate directory for this to succeed. (either `practice` or `solution`) | +| `ex4st` | Execute the Exercise 3 Starter. Must be within the appropriate directory for this to succeed. (either `practice` or `solution`) | +| `ex4c TASK_TOKEN TRANSLATION` | Complete the Exercise 3 Activity, passing in the Task Token and verified translation. Must be within the appropriate directory for this to succeed. (either `practice` or `solution`) | ## Part A: Retrieving the Task Token -1. Open the `AsyncActivityCompletionActivitiesImpl.java` file in the `src/main/java/asyncactivitycompletion` subdirectory. -1. In the `activity()` definition, add the line `ActivityExecutionContext context = Activity.getExecutionContext();` to get the current Execution Context of the Activity. +1. Open the `TranslationActivitiesActivitiesImpl.java` file in the `src/main/java/asyncactivitycompletion` subdirectory. +1. In the `translateTerm()` method, add the line `ActivityExecutionContext context = Activity.getExecutionContext();` to get the current Execution Context of the Activity. 1. Add a call to `getTaskToken()` from the `context` object above and store it in a `byte []` named `taskToken` 1. Uncomment the line below to convert the `taskToken` byte array to Base64. 1. Log the Task Token at `info` level using the `logger` object for later use. You will need to convert this to a new String. @@ -33,49 +33,53 @@ the code. ## Part B: Set Your Activity to `doNotCompleteOnReturn()` -1. Continue editing the same Activity definition in the `AsyncActivityCompletionImpl.java` file. - 1. Add a call to the `doNotCompleteOnReturn();` method using the `context` object from Part A. This notifies - Temporal that the Activity should not be completed on return and will be completed asynchronously. +1. Continue editing the same Activity definition in the `TranslationActivitiesImpl.java` file. + 1. Add a call to the `doNotCompleteOnReturn();` method at the end of the `translateTerm()` method using the `context` object from Part A. This notifies Temporal that the Activity should not be completed on return and will be completed asynchronously. 1. Save the file. -1. Open the `AsyncActivityCompletionWorkflowImpl.java` file in the `src/main/java/asyncactivitycompletion` subdirectory. +1. Open the `TranslationWorkflowImpl.java` file in the `src/main/java/asyncactivitycompletion` subdirectory. 1. Observe that the Workflow's `StartToCloseTimeout` has been lengthened to 300 seconds for this exercise. Activities can still time out if they are running in the background. 1. Close this file without making any changes. ## Part C: Configure a Client to send CompleteActivity -1. Open the `CompletionClient.java` file in the `src/main/java/asyncactivitycompletion` subdirectory. -1. The first thing you'll need to do is add some way of supplying the `taskToken` specific to the Activity you are trying to complete at runtime. In a production system, you might store and retrieve the token from a database, but for now, you can configure this Client to accept it as a command line arguement. Read in the token from the command line `args[0]` and decode the base 64, storing it in a `byte[]`. Hint, invert the call in `AsyncActivityCompletionActivitiesImpl.java` -1. Add a call to the `doNotCompleteOnReturn();` method using the `context` object from Part A. This notifies Temporal that the Activity should not be completed on return and will be completed asynchronously. +1. Open the `VerifyAndCompleteTranslation.java` file in the `src/main/java/asyncactivitycompletion` subdirectory. +1. The first thing you'll need to do is add some way of supplying the `taskToken` and translated text specific to the Activity you are trying to complete at runtime. In a production system, you might store and retrieve the token from a database, but for now, you can configure this Client to accept it as a command line argument. Both the `taskToken` and `translation` can be found in the logs of the Worker. + 1. Read in the token from the command line `args[0]` and decode the base 64, storing it in a `byte[]`. Hint, invert the call in `TranslationActivitiesImpl.java` + 1. Read in the Translation of the phrase that was outputted in the Worker logs as `args[1]` +1. Add a call to the `complete();` method using the `activityCompletionClient`. This call should provide the task token and result of the Activity. This notifies Temporal that the Activity should not be completed on return and will be completed asynchronously. + 1. The result has already been instantiated into a `TranslationActivityOutput` object for you. 1. Save the file. ## Part D: Running the Workflow and Completing it Asynchronously -At this point, you can run your Workflow. As with the Signal Exercise, the Workflow will not return on its own -- in this case, because your Activity is set to complete asynchronously, and will wait to receive `CompleteActivity()`. +At this point, you can run your Workflow. As with the Signal Exercise, the Workflow will not return on its own -- in this case, because your Activity is set to complete asynchronously, and will wait to receive `complete()`. 1. In one terminal, navigate to the `practice` subdirectory - 1. If you're in the GitPod environment you can instead run `ex3` + 1. If you're in the GitPod environment you can instead run `ex4` 1. Compile the code using `mvn clean compile` - 1. Run the worker using `mvn exec:java -Dexec.mainClas='asyncactivitycompletion.AsyncActivityCompletionWorker'`. - 1. If you're in the GitPod environment you can instead run `ex3w` + 1. Run the worker using `mvn exec:java -Dexec.mainClas='asyncactivitycompletion.TranslationWorker'`. + 1. If you're in the GitPod environment you can instead run `ex4w` 1. In another terminal, navigate to the `practice` subdirectory - 1. If you're in the GitPod environment you can instead run `ex3` - 1. Invoke the Workflow using `mvn exec:java -Dexec.mainClass='asyncactivitycompletion.Starter'` - If you're in the GitPod environment you can instead run `ex3st` + 1. If you're in the GitPod environment you can instead run `ex4` + 1. Invoke the Workflow using `mvn exec:java -Dexec.mainClass='asyncactivitycompletion.Starter' -Dexec.args="Mason de"` + If you're in the GitPod environment you can instead run `ex4st Mason de`, replacing the name with yours 1. Navigate back to the Worker terminal. Your work will produce some logging, eventually including your `taskToken`: ``` -12:06:58.679 INFO - Async Activity Workflow started with input: Plain text input -12:06:58.684 INFO - Activity received input: Plain text input -CiQ3NGE5NGEyMC05NjE2LTQ3YWEtOTMwMS01YmQ4MzFlZTE4M2ISF2FzeW5jLWNvbXBsZXRlLXdvcmtmbG93GiQzZWY3YjE0YS1lMzdiLTQ3NGItOTIyMS1iN2UyYjM3MGU0MGMgBSgBMiRhMzMxN2E1My0xMzMwLTM4N2YtYTBiNC1kN2RjMjAzNjAwY2NCCEFjdGl2aXR5SggIARC+gEAYAQ== +10:28:40.579 INFO - sayHelloGoodbye Workflow Invoked with input name: Mason language code: de +10:28:40.614 INFO - translateTerm Activity received input: asyncactivitycompletion.model.TranslationActivityInput@394250e6 +10:28:40.614 INFO - TASK TOKEN: CiQ1NzVkNTNlYi1lM2UyLTRmNmEtODFjMy04ZmY0NmJiYjJjOWYSFHRyYW5zbGF0aW9uLXdvcmtmbG93GiQ0OWQ5NjgyOC1iYmJkLTQ5MjMtOTE4Mi00MWY2YmFlNjI4YzEgBSgBMiRlNGJmZmJhMC1jNGJhLTM1MDgtYThkYS01MjgwYjNjMzVkZmJCDVRyYW5zbGF0ZVRlcm1KCAgBEJuAQBgB +10:28:40.614 INFO - [ACTIVITY INVOKED] translateTerm invoked with input term: hello language code: de +10:28:40.642 INFO - Translation Service returned: Hallo ``` 1. You can now use this token to send a `complete()` call from another client. In another terminal, navigate to the `practice` subdirectory - 1. If you're in the GitPod environment you can instead run `ex3` - 1. Run the command `mvn exec:java -Dexec.mainClass='asyncactivitycompletion.CompletionClient' -Dexec.args="TASK_TOKEN"` with your Task Token replacing `TASK_TOKEN` with your Task Token to complete the Activity. - 1. If you're in the GitPod environment you can instead run `ex3c TASK_TOKEN` replacing `TASK_TOKEN` with your Task Token to complete the Activity. + 1. If you're in the GitPod environment you can instead run `ex4` + 1. Run the command `mvn exec:java -Dexec.mainClass='asyncactivitycompletion.VerifyAndCompleteTranslatin' -Dexec.args="TASK_TOKEN TRANSLATION"` with your Task Token replacing `TASK_TOKEN` with your Task Token to complete the Activity and replacing `TRANSLATION` with the results from the translation service + 1. If you're in the GitPod environment you can instead run `ex4c TASK_TOKEN TRANSLATION` replacing `TASK_TOKEN` with your Task Token to complete the Activity and replacing `TRANSLATION` with the results from the translation service 1. This will cause your Activity to return and your Workflow to successfully complete. The terminal running your Worker process should now show ``` - 12:07:43.689 INFO - Async Activity Workflow completed with result: Asynchronously completed + 12:07:43.689 INFO - Workflow Completed ``` ### This is the end of the exercise. diff --git a/exercises/async-activity-completion/practice/pom.xml b/exercises/async-activity-completion/practice/pom.xml index 3e360ad..52698ac 100644 --- a/exercises/async-activity-completion/practice/pom.xml +++ b/exercises/async-activity-completion/practice/pom.xml @@ -5,10 +5,10 @@ 4.0.0 io.temporal.learn - async-activity-completion-solution + async-activity-completion 1.0.0-SNAPSHOT - async activity completion (solution) + async activity completion https://learn.temporal.io/ diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivities.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivities.java deleted file mode 100644 index 3e25b00..0000000 --- a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivities.java +++ /dev/null @@ -1,10 +0,0 @@ -package asyncactivitycompletion; - -import io.temporal.activity.ActivityInterface; - -@ActivityInterface -public interface AsyncActivityCompletionActivities { - - String activity(String input); - -} diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivitiesImpl.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivitiesImpl.java deleted file mode 100644 index 1a3907d..0000000 --- a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivitiesImpl.java +++ /dev/null @@ -1,43 +0,0 @@ -package asyncactivitycompletion; - - -import io.temporal.activity.Activity; -import io.temporal.activity.ActivityExecutionContext; -import io.temporal.client.ActivityCompletionClient; - -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.concurrent.ForkJoinPool; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.ForkJoinPool; - - -public class AsyncActivityCompletionActivitiesImpl implements AsyncActivityCompletionActivities { - - private static final Logger logger = LoggerFactory.getLogger(AsyncActivityCompletionActivitiesImpl.class); - - @Override - public String activity(String input) { - logger.info("Activity received input: {}", input); - - - // TODO PART A: - // * Add the code to get the Activity execution context - // * Add the code to get the Task Token from the context - // * Uncomment the code to encode the Task Token in Base64 - // * Log the task token at info level. Hint, you will need to convert the byte[] to a new String - - //byte[] encoded = Base64.getEncoder().encode(taskToken); - - - // TODO Part B: Add a call to the `doNotCompleteOnReturn();` method using - //the `context` object from Part A. This notifies Temporal that the Activity - //should not be completed on return and will be completed asynchronously. - - return "This return value will be ignored."; - } - -} diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflow.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflow.java deleted file mode 100644 index 29510bc..0000000 --- a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflow.java +++ /dev/null @@ -1,14 +0,0 @@ -package asyncactivitycompletion; - -import io.temporal.workflow.SignalMethod; -import io.temporal.workflow.QueryMethod; -import io.temporal.workflow.WorkflowInterface; -import io.temporal.workflow.WorkflowMethod; - -@WorkflowInterface -public interface AsyncActivityCompletionWorkflow { - - @WorkflowMethod - String workflow(String input); - -} diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflowImpl.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflowImpl.java deleted file mode 100644 index 5d79b5e..0000000 --- a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflowImpl.java +++ /dev/null @@ -1,38 +0,0 @@ -package asyncactivitycompletion; - -import java.time.Duration; - -import org.slf4j.Logger; - -import io.temporal.activity.ActivityOptions; -import io.temporal.workflow.Workflow; - - -public class AsyncActivityCompletionWorkflowImpl implements AsyncActivityCompletionWorkflow { - - public static final Logger logger = Workflow.getLogger(AsyncActivityCompletionWorkflowImpl.class); - - private boolean fulfilled = false; - private String currentState; - - private final ActivityOptions options = - ActivityOptions.newBuilder() - .setStartToCloseTimeout(Duration.ofSeconds(300)) - .build(); - - private final AsyncActivityCompletionActivities activities = - Workflow.newActivityStub(AsyncActivityCompletionActivities.class, options); - - @Override - public String workflow(String input) { - - logger.info("Async Activity Workflow started with input: {}", input); - - - String activityResult = activities.activity(input); - - logger.info("Async Activity Workflow completed with result: {}", activityResult); - - return activityResult; - } -} diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/Starter.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/Starter.java index aac2f17..af2f975 100644 --- a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/Starter.java +++ b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/Starter.java @@ -1,11 +1,10 @@ package asyncactivitycompletion; -import java.util.concurrent.CompletableFuture; - import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowOptions; import io.temporal.serviceclient.WorkflowServiceStubs; - +import asyncactivitycompletion.model.TranslationWorkflowInput; +import asyncactivitycompletion.model.TranslationWorkflowOutput; public class Starter { public static void main(String[] args) throws Exception { @@ -15,15 +14,20 @@ public static void main(String[] args) throws Exception { WorkflowClient client = WorkflowClient.newInstance(service); WorkflowOptions options = WorkflowOptions.newBuilder() - .setWorkflowId("async-complete-workflow") - .setTaskQueue("async-complete") + .setWorkflowId("translation-workflow") + .setTaskQueue("translation-tasks") .build(); - AsyncActivityCompletionWorkflow workflow = client.newWorkflowStub(AsyncActivityCompletionWorkflow.class, options); + TranslationWorkflow workflow = client.newWorkflowStub(TranslationWorkflow.class, options); + + String name = args[0]; + String languageCode = args[1]; + + TranslationWorkflowInput input = new TranslationWorkflowInput(name, languageCode); - CompletableFuture result = WorkflowClient.execute(workflow::workflow, "Plain text input"); + TranslationWorkflowOutput greeting = workflow.sayHelloGoodbye(input); - System.out.printf("Workflow result: %s\n", result.get()); + System.out.printf("Workflow result: %s\n", greeting); System.exit(0); } } diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/TranslationActivities.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/TranslationActivities.java new file mode 100644 index 0000000..8b4b3f6 --- /dev/null +++ b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/TranslationActivities.java @@ -0,0 +1,12 @@ +package asyncactivitycompletion; + +import io.temporal.activity.ActivityInterface; +import asyncactivitycompletion.model.TranslationActivityInput; +import asyncactivitycompletion.model.TranslationActivityOutput; + +@ActivityInterface +public interface TranslationActivities { + + TranslationActivityOutput translateTerm(TranslationActivityInput input); + +} diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/TranslationActivitiesImpl.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/TranslationActivitiesImpl.java new file mode 100644 index 0000000..b1378bc --- /dev/null +++ b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/TranslationActivitiesImpl.java @@ -0,0 +1,127 @@ +package asyncactivitycompletion; + +import java.net.HttpURLConnection; +import java.net.ProtocolException; +import java.net.URI; +import java.net.URL; +import java.net.URLEncoder; +import java.util.Base64; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; +import io.temporal.activity.Activity; +import io.temporal.failure.ApplicationFailure; +import java.net.HttpURLConnection; +import io.temporal.activity.ActivityExecutionContext; +import io.temporal.client.ActivityCompletionClient; +import io.temporal.client.WorkflowClient; +import io.temporal.serviceclient.WorkflowServiceStubs; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.Base64; +import asyncactivitycompletion.model.TranslationActivityInput; +import asyncactivitycompletion.model.TranslationActivityOutput; + +public class TranslationActivitiesImpl implements TranslationActivities { + + private static final Logger logger = LoggerFactory.getLogger(TranslationActivitiesImpl.class); + + @Override + public TranslationActivityOutput translateTerm(TranslationActivityInput input) { + + logger.info("translateTerm Activity received input: {}", input); + + // TODO PART A: Add the call to `getExecutionContext()` + + // TODO: PART A: Get the task token using the context object defined in the previous step. + // The taskToken should be a byte[] + + // TODO: PART A: Uncomment me + //String encoded = new String(Base64.getEncoder().encode(taskToken)); + + // TODO: PART A: Log the encoded task token + + String term = input.getTerm(); + String lang = input.getLanguageCode(); + + logger.info("[ACTIVITY INVOKED] translateTerm invoked with input term: {} language code: {}", + term, lang); + + // construct the URL, with supplied input parameters, for accessing the + // microservice + URL url = null; + try { + String baseUrl = "http://localhost:9999/translate?term=%s&lang=%s"; + url = URI.create( + String.format(baseUrl, + URLEncoder.encode(term, "UTF-8"), + URLEncoder.encode(lang, "UTF-8"))) + .toURL(); + } catch (IOException e) { + logger.error(e.getMessage()); + throw Activity.wrap(e); + } + + TranslationActivityOutput result = new TranslationActivityOutput(); + + try { + // Open a connection to the URL + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + + // Set the HTTP request method (GET, POST, etc.) + connection.setRequestMethod("GET"); + + // Get the response code + int responseCode = connection.getResponseCode(); + + if (responseCode == HttpURLConnection.HTTP_OK) { + // If the response code is 200 (HTTP OK), the request was successful + BufferedReader reader = new BufferedReader( + new InputStreamReader(connection.getInputStream())); + String line; + StringBuilder response = new StringBuilder(); + + while ((line = reader.readLine()) != null) { + response.append(line); + } + + reader.close(); + + connection.disconnect(); + result.setTranslation(response.toString()); + + } else { + // If the response code is not 200, there was an error + BufferedReader errorReader = new BufferedReader( + new InputStreamReader(connection.getErrorStream())); + String line; + StringBuilder errorResponse = new StringBuilder(); + + while ((line = errorReader.readLine()) != null) { + errorResponse.append(line); + } + + errorReader.close(); + + connection.disconnect(); + // Print the error response + throw ApplicationFailure.newFailure(errorResponse.toString(), IOException.class.getName()); + } + + } catch (IOException e) { + logger.error(e.getMessage()); + throw Activity.wrap(e); + } + + logger.info("Translation Service returned: {}", result.getTranslation()); + + // TODO: PART B: Use the `context` object from above to call `doNotCompleteOnReturn()`; + // This notifies Temporal that the Activity should not be completed on return and will be completed asynchronously. + + + // Since we have set doNotCompleteOnReturn(), the return value is ignored. + return new TranslationActivityOutput("this will be ignored"); + } + +} diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorker.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/TranslationWorker.java similarity index 59% rename from exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorker.java rename to exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/TranslationWorker.java index d010558..44a1e4b 100644 --- a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorker.java +++ b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/TranslationWorker.java @@ -2,22 +2,21 @@ import io.temporal.client.WorkflowClient; import io.temporal.serviceclient.WorkflowServiceStubs; -import io.temporal.client.ActivityCompletionClient; import io.temporal.worker.Worker; import io.temporal.worker.WorkerFactory; -public class AsyncActivityCompletionWorker { +public class TranslationWorker { public static void main(String[] args) { WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); WorkflowClient client = WorkflowClient.newInstance(service); WorkerFactory factory = WorkerFactory.newInstance(client); - Worker worker = factory.newWorker("async-complete"); + Worker worker = factory.newWorker("translation-tasks"); - worker.registerWorkflowImplementationTypes(AsyncActivityCompletionWorkflowImpl.class); + worker.registerWorkflowImplementationTypes(TranslationWorkflowImpl.class); - worker.registerActivitiesImplementations(new AsyncActivityCompletionActivitiesImpl()); + worker.registerActivitiesImplementations(new TranslationActivitiesImpl()); factory.start(); } diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/TranslationWorkflow.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/TranslationWorkflow.java new file mode 100644 index 0000000..130133e --- /dev/null +++ b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/TranslationWorkflow.java @@ -0,0 +1,14 @@ +package asyncactivitycompletion; + +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; +import asyncactivitycompletion.model.TranslationWorkflowInput; +import asyncactivitycompletion.model.TranslationWorkflowOutput; + +@WorkflowInterface +public interface TranslationWorkflow { + + @WorkflowMethod + TranslationWorkflowOutput sayHelloGoodbye(TranslationWorkflowInput input); + +} diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/TranslationWorkflowImpl.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/TranslationWorkflowImpl.java new file mode 100644 index 0000000..73e007b --- /dev/null +++ b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/TranslationWorkflowImpl.java @@ -0,0 +1,42 @@ +package asyncactivitycompletion; + +import java.time.Duration; +import org.slf4j.Logger; + +import io.temporal.activity.ActivityOptions; +import io.temporal.workflow.Workflow; +import asyncactivitycompletion.model.TranslationActivityInput; +import asyncactivitycompletion.model.TranslationActivityOutput; +import asyncactivitycompletion.model.TranslationWorkflowInput; +import asyncactivitycompletion.model.TranslationWorkflowOutput; + +public class TranslationWorkflowImpl implements TranslationWorkflow { + + public static final Logger logger = Workflow.getLogger(TranslationWorkflowImpl.class); + + private final ActivityOptions options = + ActivityOptions.newBuilder() + .setStartToCloseTimeout(Duration.ofSeconds(300)) + .build(); + + private final TranslationActivities activities = + Workflow.newActivityStub(TranslationActivities.class, options); + + @Override + public TranslationWorkflowOutput sayHelloGoodbye(TranslationWorkflowInput input) { + String name = input.getName(); + String languageCode = input.getLanguageCode(); + + logger.info("sayHelloGoodbye Workflow Invoked with input name: {} language code: {}", name, + languageCode); + + logger.debug("Preparing to translate Hello into languageCode: {}", languageCode); + TranslationActivityInput helloInput = new TranslationActivityInput("hello", languageCode); + TranslationActivityOutput helloResult = activities.translateTerm(helloInput); + String helloMessage = helloResult.getTranslation() + ", " + name; + + logger.info("Workflow completed"); + + return new TranslationWorkflowOutput(helloMessage); + } +} diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/CompletionClient.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/VerifyAndCompleteTranslation.java similarity index 57% rename from exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/CompletionClient.java rename to exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/VerifyAndCompleteTranslation.java index 1ff7b5f..b6e9871 100644 --- a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/CompletionClient.java +++ b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/VerifyAndCompleteTranslation.java @@ -8,26 +8,31 @@ import io.temporal.serviceclient.WorkflowServiceStubs; import io.temporal.client.ActivityCompletionClient; +import asyncactivitycompletion.model.TranslationActivityOutput; + import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -public class CompletionClient { +public class VerifyAndCompleteTranslation { public static void main(String[] args) throws ExecutionException, InterruptedException { WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); WorkflowClient client = WorkflowClient.newInstance(service); - String result = "Asynchronously completed"; - - // TODO Part C: Read in the token from the command line `args[0]` and decode - // the base 64, storing it in a `byte[]`. Hint, invert the call in `AsyncActivityCompletionActivitiesImpl.java` + // TODO: PART C: Read in the taskToken from args[0] and decode it from Base64 + byte[] taskToken = Base64.getDecoder().decode(args[0]); + + // TODO: PART C: Get the translated text from args[1] + ActivityCompletionClient activityCompletionClient = client.newActivityCompletionClient(); + + TranslationActivityOutput result = new TranslationActivityOutput(result); - // TODO Part C: Use the `activityCompletionClient` object above to `complete()` - // the Activity, passing in the Task Token and the result. + // TODO: PART C: Call the `complete()` method using the `activityCompletionClient` object + // from above. The call should contain the `taskToken` and the `result`. System.exit(0); } -} +} \ No newline at end of file diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/model/TranslationActivityInput.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/model/TranslationActivityInput.java new file mode 100644 index 0000000..4b87e58 --- /dev/null +++ b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/model/TranslationActivityInput.java @@ -0,0 +1,59 @@ +package asyncactivitycompletion.model; + +import java.util.Objects; + +public class TranslationActivityInput { + + private String term; + private String languageCode; + + public TranslationActivityInput() { + } + + public TranslationActivityInput(String term, String languageCode) { + this.term = term; + this.languageCode = languageCode; + } + + public String getTerm() { + return term; + } + + public void setTerm(String term) { + this.term = term; + } + + public String getLanguageCode() { + return languageCode; + } + + public void setLanguageCode(String languageCode) { + this.languageCode = languageCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final TranslationActivityInput other = (TranslationActivityInput) obj; + if (!Objects.equals(this.term, other.term)) { + return false; + } + return Objects.equals(this.languageCode, other.languageCode); + } + + @Override + public int hashCode() { + int hash = 3; + hash = 53 * hash + Objects.hashCode(this.term); + hash = 53 * hash + Objects.hashCode(this.languageCode); + return hash; + } +} diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/model/TranslationActivityOutput.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/model/TranslationActivityOutput.java new file mode 100644 index 0000000..71c6bc4 --- /dev/null +++ b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/model/TranslationActivityOutput.java @@ -0,0 +1,21 @@ +package asyncactivitycompletion.model; + +public class TranslationActivityOutput { + + private String translation; + + public TranslationActivityOutput() { + } + + public TranslationActivityOutput(String translation) { + this.translation = translation; + } + + public String getTranslation() { + return translation; + } + + public void setTranslation(String translation) { + this.translation = translation; + } +} diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/model/TranslationWorkflowInput.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/model/TranslationWorkflowInput.java new file mode 100644 index 0000000..0737015 --- /dev/null +++ b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/model/TranslationWorkflowInput.java @@ -0,0 +1,31 @@ +package asyncactivitycompletion.model; + +public class TranslationWorkflowInput { + + private String name; + private String languageCode; + + public TranslationWorkflowInput() { + } + + public TranslationWorkflowInput(String name, String languageCode) { + this.name = name; + this.languageCode = languageCode; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLanguageCode() { + return languageCode; + } + + public void setLanguageCode(String languageCode) { + this.languageCode = languageCode; + } +} diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/model/TranslationWorkflowOutput.java b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/model/TranslationWorkflowOutput.java new file mode 100644 index 0000000..e12c2d8 --- /dev/null +++ b/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/model/TranslationWorkflowOutput.java @@ -0,0 +1,26 @@ +package asyncactivitycompletion.model; + +public class TranslationWorkflowOutput { + + private String helloMessage; + + public TranslationWorkflowOutput() { + } + + public TranslationWorkflowOutput(String helloMessage) { + this.helloMessage = helloMessage; + } + + public String getHelloMessage() { + return helloMessage; + } + + public void setHelloMessage(String helloMessage) { + this.helloMessage = helloMessage; + } + + @Override + public String toString() { + return this.helloMessage; + } +} diff --git a/exercises/async-activity-completion/practice/src/test/java/translationworkflow/TranslationActivitiesTest.java b/exercises/async-activity-completion/practice/src/test/java/translationworkflow/TranslationActivitiesTest.java new file mode 100644 index 0000000..10dfa8c --- /dev/null +++ b/exercises/async-activity-completion/practice/src/test/java/translationworkflow/TranslationActivitiesTest.java @@ -0,0 +1,61 @@ +package translationworkflow; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.temporal.failure.ActivityFailure; +import io.temporal.testing.TestActivityEnvironment; +import translationworkflow.model.TranslationActivityInput; +import translationworkflow.model.TranslationActivityOutput; + +public class TranslationActivitiesTest { + + private TestActivityEnvironment testEnvironment; + private TranslationActivities activity; + + @BeforeEach + public void init() { + testEnvironment = TestActivityEnvironment.newInstance(); + testEnvironment.registerActivitiesImplementations(new TranslationActivitiesImpl()); + activity = testEnvironment.newActivityStub(TranslationActivities.class); + } + + @AfterEach + public void destroy() { + testEnvironment.close(); + } + + @Test + public void testSuccessfulTranslateActivityHelloGerman() { + TranslationActivityInput input = new TranslationActivityInput("hello", "de"); + TranslationActivityOutput output = activity.translateTerm(input); + assertEquals("Hallo", output.getTranslation()); + } + + @Test + public void testSuccessfulTranslateActivityHelloLatvian() { + TranslationActivityInput input = new TranslationActivityInput("goodbye", "lv"); + TranslationActivityOutput output = activity.translateTerm(input); + assertEquals("Ardievu", output.getTranslation()); + } + + @Test + public void testFailedTranslateActivityBadLanguageCode() { + TranslationActivityInput input = new TranslationActivityInput("goodbye", "xq"); + + // Assert that an error was thrown and it was an Activity Failure + Exception exception = assertThrows(ActivityFailure.class, () -> { + TranslationActivityOutput output = activity.translateTerm(input); + }); + + // Assert that the error contains the expected message + assertTrue(exception.getMessage().contains( + "Invalid language code"), + "expected error message"); + } +} diff --git a/exercises/async-activity-completion/practice/src/test/java/translationworkflow/TranslationWorkflowMockTest.java b/exercises/async-activity-completion/practice/src/test/java/translationworkflow/TranslationWorkflowMockTest.java new file mode 100644 index 0000000..8f6215e --- /dev/null +++ b/exercises/async-activity-completion/practice/src/test/java/translationworkflow/TranslationWorkflowMockTest.java @@ -0,0 +1,46 @@ +package translationworkflow; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.temporal.testing.TestWorkflowEnvironment; +import io.temporal.testing.TestWorkflowExtension; +import io.temporal.worker.Worker; +import translationworkflow.model.TranslationActivityInput; +import translationworkflow.model.TranslationActivityOutput; +import translationworkflow.model.TranslationWorkflowInput; +import translationworkflow.model.TranslationWorkflowOutput; +import static org.mockito.Mockito.*; + +public class TranslationWorkflowMockTest { + + @RegisterExtension + public static final TestWorkflowExtension testWorkflowExtension = + TestWorkflowExtension.newBuilder() + .setWorkflowTypes(TranslationWorkflowImpl.class) + .setDoNotStart(true) + .build(); + + @Test + public void testSuccessfulTranslationWithMocks(TestWorkflowEnvironment testEnv, Worker worker, + TranslationWorkflow workflow) { + + TranslationActivities mockedActivities = + mock(TranslationActivities.class, withSettings().withoutAnnotations()); + when(mockedActivities.translateTerm(new TranslationActivityInput("hello", "fr"))) + .thenReturn(new TranslationActivityOutput("Bonjour")); + when(mockedActivities.translateTerm(new TranslationActivityInput("goodbye", "fr"))) + .thenReturn(new TranslationActivityOutput("Au revoir")); + + worker.registerActivitiesImplementations(mockedActivities); + testEnv.start(); + + TranslationWorkflowOutput output = + workflow.sayHelloGoodbye(new TranslationWorkflowInput("Pierre", "fr")); + + assertEquals("Bonjour, Pierre", output.getHelloMessage()); + assertEquals("Au revoir, Pierre", output.getGoodbyeMessage()); + } +} diff --git a/exercises/async-activity-completion/practice/src/test/java/translationworkflow/TranslationWorkflowTest.java b/exercises/async-activity-completion/practice/src/test/java/translationworkflow/TranslationWorkflowTest.java new file mode 100644 index 0000000..4516bca --- /dev/null +++ b/exercises/async-activity-completion/practice/src/test/java/translationworkflow/TranslationWorkflowTest.java @@ -0,0 +1,38 @@ +package translationworkflow; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.temporal.testing.TestWorkflowEnvironment; +import io.temporal.testing.TestWorkflowExtension; +import io.temporal.worker.Worker; +import translationworkflow.model.TranslationActivityInput; +import translationworkflow.model.TranslationActivityOutput; +import translationworkflow.model.TranslationWorkflowInput; +import translationworkflow.model.TranslationWorkflowOutput; + +public class TranslationWorkflowTest { + + @RegisterExtension + public static final TestWorkflowExtension testWorkflowExtension = + TestWorkflowExtension.newBuilder() + .setWorkflowTypes(TranslationWorkflowImpl.class) + .setDoNotStart(true) + .build(); + + @Test + public void testSuccessfulTranslation(TestWorkflowEnvironment testEnv, Worker worker, + TranslationWorkflow workflow) { + + worker.registerActivitiesImplementations(new TranslationActivitiesImpl()); + testEnv.start(); + + TranslationWorkflowOutput output = + workflow.sayHelloGoodbye(new TranslationWorkflowInput("Pierre", "fr")); + + assertEquals("Bonjour, Pierre", output.getHelloMessage()); + assertEquals("Au revoir, Pierre", output.getGoodbyeMessage()); + } +} diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivities.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivities.java deleted file mode 100644 index 3e25b00..0000000 --- a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivities.java +++ /dev/null @@ -1,10 +0,0 @@ -package asyncactivitycompletion; - -import io.temporal.activity.ActivityInterface; - -@ActivityInterface -public interface AsyncActivityCompletionActivities { - - String activity(String input); - -} diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivitiesImpl.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivitiesImpl.java deleted file mode 100644 index 1ff3a69..0000000 --- a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionActivitiesImpl.java +++ /dev/null @@ -1,42 +0,0 @@ -package asyncactivitycompletion; - - -import io.temporal.activity.Activity; -import io.temporal.activity.ActivityExecutionContext; -import io.temporal.client.ActivityCompletionClient; - -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.concurrent.ForkJoinPool; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.ForkJoinPool; - - -public class AsyncActivityCompletionActivitiesImpl implements AsyncActivityCompletionActivities { - - private static final Logger logger = LoggerFactory.getLogger(AsyncActivityCompletionActivitiesImpl.class); - - @Override - public String activity(String input) { - logger.info("Activity received input: {}", input); - - // Get the activity execution context - ActivityExecutionContext context = Activity.getExecutionContext(); - - // Set a correlation token that can be used to complete the activity asynchronously - byte[] taskToken = context.getTaskToken(); - - byte[] encoded = Base64.getEncoder().encode(taskToken); - logger.info(new String(encoded)); - - context.doNotCompleteOnReturn(); - - // Since we have set doNotCompleteOnReturn(), the workflow action method return value is - // ignored. - return "This return value will be ignored."; - } - -} diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflow.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflow.java deleted file mode 100644 index 29510bc..0000000 --- a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflow.java +++ /dev/null @@ -1,14 +0,0 @@ -package asyncactivitycompletion; - -import io.temporal.workflow.SignalMethod; -import io.temporal.workflow.QueryMethod; -import io.temporal.workflow.WorkflowInterface; -import io.temporal.workflow.WorkflowMethod; - -@WorkflowInterface -public interface AsyncActivityCompletionWorkflow { - - @WorkflowMethod - String workflow(String input); - -} diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflowImpl.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflowImpl.java deleted file mode 100644 index 5d79b5e..0000000 --- a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorkflowImpl.java +++ /dev/null @@ -1,38 +0,0 @@ -package asyncactivitycompletion; - -import java.time.Duration; - -import org.slf4j.Logger; - -import io.temporal.activity.ActivityOptions; -import io.temporal.workflow.Workflow; - - -public class AsyncActivityCompletionWorkflowImpl implements AsyncActivityCompletionWorkflow { - - public static final Logger logger = Workflow.getLogger(AsyncActivityCompletionWorkflowImpl.class); - - private boolean fulfilled = false; - private String currentState; - - private final ActivityOptions options = - ActivityOptions.newBuilder() - .setStartToCloseTimeout(Duration.ofSeconds(300)) - .build(); - - private final AsyncActivityCompletionActivities activities = - Workflow.newActivityStub(AsyncActivityCompletionActivities.class, options); - - @Override - public String workflow(String input) { - - logger.info("Async Activity Workflow started with input: {}", input); - - - String activityResult = activities.activity(input); - - logger.info("Async Activity Workflow completed with result: {}", activityResult); - - return activityResult; - } -} diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/Starter.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/Starter.java index aac2f17..af2f975 100644 --- a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/Starter.java +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/Starter.java @@ -1,11 +1,10 @@ package asyncactivitycompletion; -import java.util.concurrent.CompletableFuture; - import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowOptions; import io.temporal.serviceclient.WorkflowServiceStubs; - +import asyncactivitycompletion.model.TranslationWorkflowInput; +import asyncactivitycompletion.model.TranslationWorkflowOutput; public class Starter { public static void main(String[] args) throws Exception { @@ -15,15 +14,20 @@ public static void main(String[] args) throws Exception { WorkflowClient client = WorkflowClient.newInstance(service); WorkflowOptions options = WorkflowOptions.newBuilder() - .setWorkflowId("async-complete-workflow") - .setTaskQueue("async-complete") + .setWorkflowId("translation-workflow") + .setTaskQueue("translation-tasks") .build(); - AsyncActivityCompletionWorkflow workflow = client.newWorkflowStub(AsyncActivityCompletionWorkflow.class, options); + TranslationWorkflow workflow = client.newWorkflowStub(TranslationWorkflow.class, options); + + String name = args[0]; + String languageCode = args[1]; + + TranslationWorkflowInput input = new TranslationWorkflowInput(name, languageCode); - CompletableFuture result = WorkflowClient.execute(workflow::workflow, "Plain text input"); + TranslationWorkflowOutput greeting = workflow.sayHelloGoodbye(input); - System.out.printf("Workflow result: %s\n", result.get()); + System.out.printf("Workflow result: %s\n", greeting); System.exit(0); } } diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/TranslationActivities.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/TranslationActivities.java new file mode 100644 index 0000000..8b4b3f6 --- /dev/null +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/TranslationActivities.java @@ -0,0 +1,12 @@ +package asyncactivitycompletion; + +import io.temporal.activity.ActivityInterface; +import asyncactivitycompletion.model.TranslationActivityInput; +import asyncactivitycompletion.model.TranslationActivityOutput; + +@ActivityInterface +public interface TranslationActivities { + + TranslationActivityOutput translateTerm(TranslationActivityInput input); + +} diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/TranslationActivitiesImpl.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/TranslationActivitiesImpl.java new file mode 100644 index 0000000..c4dc0e0 --- /dev/null +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/TranslationActivitiesImpl.java @@ -0,0 +1,125 @@ +package asyncactivitycompletion; + +import java.net.HttpURLConnection; +import java.net.ProtocolException; +import java.net.URI; +import java.net.URL; +import java.net.URLEncoder; +import java.util.Base64; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; +import io.temporal.activity.Activity; +import io.temporal.failure.ApplicationFailure; +import java.net.HttpURLConnection; +import io.temporal.activity.ActivityExecutionContext; +import io.temporal.client.ActivityCompletionClient; +import io.temporal.client.WorkflowClient; +import io.temporal.serviceclient.WorkflowServiceStubs; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.Base64; +import asyncactivitycompletion.model.TranslationActivityInput; +import asyncactivitycompletion.model.TranslationActivityOutput; + +public class TranslationActivitiesImpl implements TranslationActivities { + + private static final Logger logger = LoggerFactory.getLogger(TranslationActivitiesImpl.class); + + @Override + public TranslationActivityOutput translateTerm(TranslationActivityInput input) { + + logger.info("translateTerm Activity received input: {}", input); + + // Get the activity execution context + ActivityExecutionContext context = Activity.getExecutionContext(); + + // Set a correlation token that can be used to complete the activity asynchronously + byte[] taskToken = context.getTaskToken(); + + String encoded = new String(Base64.getEncoder().encode(taskToken)); + + logger.info("TASK TOKEN: {}", encoded); + + String term = input.getTerm(); + String lang = input.getLanguageCode(); + + logger.info("[ACTIVITY INVOKED] translateTerm invoked with input term: {} language code: {}", + term, lang); + + // construct the URL, with supplied input parameters, for accessing the + // microservice + URL url = null; + try { + String baseUrl = "http://localhost:9999/translate?term=%s&lang=%s"; + url = URI.create( + String.format(baseUrl, + URLEncoder.encode(term, "UTF-8"), + URLEncoder.encode(lang, "UTF-8"))) + .toURL(); + } catch (IOException e) { + logger.error(e.getMessage()); + throw Activity.wrap(e); + } + + TranslationActivityOutput result = new TranslationActivityOutput(); + + try { + // Open a connection to the URL + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + + // Set the HTTP request method (GET, POST, etc.) + connection.setRequestMethod("GET"); + + // Get the response code + int responseCode = connection.getResponseCode(); + + if (responseCode == HttpURLConnection.HTTP_OK) { + // If the response code is 200 (HTTP OK), the request was successful + BufferedReader reader = new BufferedReader( + new InputStreamReader(connection.getInputStream())); + String line; + StringBuilder response = new StringBuilder(); + + while ((line = reader.readLine()) != null) { + response.append(line); + } + + reader.close(); + + connection.disconnect(); + result.setTranslation(response.toString()); + + } else { + // If the response code is not 200, there was an error + BufferedReader errorReader = new BufferedReader( + new InputStreamReader(connection.getErrorStream())); + String line; + StringBuilder errorResponse = new StringBuilder(); + + while ((line = errorReader.readLine()) != null) { + errorResponse.append(line); + } + + errorReader.close(); + + connection.disconnect(); + // Print the error response + throw ApplicationFailure.newFailure(errorResponse.toString(), IOException.class.getName()); + } + + } catch (IOException e) { + logger.error(e.getMessage()); + throw Activity.wrap(e); + } + + logger.info("Translation Service returned: {}", result.getTranslation()); + + context.doNotCompleteOnReturn(); + + // Since we have set doNotCompleteOnReturn(), the return value is ignored. + return new TranslationActivityOutput("this will be ignored"); + } + +} diff --git a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorker.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/TranslationWorker.java similarity index 59% rename from exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorker.java rename to exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/TranslationWorker.java index d010558..44a1e4b 100644 --- a/exercises/async-activity-completion/practice/src/main/java/asyncactivitycompletion/AsyncActivityCompletionWorker.java +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/TranslationWorker.java @@ -2,22 +2,21 @@ import io.temporal.client.WorkflowClient; import io.temporal.serviceclient.WorkflowServiceStubs; -import io.temporal.client.ActivityCompletionClient; import io.temporal.worker.Worker; import io.temporal.worker.WorkerFactory; -public class AsyncActivityCompletionWorker { +public class TranslationWorker { public static void main(String[] args) { WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); WorkflowClient client = WorkflowClient.newInstance(service); WorkerFactory factory = WorkerFactory.newInstance(client); - Worker worker = factory.newWorker("async-complete"); + Worker worker = factory.newWorker("translation-tasks"); - worker.registerWorkflowImplementationTypes(AsyncActivityCompletionWorkflowImpl.class); + worker.registerWorkflowImplementationTypes(TranslationWorkflowImpl.class); - worker.registerActivitiesImplementations(new AsyncActivityCompletionActivitiesImpl()); + worker.registerActivitiesImplementations(new TranslationActivitiesImpl()); factory.start(); } diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/TranslationWorkflow.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/TranslationWorkflow.java new file mode 100644 index 0000000..130133e --- /dev/null +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/TranslationWorkflow.java @@ -0,0 +1,14 @@ +package asyncactivitycompletion; + +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; +import asyncactivitycompletion.model.TranslationWorkflowInput; +import asyncactivitycompletion.model.TranslationWorkflowOutput; + +@WorkflowInterface +public interface TranslationWorkflow { + + @WorkflowMethod + TranslationWorkflowOutput sayHelloGoodbye(TranslationWorkflowInput input); + +} diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/TranslationWorkflowImpl.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/TranslationWorkflowImpl.java new file mode 100644 index 0000000..73e007b --- /dev/null +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/TranslationWorkflowImpl.java @@ -0,0 +1,42 @@ +package asyncactivitycompletion; + +import java.time.Duration; +import org.slf4j.Logger; + +import io.temporal.activity.ActivityOptions; +import io.temporal.workflow.Workflow; +import asyncactivitycompletion.model.TranslationActivityInput; +import asyncactivitycompletion.model.TranslationActivityOutput; +import asyncactivitycompletion.model.TranslationWorkflowInput; +import asyncactivitycompletion.model.TranslationWorkflowOutput; + +public class TranslationWorkflowImpl implements TranslationWorkflow { + + public static final Logger logger = Workflow.getLogger(TranslationWorkflowImpl.class); + + private final ActivityOptions options = + ActivityOptions.newBuilder() + .setStartToCloseTimeout(Duration.ofSeconds(300)) + .build(); + + private final TranslationActivities activities = + Workflow.newActivityStub(TranslationActivities.class, options); + + @Override + public TranslationWorkflowOutput sayHelloGoodbye(TranslationWorkflowInput input) { + String name = input.getName(); + String languageCode = input.getLanguageCode(); + + logger.info("sayHelloGoodbye Workflow Invoked with input name: {} language code: {}", name, + languageCode); + + logger.debug("Preparing to translate Hello into languageCode: {}", languageCode); + TranslationActivityInput helloInput = new TranslationActivityInput("hello", languageCode); + TranslationActivityOutput helloResult = activities.translateTerm(helloInput); + String helloMessage = helloResult.getTranslation() + ", " + name; + + logger.info("Workflow completed"); + + return new TranslationWorkflowOutput(helloMessage); + } +} diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/CompletionClient.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/VerifyAndCompleteTranslation.java similarity index 71% rename from exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/CompletionClient.java rename to exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/VerifyAndCompleteTranslation.java index a11c796..675bef7 100644 --- a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/CompletionClient.java +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/VerifyAndCompleteTranslation.java @@ -8,23 +8,29 @@ import io.temporal.serviceclient.WorkflowServiceStubs; import io.temporal.client.ActivityCompletionClient; +import asyncactivitycompletion.model.TranslationActivityOutput; + import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -public class CompletionClient { +public class VerifyAndCompleteTranslation { public static void main(String[] args) throws ExecutionException, InterruptedException { WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); WorkflowClient client = WorkflowClient.newInstance(service); - String result = "Asynchronously completed"; + byte[] taskToken = Base64.getDecoder().decode(args[0]); + // Pass in the translated text as a command line argument and pretend to "verify" + // the results + String result = args[1]; + ActivityCompletionClient activityCompletionClient = client.newActivityCompletionClient(); - activityCompletionClient.complete(taskToken, result); + activityCompletionClient.complete(taskToken, new TranslationActivityOutput(result)); System.exit(0); } -} +} \ No newline at end of file diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/model/TranslationActivityInput.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/model/TranslationActivityInput.java new file mode 100644 index 0000000..4b87e58 --- /dev/null +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/model/TranslationActivityInput.java @@ -0,0 +1,59 @@ +package asyncactivitycompletion.model; + +import java.util.Objects; + +public class TranslationActivityInput { + + private String term; + private String languageCode; + + public TranslationActivityInput() { + } + + public TranslationActivityInput(String term, String languageCode) { + this.term = term; + this.languageCode = languageCode; + } + + public String getTerm() { + return term; + } + + public void setTerm(String term) { + this.term = term; + } + + public String getLanguageCode() { + return languageCode; + } + + public void setLanguageCode(String languageCode) { + this.languageCode = languageCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final TranslationActivityInput other = (TranslationActivityInput) obj; + if (!Objects.equals(this.term, other.term)) { + return false; + } + return Objects.equals(this.languageCode, other.languageCode); + } + + @Override + public int hashCode() { + int hash = 3; + hash = 53 * hash + Objects.hashCode(this.term); + hash = 53 * hash + Objects.hashCode(this.languageCode); + return hash; + } +} diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/model/TranslationActivityOutput.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/model/TranslationActivityOutput.java new file mode 100644 index 0000000..71c6bc4 --- /dev/null +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/model/TranslationActivityOutput.java @@ -0,0 +1,21 @@ +package asyncactivitycompletion.model; + +public class TranslationActivityOutput { + + private String translation; + + public TranslationActivityOutput() { + } + + public TranslationActivityOutput(String translation) { + this.translation = translation; + } + + public String getTranslation() { + return translation; + } + + public void setTranslation(String translation) { + this.translation = translation; + } +} diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/model/TranslationWorkflowInput.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/model/TranslationWorkflowInput.java new file mode 100644 index 0000000..0737015 --- /dev/null +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/model/TranslationWorkflowInput.java @@ -0,0 +1,31 @@ +package asyncactivitycompletion.model; + +public class TranslationWorkflowInput { + + private String name; + private String languageCode; + + public TranslationWorkflowInput() { + } + + public TranslationWorkflowInput(String name, String languageCode) { + this.name = name; + this.languageCode = languageCode; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLanguageCode() { + return languageCode; + } + + public void setLanguageCode(String languageCode) { + this.languageCode = languageCode; + } +} diff --git a/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/model/TranslationWorkflowOutput.java b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/model/TranslationWorkflowOutput.java new file mode 100644 index 0000000..e12c2d8 --- /dev/null +++ b/exercises/async-activity-completion/solution/src/main/java/asyncactivitycompletion/model/TranslationWorkflowOutput.java @@ -0,0 +1,26 @@ +package asyncactivitycompletion.model; + +public class TranslationWorkflowOutput { + + private String helloMessage; + + public TranslationWorkflowOutput() { + } + + public TranslationWorkflowOutput(String helloMessage) { + this.helloMessage = helloMessage; + } + + public String getHelloMessage() { + return helloMessage; + } + + public void setHelloMessage(String helloMessage) { + this.helloMessage = helloMessage; + } + + @Override + public String toString() { + return this.helloMessage; + } +} diff --git a/exercises/async-activity-completion/solution/src/test/java/translationworkflow/TranslationActivitiesTest.java b/exercises/async-activity-completion/solution/src/test/java/translationworkflow/TranslationActivitiesTest.java new file mode 100644 index 0000000..10dfa8c --- /dev/null +++ b/exercises/async-activity-completion/solution/src/test/java/translationworkflow/TranslationActivitiesTest.java @@ -0,0 +1,61 @@ +package translationworkflow; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.temporal.failure.ActivityFailure; +import io.temporal.testing.TestActivityEnvironment; +import translationworkflow.model.TranslationActivityInput; +import translationworkflow.model.TranslationActivityOutput; + +public class TranslationActivitiesTest { + + private TestActivityEnvironment testEnvironment; + private TranslationActivities activity; + + @BeforeEach + public void init() { + testEnvironment = TestActivityEnvironment.newInstance(); + testEnvironment.registerActivitiesImplementations(new TranslationActivitiesImpl()); + activity = testEnvironment.newActivityStub(TranslationActivities.class); + } + + @AfterEach + public void destroy() { + testEnvironment.close(); + } + + @Test + public void testSuccessfulTranslateActivityHelloGerman() { + TranslationActivityInput input = new TranslationActivityInput("hello", "de"); + TranslationActivityOutput output = activity.translateTerm(input); + assertEquals("Hallo", output.getTranslation()); + } + + @Test + public void testSuccessfulTranslateActivityHelloLatvian() { + TranslationActivityInput input = new TranslationActivityInput("goodbye", "lv"); + TranslationActivityOutput output = activity.translateTerm(input); + assertEquals("Ardievu", output.getTranslation()); + } + + @Test + public void testFailedTranslateActivityBadLanguageCode() { + TranslationActivityInput input = new TranslationActivityInput("goodbye", "xq"); + + // Assert that an error was thrown and it was an Activity Failure + Exception exception = assertThrows(ActivityFailure.class, () -> { + TranslationActivityOutput output = activity.translateTerm(input); + }); + + // Assert that the error contains the expected message + assertTrue(exception.getMessage().contains( + "Invalid language code"), + "expected error message"); + } +} diff --git a/exercises/async-activity-completion/solution/src/test/java/translationworkflow/TranslationWorkflowMockTest.java b/exercises/async-activity-completion/solution/src/test/java/translationworkflow/TranslationWorkflowMockTest.java new file mode 100644 index 0000000..8f6215e --- /dev/null +++ b/exercises/async-activity-completion/solution/src/test/java/translationworkflow/TranslationWorkflowMockTest.java @@ -0,0 +1,46 @@ +package translationworkflow; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.temporal.testing.TestWorkflowEnvironment; +import io.temporal.testing.TestWorkflowExtension; +import io.temporal.worker.Worker; +import translationworkflow.model.TranslationActivityInput; +import translationworkflow.model.TranslationActivityOutput; +import translationworkflow.model.TranslationWorkflowInput; +import translationworkflow.model.TranslationWorkflowOutput; +import static org.mockito.Mockito.*; + +public class TranslationWorkflowMockTest { + + @RegisterExtension + public static final TestWorkflowExtension testWorkflowExtension = + TestWorkflowExtension.newBuilder() + .setWorkflowTypes(TranslationWorkflowImpl.class) + .setDoNotStart(true) + .build(); + + @Test + public void testSuccessfulTranslationWithMocks(TestWorkflowEnvironment testEnv, Worker worker, + TranslationWorkflow workflow) { + + TranslationActivities mockedActivities = + mock(TranslationActivities.class, withSettings().withoutAnnotations()); + when(mockedActivities.translateTerm(new TranslationActivityInput("hello", "fr"))) + .thenReturn(new TranslationActivityOutput("Bonjour")); + when(mockedActivities.translateTerm(new TranslationActivityInput("goodbye", "fr"))) + .thenReturn(new TranslationActivityOutput("Au revoir")); + + worker.registerActivitiesImplementations(mockedActivities); + testEnv.start(); + + TranslationWorkflowOutput output = + workflow.sayHelloGoodbye(new TranslationWorkflowInput("Pierre", "fr")); + + assertEquals("Bonjour, Pierre", output.getHelloMessage()); + assertEquals("Au revoir, Pierre", output.getGoodbyeMessage()); + } +} diff --git a/exercises/async-activity-completion/solution/src/test/java/translationworkflow/TranslationWorkflowTest.java b/exercises/async-activity-completion/solution/src/test/java/translationworkflow/TranslationWorkflowTest.java new file mode 100644 index 0000000..4516bca --- /dev/null +++ b/exercises/async-activity-completion/solution/src/test/java/translationworkflow/TranslationWorkflowTest.java @@ -0,0 +1,38 @@ +package translationworkflow; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.temporal.testing.TestWorkflowEnvironment; +import io.temporal.testing.TestWorkflowExtension; +import io.temporal.worker.Worker; +import translationworkflow.model.TranslationActivityInput; +import translationworkflow.model.TranslationActivityOutput; +import translationworkflow.model.TranslationWorkflowInput; +import translationworkflow.model.TranslationWorkflowOutput; + +public class TranslationWorkflowTest { + + @RegisterExtension + public static final TestWorkflowExtension testWorkflowExtension = + TestWorkflowExtension.newBuilder() + .setWorkflowTypes(TranslationWorkflowImpl.class) + .setDoNotStart(true) + .build(); + + @Test + public void testSuccessfulTranslation(TestWorkflowEnvironment testEnv, Worker worker, + TranslationWorkflow workflow) { + + worker.registerActivitiesImplementations(new TranslationActivitiesImpl()); + testEnv.start(); + + TranslationWorkflowOutput output = + workflow.sayHelloGoodbye(new TranslationWorkflowInput("Pierre", "fr")); + + assertEquals("Bonjour, Pierre", output.getHelloMessage()); + assertEquals("Au revoir, Pierre", output.getGoodbyeMessage()); + } +} diff --git a/utilities/microservice/pom.xml b/utilities/microservice/pom.xml new file mode 100644 index 0000000..99d8824 --- /dev/null +++ b/utilities/microservice/pom.xml @@ -0,0 +1,82 @@ + + + + 4.0.0 + + io.temporal.learn + translation-microservice + 1.0.0-SNAPSHOT + + translation-microservice + https://learn.temporal.io/ + + + UTF-8 + 1.8 + 1.8 + + + + + + + org.apache.commons + commons-lang3 + 3.11 + + + + org.rapidoid + rapidoid-quick + 5.5.5 + + + + + + + + + + maven-clean-plugin + 3.1.0 + + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.8.0 + + + maven-surefire-plugin + 2.22.1 + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + + maven-site-plugin + 3.7.1 + + + maven-project-info-reports-plugin + 3.0.0 + + + + + diff --git a/utilities/microservice/src/main/java/translationapi/Microservice.java b/utilities/microservice/src/main/java/translationapi/Microservice.java new file mode 100644 index 0000000..bf79014 --- /dev/null +++ b/utilities/microservice/src/main/java/translationapi/Microservice.java @@ -0,0 +1,149 @@ +package translationapi; + +import java.io.IOException; + +import org.rapidoid.http.Req; +import org.rapidoid.http.ReqRespHandler; +import org.rapidoid.http.Resp; +import org.rapidoid.setup.On; +import org.rapidoid.u.U; + +import java.util.Map; +import java.util.HashMap; + +public class Microservice { + + // port number where this service will listen for incoming HTTP requests + public static final int PORT_NUMBER = 9999; + + // IP address to which the service will be bound. Using a value of 0.0.0.0 + // will make it available on all available interfaces, but you could use + // 127.0.0.1 to restrict it to the loopback interface + public static final String SERVER_IP = "0.0.0.0"; + + public static void main(String[] args) throws IOException { + // Start the service on the specified IP address and port + On.address(SERVER_IP).port(PORT_NUMBER); + + final Map> translations = loadTranslations(); + + // Define the service endpoints and handlers + On.get("/translate").plain(new TranslationHandler(translations)); + + // Also define a catch-all to return an HTTP 404 Not Found error if the URL + // path in the request didn't match an endpoint defined above. It's essential + // that this code remains at the end. + On.req( + (req, resp) -> { + String message = String.format("Error: Invalid endpoint address '%s'", req.path()); + return req.response().result(message).code(404); + }); + } + + private static class TranslationHandler implements ReqRespHandler { + + private final Map> translations; + + public TranslationHandler(Map> translations) { + super(); + this.translations = translations; + } + + @Override + public Object execute(Req req, Resp resp) throws Exception { + Map params = req.params(); + + if (!params.containsKey("term")) { + String message = "Error: Missing required 'term' parameter!"; + return req.response().result(message).code(500); + } + + if (!params.containsKey("lang")) { + String message = "Error: Missing required 'lang' parameter!"; + return req.response().result(message).code(500); + } + + String languageCode = params.get("lang"); + String term = params.get("term"); + + if (!translations.containsKey(languageCode)) { + String message = "Error: Invalid language code '" + languageCode + "'"; + return req.response().result(message).code(500); + } + + if (!translations.get(languageCode).containsKey(term)) { + String message = "Error: Invalid translation term '" + term + "'"; + return req.response().result(message).code(500); + } + + String message = translations.get(languageCode).get(term); + String capMessage = message.substring(0, 1).toUpperCase() + message.substring(1); + String response = String.format("%s", capMessage); + System.out.println(response); + + return U.str(response); + } + } + + private static Map> loadTranslations() { + Map> translations = new HashMap<>(); + + // German translations + Map germanTranslations = new HashMap<>(); + germanTranslations.put("hello", "hallo"); + germanTranslations.put("goodbye", "auf wiedersehen"); + germanTranslations.put("thanks", "danke schön"); + translations.put("de", germanTranslations); + + // Spanish translations + Map spanishTranslations = new HashMap<>(); + spanishTranslations.put("hello", "hola"); + spanishTranslations.put("goodbye", "adiós"); + spanishTranslations.put("thanks", "gracias"); + translations.put("es", spanishTranslations); + + // French translations + Map frenchTranslations = new HashMap<>(); + frenchTranslations.put("hello", "bonjour"); + frenchTranslations.put("goodbye", "au revoir"); + frenchTranslations.put("thanks", "merci"); + translations.put("fr", frenchTranslations); + + // Latvian translations + Map latvianTranslations = new HashMap<>(); + latvianTranslations.put("hello", "sveiks"); + latvianTranslations.put("goodbye", "ardievu"); + latvianTranslations.put("thanks", "paldies"); + translations.put("lv", latvianTranslations); + + // Maori translations + Map maoriTranslations = new HashMap<>(); + maoriTranslations.put("hello", "kia ora"); + maoriTranslations.put("goodbye", "poroporoaki"); + maoriTranslations.put("thanks", "whakawhetai koe"); + translations.put("mi", maoriTranslations); + + // Slovak translations + Map slovakTranslations = new HashMap<>(); + slovakTranslations.put("hello", "ahoj"); + slovakTranslations.put("goodbye", "zbohom"); + slovakTranslations.put("thanks", "ďakujem koe"); + translations.put("sk", slovakTranslations); + + // Turkish translations + Map turkishTranslations = new HashMap<>(); + turkishTranslations.put("hello", "merhaba"); + turkishTranslations.put("goodbye", "güle güle"); + turkishTranslations.put("thanks", "teşekkür ederim"); + translations.put("tr", turkishTranslations); + + // Zulu translations + Map zuluTranslations = new HashMap<>(); + zuluTranslations.put("hello", "hamba kahle"); + zuluTranslations.put("goodbye", "sawubona"); + zuluTranslations.put("thanks", "ngiyabonga"); + translations.put("zu", zuluTranslations); + + return translations; + } +} From 9e118797cc512ad2c396e77b599f48830a95abcb Mon Sep 17 00:00:00 2001 From: Mason Egger Date: Mon, 29 Apr 2024 13:13:07 -0500 Subject: [PATCH 4/4] minor tweaks from Quinn --- exercises/async-activity-completion/README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/exercises/async-activity-completion/README.md b/exercises/async-activity-completion/README.md index 0c59376..340df47 100644 --- a/exercises/async-activity-completion/README.md +++ b/exercises/async-activity-completion/README.md @@ -37,9 +37,14 @@ the code. 1. Add a call to the `doNotCompleteOnReturn();` method at the end of the `translateTerm()` method using the `context` object from Part A. This notifies Temporal that the Activity should not be completed on return and will be completed asynchronously. 1. Save the file. 1. Open the `TranslationWorkflowImpl.java` file in the `src/main/java/asyncactivitycompletion` subdirectory. - 1. Observe that the Workflow's `StartToCloseTimeout` has been lengthened to 300 seconds for this exercise. Activities can still time out if they are running in the background. + 1. Observe that the Workflow's `StartToCloseTimeout` has been lengthened to `300` seconds for this exercise. Activities can still time out if they are running in the background. + 1. If you don't do this and your Activity retries due to a timeout, the Task Token will be reset. 1. Close this file without making any changes. +**Note:** In practice, it is recommended to use Heartbeats for longer running +Activities. While this exercise doesn't include them, it is a good idea to +include them in Activities that will complete Asynchronously. + ## Part C: Configure a Client to send CompleteActivity 1. Open the `VerifyAndCompleteTranslation.java` file in the `src/main/java/asyncactivitycompletion` subdirectory.