From 3de3b64f14706d0e0411415aa1081ba43a6a580c Mon Sep 17 00:00:00 2001 From: HCaupert <hcaupert@takima.fr> Date: Mon, 19 May 2025 16:47:12 +0200 Subject: [PATCH 1/2] solutions: added part 4/5 updated 2/3 --- .../bakery/kitchen/KitchenWorkflowImpl.java | 18 ++-- .../bakery/MakeCookiesApp.java | 10 -- .../bakery/OrderCookiesApp.java | 7 +- .../temporalpractice/bakery/bake/BakeApp.java | 12 +++ .../{kitchen => }/bake/BakeService.java | 6 +- .../{kitchen => }/bake/BakeServiceImpl.java | 6 +- .../bakery/bake/dtos/BakeRequest.java | 12 +++ .../{kitchen => }/bake/dtos/BakeResult.java | 4 +- .../bakery/batter/BatterApp.java | 12 +++ .../{kitchen => }/batter/BatterService.java | 6 +- .../bakery/batter/BatterServiceImpl.java | 21 +++++ .../bakery/batter/dtos/Batter.java | 10 ++ .../batter/dtos/BatterCreationRequest.java | 11 +++ .../batter/dtos/BatterCreationResult.java | 5 + .../bakery/kitchen/KitchenApp.java | 13 +++ .../bakery/kitchen/KitchenWorker.java | 25 ----- .../bakery/kitchen/KitchenWorkflow.java | 14 ++- .../bakery/kitchen/KitchenWorkflowImpl.java | 37 ++++++-- .../bakery/kitchen/bake/dtos/BakeRequest.java | 12 --- .../kitchen/batter/BatterServiceImpl.java | 21 ----- .../bakery/kitchen/batter/dtos/Batter.java | 10 -- .../batter/dtos/BatterCreationRequest.java | 11 --- .../batter/dtos/BatterCreationResult.java | 5 - .../bakery/kitchen/dtos/KitchenDtos.java | 91 ++++++++++--------- .../bakery/temporal/TemporalQueues.java | 6 +- .../bakery/temporal/TemporalUtils.java | 2 - .../bakery/OrderCookiesApp.java | 25 +++++ .../bakery/OrderManyCookies.java | 70 ++++++++++++++ .../temporalpractice/bakery/bake/BakeApp.java | 12 +++ .../bakery/bake/BakeService.java | 12 +++ .../bakery/bake/BakeServiceImpl.java | 22 +++++ .../bakery/bake/dtos/BakeRequest.java | 12 +++ .../bakery/bake/dtos/BakeResult.java | 8 ++ .../bakery/batter/BatterApp.java | 12 +++ .../bakery/batter/BatterService.java | 13 +++ .../bakery/batter/BatterServiceImpl.java | 21 +++++ .../bakery/batter/dtos/Batter.java | 10 ++ .../batter/dtos/BatterCreationRequest.java | 11 +++ .../batter/dtos/BatterCreationResult.java | 5 + .../bakery/kitchen/KitchenApp.java | 13 +++ .../bakery/kitchen/KitchenWorkflow.java | 21 +++++ .../bakery/kitchen/KitchenWorkflowImpl.java | 57 ++++++++++++ .../bakery/kitchen/dtos/KitchenDtos.java | 62 +++++++++++++ .../bakery/temporal/TemporalQueues.java | 7 ++ .../bakery/temporal/TemporalUtils.java | 51 +++++++++++ .../bakery/CookieWorkflowTest.java | 36 ++++++++ 46 files changed, 688 insertions(+), 179 deletions(-) delete mode 100644 solutions/day1/part-3.0/temporalpractice/bakery/MakeCookiesApp.java create mode 100644 solutions/day1/part-3.0/temporalpractice/bakery/bake/BakeApp.java rename solutions/day1/part-3.0/temporalpractice/bakery/{kitchen => }/bake/BakeService.java (50%) rename solutions/day1/part-3.0/temporalpractice/bakery/{kitchen => }/bake/BakeServiceImpl.java (76%) create mode 100644 solutions/day1/part-3.0/temporalpractice/bakery/bake/dtos/BakeRequest.java rename solutions/day1/part-3.0/temporalpractice/bakery/{kitchen => }/bake/dtos/BakeResult.java (65%) create mode 100644 solutions/day1/part-3.0/temporalpractice/bakery/batter/BatterApp.java rename solutions/day1/part-3.0/temporalpractice/bakery/{kitchen => }/batter/BatterService.java (51%) create mode 100644 solutions/day1/part-3.0/temporalpractice/bakery/batter/BatterServiceImpl.java create mode 100644 solutions/day1/part-3.0/temporalpractice/bakery/batter/dtos/Batter.java create mode 100644 solutions/day1/part-3.0/temporalpractice/bakery/batter/dtos/BatterCreationRequest.java create mode 100644 solutions/day1/part-3.0/temporalpractice/bakery/batter/dtos/BatterCreationResult.java create mode 100644 solutions/day1/part-3.0/temporalpractice/bakery/kitchen/KitchenApp.java delete mode 100644 solutions/day1/part-3.0/temporalpractice/bakery/kitchen/KitchenWorker.java delete mode 100644 solutions/day1/part-3.0/temporalpractice/bakery/kitchen/bake/dtos/BakeRequest.java delete mode 100644 solutions/day1/part-3.0/temporalpractice/bakery/kitchen/batter/BatterServiceImpl.java delete mode 100644 solutions/day1/part-3.0/temporalpractice/bakery/kitchen/batter/dtos/Batter.java delete mode 100644 solutions/day1/part-3.0/temporalpractice/bakery/kitchen/batter/dtos/BatterCreationRequest.java delete mode 100644 solutions/day1/part-3.0/temporalpractice/bakery/kitchen/batter/dtos/BatterCreationResult.java create mode 100644 solutions/day1/part-4.1-start/temporalpractice/bakery/OrderCookiesApp.java create mode 100644 solutions/day1/part-4.1-start/temporalpractice/bakery/OrderManyCookies.java create mode 100644 solutions/day1/part-4.1-start/temporalpractice/bakery/bake/BakeApp.java create mode 100644 solutions/day1/part-4.1-start/temporalpractice/bakery/bake/BakeService.java create mode 100644 solutions/day1/part-4.1-start/temporalpractice/bakery/bake/BakeServiceImpl.java create mode 100644 solutions/day1/part-4.1-start/temporalpractice/bakery/bake/dtos/BakeRequest.java create mode 100644 solutions/day1/part-4.1-start/temporalpractice/bakery/bake/dtos/BakeResult.java create mode 100644 solutions/day1/part-4.1-start/temporalpractice/bakery/batter/BatterApp.java create mode 100644 solutions/day1/part-4.1-start/temporalpractice/bakery/batter/BatterService.java create mode 100644 solutions/day1/part-4.1-start/temporalpractice/bakery/batter/BatterServiceImpl.java create mode 100644 solutions/day1/part-4.1-start/temporalpractice/bakery/batter/dtos/Batter.java create mode 100644 solutions/day1/part-4.1-start/temporalpractice/bakery/batter/dtos/BatterCreationRequest.java create mode 100644 solutions/day1/part-4.1-start/temporalpractice/bakery/batter/dtos/BatterCreationResult.java create mode 100644 solutions/day1/part-4.1-start/temporalpractice/bakery/kitchen/KitchenApp.java create mode 100644 solutions/day1/part-4.1-start/temporalpractice/bakery/kitchen/KitchenWorkflow.java create mode 100644 solutions/day1/part-4.1-start/temporalpractice/bakery/kitchen/KitchenWorkflowImpl.java create mode 100644 solutions/day1/part-4.1-start/temporalpractice/bakery/kitchen/dtos/KitchenDtos.java create mode 100644 solutions/day1/part-4.1-start/temporalpractice/bakery/temporal/TemporalQueues.java create mode 100644 solutions/day1/part-4.1-start/temporalpractice/bakery/temporal/TemporalUtils.java create mode 100644 solutions/day1/part-5.1-start/temporalpractice/bakery/CookieWorkflowTest.java diff --git a/solutions/day1/part-2/temporalpractice/bakery/kitchen/KitchenWorkflowImpl.java b/solutions/day1/part-2/temporalpractice/bakery/kitchen/KitchenWorkflowImpl.java index 529793a..8573360 100644 --- a/solutions/day1/part-2/temporalpractice/bakery/kitchen/KitchenWorkflowImpl.java +++ b/solutions/day1/part-2/temporalpractice/bakery/kitchen/KitchenWorkflowImpl.java @@ -8,19 +8,13 @@ import io.temporal.workflow.Workflow; import java.time.Duration; public class KitchenWorkflowImpl implements KitchenWorkflow { - private final BatterService batterService; - private final BakeService bakeService; - - public KitchenWorkflowImpl() { - ActivityOptions options = ActivityOptions.newBuilder() - .setTaskQueue("TheOneAndOnlyQueue") - .setStartToCloseTimeout(Duration.ofSeconds(10)) - .build(); - - this.batterService = Workflow.newActivityStub(BatterService.class, options); - this.bakeService = Workflow.newActivityStub(BakeService.class, options); - } + private final ActivityOptions options = ActivityOptions.newBuilder() + .setTaskQueue("TheOneAndOnlyQueue") + .setStartToCloseTimeout(Duration.ofSeconds(10)) + .build(); + private final BatterService batterService = Workflow.newActivityStub(BatterService.class, options); + private final BakeService bakeService = Workflow.newActivityStub(BakeService.class, options); @Override public void makeCookies() { diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/MakeCookiesApp.java b/solutions/day1/part-3.0/temporalpractice/bakery/MakeCookiesApp.java deleted file mode 100644 index 2ea7f9b..0000000 --- a/solutions/day1/part-3.0/temporalpractice/bakery/MakeCookiesApp.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.takima.temporalpractice.bakery; - -import io.takima.temporalpractice.bakery.kitchen.KitchenWorker; - -public class MakeCookiesApp { - public static void main(String[] args) { - // This app should execute the making of all cookies ordered - new KitchenWorker().start(); - } -} diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/OrderCookiesApp.java b/solutions/day1/part-3.0/temporalpractice/bakery/OrderCookiesApp.java index 4dd036c..2ff2b24 100644 --- a/solutions/day1/part-3.0/temporalpractice/bakery/OrderCookiesApp.java +++ b/solutions/day1/part-3.0/temporalpractice/bakery/OrderCookiesApp.java @@ -12,10 +12,13 @@ public class OrderCookiesApp { public static void main(String[] args) { // This app simulates an order of cookies // It should call the workflow which makes cookies - var orderRequest = CookieOrderRequest.random(); - KitchenWorkflow workflow = TemporalUtils.newWorkflowStub(KitchenWorkflow.class, TemporalQueues.KITCHEN, "kitchen-" + orderRequest.orderId()); + KitchenWorkflow workflow = TemporalUtils.newWorkflowStub( + KitchenWorkflow.class, + TemporalQueues.KITCHEN, + "kitchen-" + orderRequest.orderId() + ); workflow.makeCookies(orderRequest); // Start the Workflow Execution } diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/bake/BakeApp.java b/solutions/day1/part-3.0/temporalpractice/bakery/bake/BakeApp.java new file mode 100644 index 0000000..2d1cdb1 --- /dev/null +++ b/solutions/day1/part-3.0/temporalpractice/bakery/bake/BakeApp.java @@ -0,0 +1,12 @@ +package io.takima.temporalpractice.bakery.bake; + +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.takima.temporalpractice.bakery.temporal.TemporalUtils; + +public class BakeApp { + public static void main(String[] args) { + var worker = TemporalUtils.newWorker(TemporalQueues.BAKE); + worker.registerActivitiesImplementations(new BakeServiceImpl()); + TemporalUtils.startWorkerFactory(); + } +} diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/bake/BakeService.java b/solutions/day1/part-3.0/temporalpractice/bakery/bake/BakeService.java similarity index 50% rename from solutions/day1/part-3.0/temporalpractice/bakery/kitchen/bake/BakeService.java rename to solutions/day1/part-3.0/temporalpractice/bakery/bake/BakeService.java index a0e42de..36c0d02 100644 --- a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/bake/BakeService.java +++ b/solutions/day1/part-3.0/temporalpractice/bakery/bake/BakeService.java @@ -1,7 +1,7 @@ -package io.takima.temporalpractice.bakery.kitchen.bake; +package io.takima.temporalpractice.bakery.bake; -import io.takima.temporalpractice.bakery.kitchen.bake.dtos.BakeRequest; -import io.takima.temporalpractice.bakery.kitchen.bake.dtos.BakeResult; +import io.takima.temporalpractice.bakery.bake.dtos.BakeRequest; +import io.takima.temporalpractice.bakery.bake.dtos.BakeResult; import io.temporal.activity.ActivityInterface; import io.temporal.activity.ActivityMethod; diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/bake/BakeServiceImpl.java b/solutions/day1/part-3.0/temporalpractice/bakery/bake/BakeServiceImpl.java similarity index 76% rename from solutions/day1/part-3.0/temporalpractice/bakery/kitchen/bake/BakeServiceImpl.java rename to solutions/day1/part-3.0/temporalpractice/bakery/bake/BakeServiceImpl.java index 4924604..22509b6 100644 --- a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/bake/BakeServiceImpl.java +++ b/solutions/day1/part-3.0/temporalpractice/bakery/bake/BakeServiceImpl.java @@ -1,7 +1,7 @@ -package io.takima.temporalpractice.bakery.kitchen.bake; +package io.takima.temporalpractice.bakery.bake; -import io.takima.temporalpractice.bakery.kitchen.bake.dtos.BakeRequest; -import io.takima.temporalpractice.bakery.kitchen.bake.dtos.BakeResult; +import io.takima.temporalpractice.bakery.bake.dtos.BakeRequest; +import io.takima.temporalpractice.bakery.bake.dtos.BakeResult; import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.Cookie; import java.util.ArrayList; diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/bake/dtos/BakeRequest.java b/solutions/day1/part-3.0/temporalpractice/bakery/bake/dtos/BakeRequest.java new file mode 100644 index 0000000..42701fe --- /dev/null +++ b/solutions/day1/part-3.0/temporalpractice/bakery/bake/dtos/BakeRequest.java @@ -0,0 +1,12 @@ +package io.takima.temporalpractice.bakery.bake.dtos; + +import io.takima.temporalpractice.bakery.batter.dtos.Batter; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.BakingPreference; + +public record BakeRequest( + String orderId, + Batter batter, + BakingPreference bakingPreference +) { +} + diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/bake/dtos/BakeResult.java b/solutions/day1/part-3.0/temporalpractice/bakery/bake/dtos/BakeResult.java similarity index 65% rename from solutions/day1/part-3.0/temporalpractice/bakery/kitchen/bake/dtos/BakeResult.java rename to solutions/day1/part-3.0/temporalpractice/bakery/bake/dtos/BakeResult.java index 3bdca8f..f9afcf7 100644 --- a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/bake/dtos/BakeResult.java +++ b/solutions/day1/part-3.0/temporalpractice/bakery/bake/dtos/BakeResult.java @@ -1,8 +1,8 @@ -package io.takima.temporalpractice.bakery.kitchen.bake.dtos; +package io.takima.temporalpractice.bakery.bake.dtos; import java.util.List; -import static io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.*; +import static io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.Cookie; public record BakeResult(List<Cookie> cookies) { } diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/batter/BatterApp.java b/solutions/day1/part-3.0/temporalpractice/bakery/batter/BatterApp.java new file mode 100644 index 0000000..8b4bdb9 --- /dev/null +++ b/solutions/day1/part-3.0/temporalpractice/bakery/batter/BatterApp.java @@ -0,0 +1,12 @@ +package io.takima.temporalpractice.bakery.batter; + +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.takima.temporalpractice.bakery.temporal.TemporalUtils; + +public class BatterApp { + public static void main(String[] args) { + var worker = TemporalUtils.newWorker(TemporalQueues.BATTER); + worker.registerActivitiesImplementations(new BatterServiceImpl()); + TemporalUtils.startWorkerFactory(); + } +} diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/batter/BatterService.java b/solutions/day1/part-3.0/temporalpractice/bakery/batter/BatterService.java similarity index 51% rename from solutions/day1/part-3.0/temporalpractice/bakery/kitchen/batter/BatterService.java rename to solutions/day1/part-3.0/temporalpractice/bakery/batter/BatterService.java index e9f7434..7da3e9f 100644 --- a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/batter/BatterService.java +++ b/solutions/day1/part-3.0/temporalpractice/bakery/batter/BatterService.java @@ -1,7 +1,7 @@ -package io.takima.temporalpractice.bakery.kitchen.batter; +package io.takima.temporalpractice.bakery.batter; -import io.takima.temporalpractice.bakery.kitchen.batter.dtos.BatterCreationRequest; -import io.takima.temporalpractice.bakery.kitchen.batter.dtos.BatterCreationResult; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationRequest; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationResult; import io.temporal.activity.ActivityInterface; import io.temporal.activity.ActivityMethod; diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/batter/BatterServiceImpl.java b/solutions/day1/part-3.0/temporalpractice/bakery/batter/BatterServiceImpl.java new file mode 100644 index 0000000..b594d07 --- /dev/null +++ b/solutions/day1/part-3.0/temporalpractice/bakery/batter/BatterServiceImpl.java @@ -0,0 +1,21 @@ +package io.takima.temporalpractice.bakery.batter; + +import io.takima.temporalpractice.bakery.batter.dtos.Batter; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationRequest; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationResult; + +public class BatterServiceImpl implements BatterService { + + static final int BATTER_QTY_PER_COOKIE_IN_GRAMS = 30; // in grams + + @Override + public BatterCreationResult prepareBatter(BatterCreationRequest request) { + System.out.println("Mixing flour, sugar, and love for " + request.targetCookieCount() + " cookies with topping " + request.targetTopping()); + return new BatterCreationResult( + new Batter( + request.targetTopping(), + BATTER_QTY_PER_COOKIE_IN_GRAMS * request.targetCookieCount(), + request.targetCookieCount() + )); + } +} diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/batter/dtos/Batter.java b/solutions/day1/part-3.0/temporalpractice/bakery/batter/dtos/Batter.java new file mode 100644 index 0000000..8608c0e --- /dev/null +++ b/solutions/day1/part-3.0/temporalpractice/bakery/batter/dtos/Batter.java @@ -0,0 +1,10 @@ +package io.takima.temporalpractice.bakery.batter.dtos; + +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.Topping; + +public record Batter( + Topping topping, + int quantityInGrams, + int targetCookieCount +) { +} diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/batter/dtos/BatterCreationRequest.java b/solutions/day1/part-3.0/temporalpractice/bakery/batter/dtos/BatterCreationRequest.java new file mode 100644 index 0000000..38cc0c1 --- /dev/null +++ b/solutions/day1/part-3.0/temporalpractice/bakery/batter/dtos/BatterCreationRequest.java @@ -0,0 +1,11 @@ +package io.takima.temporalpractice.bakery.batter.dtos; + +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.Topping; + +public record BatterCreationRequest( + String orderId, + Topping targetTopping, + int targetCookieCount +) { + +} diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/batter/dtos/BatterCreationResult.java b/solutions/day1/part-3.0/temporalpractice/bakery/batter/dtos/BatterCreationResult.java new file mode 100644 index 0000000..d75e6ab --- /dev/null +++ b/solutions/day1/part-3.0/temporalpractice/bakery/batter/dtos/BatterCreationResult.java @@ -0,0 +1,5 @@ +package io.takima.temporalpractice.bakery.batter.dtos; + +public record BatterCreationResult(Batter batter) { + +} diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/KitchenApp.java b/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/KitchenApp.java new file mode 100644 index 0000000..4c57367 --- /dev/null +++ b/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/KitchenApp.java @@ -0,0 +1,13 @@ +package io.takima.temporalpractice.bakery.kitchen; + +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.takima.temporalpractice.bakery.temporal.TemporalUtils; +import io.temporal.worker.Worker; + +public class KitchenApp { + public static void main(String[] args) { + Worker worker = TemporalUtils.newWorker(TemporalQueues.KITCHEN); + worker.registerWorkflowImplementationTypes(KitchenWorkflowImpl.class); + TemporalUtils.startWorkerFactory(); + } +} diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/KitchenWorker.java b/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/KitchenWorker.java deleted file mode 100644 index fbc3ed7..0000000 --- a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/KitchenWorker.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.takima.temporalpractice.bakery.kitchen; - -import io.takima.temporalpractice.bakery.kitchen.bake.BakeServiceImpl; -import io.takima.temporalpractice.bakery.kitchen.batter.BatterServiceImpl; -import io.takima.temporalpractice.bakery.temporal.TemporalQueues; -import io.takima.temporalpractice.bakery.temporal.TemporalUtils; -import io.temporal.client.WorkflowClient; -import io.temporal.client.WorkflowOptions; -import io.temporal.serviceclient.WorkflowServiceStubs; -import io.temporal.worker.Worker; -import io.temporal.worker.WorkerFactory; - -public class KitchenWorker { - public void start() { - Worker worker = TemporalUtils.newWorker(TemporalQueues.KITCHEN); - - // Register workflows - worker.registerWorkflowImplementationTypes(KitchenWorkflowImpl.class); - // Register activities - worker.registerActivitiesImplementations(new BakeServiceImpl()); - worker.registerActivitiesImplementations(new BatterServiceImpl()); - - TemporalUtils.startWorkerFactory(); - } -} diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/KitchenWorkflow.java b/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/KitchenWorkflow.java index 9f7fb09..ca26635 100644 --- a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/KitchenWorkflow.java +++ b/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/KitchenWorkflow.java @@ -1,13 +1,21 @@ package io.takima.temporalpractice.bakery.kitchen; -import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos; import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderRequest; import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderResult; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderStatus; +import io.temporal.workflow.QueryMethod; +import io.temporal.workflow.SignalMethod; import io.temporal.workflow.WorkflowInterface; import io.temporal.workflow.WorkflowMethod; @WorkflowInterface public interface KitchenWorkflow { - @WorkflowMethod - CookieOrderResult makeCookies(CookieOrderRequest request); + @WorkflowMethod + CookieOrderResult makeCookies(CookieOrderRequest request); + + @SignalMethod + void ovenReady(); + + @QueryMethod + CookieOrderStatus getStatus(); } diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/KitchenWorkflowImpl.java b/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/KitchenWorkflowImpl.java index e4e5026..df0bedc 100644 --- a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/KitchenWorkflowImpl.java +++ b/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/KitchenWorkflowImpl.java @@ -1,14 +1,14 @@ package io.takima.temporalpractice.bakery.kitchen; -import io.takima.temporalpractice.bakery.kitchen.bake.BakeService; -import io.takima.temporalpractice.bakery.kitchen.bake.dtos.BakeRequest; -import io.takima.temporalpractice.bakery.kitchen.batter.BatterService; -import io.takima.temporalpractice.bakery.kitchen.batter.dtos.BatterCreationRequest; +import io.takima.temporalpractice.bakery.bake.BakeService; +import io.takima.temporalpractice.bakery.bake.dtos.BakeRequest; +import io.takima.temporalpractice.bakery.batter.BatterService; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationRequest; import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderRequest; import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderResult; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderStatus; import io.takima.temporalpractice.bakery.temporal.TemporalQueues; import io.takima.temporalpractice.bakery.temporal.TemporalUtils; -import io.temporal.activity.ActivityOptions; import io.temporal.workflow.Workflow; import java.time.Duration; @@ -16,23 +16,42 @@ import java.time.Duration; public class KitchenWorkflowImpl implements KitchenWorkflow { private final BatterService batterService; private final BakeService bakeService; + private boolean ovenReady = false; + private CookieOrderRequest cookieOrder; public KitchenWorkflowImpl() { - this.batterService = TemporalUtils.newActivityStub(BatterService.class, TemporalQueues.KITCHEN); - this.bakeService = TemporalUtils.newActivityStub(BakeService.class, TemporalQueues.KITCHEN); + this.batterService = TemporalUtils.newActivityStub(BatterService.class, TemporalQueues.BATTER); + this.bakeService = TemporalUtils.newActivityStub(BakeService.class, TemporalQueues.BAKE); } @Override public CookieOrderResult makeCookies(CookieOrderRequest request) { - // Note: System.out.println is used here for simplicity - // In real applications, you should use proper Workflow-aware logging (we'll cover this in Day 2) + this.cookieOrder = request; + var timer = Workflow.newTimer(Duration.ofSeconds(30)); + System.out.println("Order " + request.orderId() + ": Starting to prepare " + request.quantity() + " cookies with topping " + request.topping() + " and baking preference " + request.bakingPreference()); + var batterCreationResult = batterService.prepareBatter(new BatterCreationRequest(request.orderId(), request.topping(), request.quantity())); var batter = batterCreationResult.batter(); + + Workflow.await(() -> ovenReady); + var bakeResult = bakeService.bake(new BakeRequest(request.orderId(), batter, request.bakingPreference())); System.out.println("Order " + request.orderId() + ": Your " + bakeResult.cookies().size() + " cookies are ready!"); + + timer.get(); return new CookieOrderResult(request.orderId(), bakeResult.cookies()); } + + @Override + public void ovenReady() { + this.ovenReady = true; + } + + @Override + public CookieOrderStatus getStatus() { + return new CookieOrderStatus(ovenReady, cookieOrder); + } } diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/bake/dtos/BakeRequest.java b/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/bake/dtos/BakeRequest.java deleted file mode 100644 index ee64b42..0000000 --- a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/bake/dtos/BakeRequest.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.takima.temporalpractice.bakery.kitchen.bake.dtos; - -import io.takima.temporalpractice.bakery.kitchen.batter.dtos.Batter; -import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.BakingPreference; - -public record BakeRequest( - String orderId, - Batter batter, - BakingPreference bakingPreference -) { -} - diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/batter/BatterServiceImpl.java b/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/batter/BatterServiceImpl.java deleted file mode 100644 index 2e000fe..0000000 --- a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/batter/BatterServiceImpl.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.takima.temporalpractice.bakery.kitchen.batter; - -import io.takima.temporalpractice.bakery.kitchen.batter.dtos.Batter; -import io.takima.temporalpractice.bakery.kitchen.batter.dtos.BatterCreationRequest; -import io.takima.temporalpractice.bakery.kitchen.batter.dtos.BatterCreationResult; - -public class BatterServiceImpl implements BatterService { - - static final int BATTER_QTY_PER_COOKIE_IN_GRAMS = 30; // in grams - - @Override - public BatterCreationResult prepareBatter(BatterCreationRequest request) { - System.out.println("Mixing flour, sugar, and love for " + request.targetCookieCount() + " cookies with topping " + request.targetTopping()); - return new BatterCreationResult( - new Batter( - request.targetTopping(), - BATTER_QTY_PER_COOKIE_IN_GRAMS * request.targetCookieCount(), - request.targetCookieCount() - )); - } -} diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/batter/dtos/Batter.java b/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/batter/dtos/Batter.java deleted file mode 100644 index 0a703c2..0000000 --- a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/batter/dtos/Batter.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.takima.temporalpractice.bakery.kitchen.batter.dtos; - -import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.Topping; - -public record Batter( - Topping topping, - int quantityInGrams, - int targetCookieCount -) { -} diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/batter/dtos/BatterCreationRequest.java b/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/batter/dtos/BatterCreationRequest.java deleted file mode 100644 index 43d419c..0000000 --- a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/batter/dtos/BatterCreationRequest.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.takima.temporalpractice.bakery.kitchen.batter.dtos; - -import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.Topping; - -public record BatterCreationRequest( - String orderId, - Topping targetTopping, - int targetCookieCount -) { - -} diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/batter/dtos/BatterCreationResult.java b/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/batter/dtos/BatterCreationResult.java deleted file mode 100644 index d350d80..0000000 --- a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/batter/dtos/BatterCreationResult.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.takima.temporalpractice.bakery.kitchen.batter.dtos; - -public record BatterCreationResult(Batter batter) { - -} diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/dtos/KitchenDtos.java b/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/dtos/KitchenDtos.java index 1e90f23..f82ef2e 100644 --- a/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/dtos/KitchenDtos.java +++ b/solutions/day1/part-3.0/temporalpractice/bakery/kitchen/dtos/KitchenDtos.java @@ -1,59 +1,62 @@ package io.takima.temporalpractice.bakery.kitchen.dtos; -import org.checkerframework.checker.units.qual.C; - import java.util.List; import java.util.UUID; public interface KitchenDtos { + enum BakingPreference { + RAW, + SOFT, + COOKED, + BURNT; + + public static BakingPreference random() { + return values()[(int) (Math.random() * values().length)]; + } + } - enum BakingPreference { - RAW, - SOFT, - COOKED, - BURNT; + enum Topping { + CHOCOLATE, + NUTS, + NONE; - public static BakingPreference random() { - return values()[(int) (Math.random() * values().length)]; + public static Topping random() { + return values()[(int) (Math.random() * values().length)]; + } } - } - enum Topping { - CHOCOLATE, - NUTS, - NONE; + record Cookie( + BakingPreference bakingPreference, + Topping topping + ) { + } - public static Topping random() { - return values()[(int) (Math.random() * values().length)]; + record CookieOrderRequest( + String orderId, + int quantity, + BakingPreference bakingPreference, + Topping topping + ) { + + public static CookieOrderRequest random() { + return new CookieOrderRequest( + UUID.randomUUID().toString(), + (int) (Math.random() * 10) + 1, + BakingPreference.random(), + Topping.random() + ); + } } - } - - record Cookie( - BakingPreference bakingPreference, - Topping topping - ) { - } - - record CookieOrderRequest( - String orderId, - int quantity, - BakingPreference bakingPreference, - Topping topping - ) { - - public static CookieOrderRequest random() { - return new CookieOrderRequest( - UUID.randomUUID().toString(), - (int) (Math.random() * 10) + 1, - BakingPreference.random(), - Topping.random() - ); + + record CookieOrderResult( + String orderId, + List<Cookie> cookies + ) { } - } - record CookieOrderResult( - String orderId, - List<Cookie> cookies - ) { - } + record CookieOrderStatus( + boolean ovenReady, + CookieOrderRequest request + ) { + } } diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/temporal/TemporalQueues.java b/solutions/day1/part-3.0/temporalpractice/bakery/temporal/TemporalQueues.java index 0d15137..0c99053 100644 --- a/solutions/day1/part-3.0/temporalpractice/bakery/temporal/TemporalQueues.java +++ b/solutions/day1/part-3.0/temporalpractice/bakery/temporal/TemporalQueues.java @@ -1,5 +1,7 @@ package io.takima.temporalpractice.bakery.temporal; -public abstract class TemporalQueues { - public static final String KITCHEN = "TheOneAndOnlyQueue"; +public class TemporalQueues { + public static final String KITCHEN = "kitchen"; + public static final String BATTER = "batter"; + public static final String BAKE = "bake"; } diff --git a/solutions/day1/part-3.0/temporalpractice/bakery/temporal/TemporalUtils.java b/solutions/day1/part-3.0/temporalpractice/bakery/temporal/TemporalUtils.java index ea0bfa3..f004ff9 100644 --- a/solutions/day1/part-3.0/temporalpractice/bakery/temporal/TemporalUtils.java +++ b/solutions/day1/part-3.0/temporalpractice/bakery/temporal/TemporalUtils.java @@ -48,6 +48,4 @@ public class TemporalUtils { .build() ); } - - } diff --git a/solutions/day1/part-4.1-start/temporalpractice/bakery/OrderCookiesApp.java b/solutions/day1/part-4.1-start/temporalpractice/bakery/OrderCookiesApp.java new file mode 100644 index 0000000..2ff2b24 --- /dev/null +++ b/solutions/day1/part-4.1-start/temporalpractice/bakery/OrderCookiesApp.java @@ -0,0 +1,25 @@ +package io.takima.temporalpractice.bakery; + +import io.takima.temporalpractice.bakery.kitchen.KitchenWorkflow; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderRequest; +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.takima.temporalpractice.bakery.temporal.TemporalUtils; +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; + +public class OrderCookiesApp { + public static void main(String[] args) { + // This app simulates an order of cookies + // It should call the workflow which makes cookies + var orderRequest = CookieOrderRequest.random(); + + KitchenWorkflow workflow = TemporalUtils.newWorkflowStub( + KitchenWorkflow.class, + TemporalQueues.KITCHEN, + "kitchen-" + orderRequest.orderId() + ); + + workflow.makeCookies(orderRequest); // Start the Workflow Execution + } +} diff --git a/solutions/day1/part-4.1-start/temporalpractice/bakery/OrderManyCookies.java b/solutions/day1/part-4.1-start/temporalpractice/bakery/OrderManyCookies.java new file mode 100644 index 0000000..6421088 --- /dev/null +++ b/solutions/day1/part-4.1-start/temporalpractice/bakery/OrderManyCookies.java @@ -0,0 +1,70 @@ +package io.takima.temporalpractice.bakery; + +import io.takima.temporalpractice.bakery.kitchen.KitchenWorkflow; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos; +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.takima.temporalpractice.bakery.temporal.TemporalUtils; +import io.temporal.api.enums.v1.ParentClosePolicy; +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.workflow.*; +import org.slf4j.Logger; + +import java.time.Duration; + +public class OrderManyCookies { + public static void main(String[] args) { + var queue = "manycookies"; + + TemporalUtils.newWorker(queue) + .registerWorkflowImplementationTypes(ManyCookiesImpl.class); + + TemporalUtils.startWorkerFactory(); + + try { + ManyCookies manyCookies = TemporalUtils.CLIENT.newWorkflowStub( + ManyCookies.class, + WorkflowOptions.newBuilder().setWorkflowId("cookie-orderer").setTaskQueue(queue).build() + ); + WorkflowClient.start(manyCookies::continuousOrders); + } catch (Exception e) { + System.out.println("Many cookies already running, skipping..."); + } + } + + public static class ManyCookiesImpl implements ManyCookies { + + private static final Logger logger = Workflow.getLogger(ManyCookiesImpl.class); + + @Override + public void continuousOrders() { + + var options = ChildWorkflowOptions.newBuilder() + .setWorkflowId("order-" + Workflow.randomUUID()) + .setTaskQueue(TemporalQueues.KITCHEN) + .setParentClosePolicy(ParentClosePolicy.PARENT_CLOSE_POLICY_ABANDON) + .build(); + var kitchenWorkflow = Workflow.newChildWorkflowStub(KitchenWorkflow.class, options); + + var quantity = Workflow.newRandom().nextInt(1, 5); + var baking = KitchenDtos.BakingPreference.values()[Workflow.newRandom().nextInt(KitchenDtos.BakingPreference.values().length)]; + var topping = KitchenDtos.Topping.values()[Workflow.newRandom().nextInt(KitchenDtos.Topping.values().length)]; + + var cookiesPreferences = new KitchenDtos.CookieOrderRequest(Workflow.randomUUID().toString(), quantity, baking, topping); + + logger.info("Ordering cookies\n{}", cookiesPreferences); + Async.function(kitchenWorkflow::makeCookies, cookiesPreferences); + + Workflow.sleep(Duration.ofSeconds(10)); + kitchenWorkflow.ovenReady(); + + Workflow.continueAsNew(); + } + } + + @WorkflowInterface + public interface ManyCookies { + @WorkflowMethod + void continuousOrders(); + } +} diff --git a/solutions/day1/part-4.1-start/temporalpractice/bakery/bake/BakeApp.java b/solutions/day1/part-4.1-start/temporalpractice/bakery/bake/BakeApp.java new file mode 100644 index 0000000..2d1cdb1 --- /dev/null +++ b/solutions/day1/part-4.1-start/temporalpractice/bakery/bake/BakeApp.java @@ -0,0 +1,12 @@ +package io.takima.temporalpractice.bakery.bake; + +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.takima.temporalpractice.bakery.temporal.TemporalUtils; + +public class BakeApp { + public static void main(String[] args) { + var worker = TemporalUtils.newWorker(TemporalQueues.BAKE); + worker.registerActivitiesImplementations(new BakeServiceImpl()); + TemporalUtils.startWorkerFactory(); + } +} diff --git a/solutions/day1/part-4.1-start/temporalpractice/bakery/bake/BakeService.java b/solutions/day1/part-4.1-start/temporalpractice/bakery/bake/BakeService.java new file mode 100644 index 0000000..36c0d02 --- /dev/null +++ b/solutions/day1/part-4.1-start/temporalpractice/bakery/bake/BakeService.java @@ -0,0 +1,12 @@ +package io.takima.temporalpractice.bakery.bake; + +import io.takima.temporalpractice.bakery.bake.dtos.BakeRequest; +import io.takima.temporalpractice.bakery.bake.dtos.BakeResult; +import io.temporal.activity.ActivityInterface; +import io.temporal.activity.ActivityMethod; + +@ActivityInterface +public interface BakeService { + @ActivityMethod + BakeResult bake(BakeRequest request); +} diff --git a/solutions/day1/part-4.1-start/temporalpractice/bakery/bake/BakeServiceImpl.java b/solutions/day1/part-4.1-start/temporalpractice/bakery/bake/BakeServiceImpl.java new file mode 100644 index 0000000..22509b6 --- /dev/null +++ b/solutions/day1/part-4.1-start/temporalpractice/bakery/bake/BakeServiceImpl.java @@ -0,0 +1,22 @@ +package io.takima.temporalpractice.bakery.bake; + +import io.takima.temporalpractice.bakery.bake.dtos.BakeRequest; +import io.takima.temporalpractice.bakery.bake.dtos.BakeResult; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.Cookie; + +import java.util.ArrayList; + +public class BakeServiceImpl implements BakeService { + @Override + public BakeResult bake(BakeRequest request) { + System.out.println("Will bake those cookies " + request.bakingPreference()); + var cookies = new ArrayList<Cookie>(); + for (int i = 0; i < request.batter().targetCookieCount(); i++) { + // Making cookies out of batter + var cookie = new Cookie(request.bakingPreference(), request.batter().topping()); + // Add it to the cookies after it is baked + cookies.add(cookie); + } + return new BakeResult(cookies); + } +} diff --git a/solutions/day1/part-4.1-start/temporalpractice/bakery/bake/dtos/BakeRequest.java b/solutions/day1/part-4.1-start/temporalpractice/bakery/bake/dtos/BakeRequest.java new file mode 100644 index 0000000..42701fe --- /dev/null +++ b/solutions/day1/part-4.1-start/temporalpractice/bakery/bake/dtos/BakeRequest.java @@ -0,0 +1,12 @@ +package io.takima.temporalpractice.bakery.bake.dtos; + +import io.takima.temporalpractice.bakery.batter.dtos.Batter; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.BakingPreference; + +public record BakeRequest( + String orderId, + Batter batter, + BakingPreference bakingPreference +) { +} + diff --git a/solutions/day1/part-4.1-start/temporalpractice/bakery/bake/dtos/BakeResult.java b/solutions/day1/part-4.1-start/temporalpractice/bakery/bake/dtos/BakeResult.java new file mode 100644 index 0000000..f9afcf7 --- /dev/null +++ b/solutions/day1/part-4.1-start/temporalpractice/bakery/bake/dtos/BakeResult.java @@ -0,0 +1,8 @@ +package io.takima.temporalpractice.bakery.bake.dtos; + +import java.util.List; + +import static io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.Cookie; + +public record BakeResult(List<Cookie> cookies) { +} diff --git a/solutions/day1/part-4.1-start/temporalpractice/bakery/batter/BatterApp.java b/solutions/day1/part-4.1-start/temporalpractice/bakery/batter/BatterApp.java new file mode 100644 index 0000000..8b4bdb9 --- /dev/null +++ b/solutions/day1/part-4.1-start/temporalpractice/bakery/batter/BatterApp.java @@ -0,0 +1,12 @@ +package io.takima.temporalpractice.bakery.batter; + +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.takima.temporalpractice.bakery.temporal.TemporalUtils; + +public class BatterApp { + public static void main(String[] args) { + var worker = TemporalUtils.newWorker(TemporalQueues.BATTER); + worker.registerActivitiesImplementations(new BatterServiceImpl()); + TemporalUtils.startWorkerFactory(); + } +} diff --git a/solutions/day1/part-4.1-start/temporalpractice/bakery/batter/BatterService.java b/solutions/day1/part-4.1-start/temporalpractice/bakery/batter/BatterService.java new file mode 100644 index 0000000..7da3e9f --- /dev/null +++ b/solutions/day1/part-4.1-start/temporalpractice/bakery/batter/BatterService.java @@ -0,0 +1,13 @@ +package io.takima.temporalpractice.bakery.batter; + +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationRequest; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationResult; +import io.temporal.activity.ActivityInterface; +import io.temporal.activity.ActivityMethod; + +@ActivityInterface +public interface BatterService { + + @ActivityMethod + BatterCreationResult prepareBatter(BatterCreationRequest request); +} diff --git a/solutions/day1/part-4.1-start/temporalpractice/bakery/batter/BatterServiceImpl.java b/solutions/day1/part-4.1-start/temporalpractice/bakery/batter/BatterServiceImpl.java new file mode 100644 index 0000000..b594d07 --- /dev/null +++ b/solutions/day1/part-4.1-start/temporalpractice/bakery/batter/BatterServiceImpl.java @@ -0,0 +1,21 @@ +package io.takima.temporalpractice.bakery.batter; + +import io.takima.temporalpractice.bakery.batter.dtos.Batter; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationRequest; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationResult; + +public class BatterServiceImpl implements BatterService { + + static final int BATTER_QTY_PER_COOKIE_IN_GRAMS = 30; // in grams + + @Override + public BatterCreationResult prepareBatter(BatterCreationRequest request) { + System.out.println("Mixing flour, sugar, and love for " + request.targetCookieCount() + " cookies with topping " + request.targetTopping()); + return new BatterCreationResult( + new Batter( + request.targetTopping(), + BATTER_QTY_PER_COOKIE_IN_GRAMS * request.targetCookieCount(), + request.targetCookieCount() + )); + } +} diff --git a/solutions/day1/part-4.1-start/temporalpractice/bakery/batter/dtos/Batter.java b/solutions/day1/part-4.1-start/temporalpractice/bakery/batter/dtos/Batter.java new file mode 100644 index 0000000..8608c0e --- /dev/null +++ b/solutions/day1/part-4.1-start/temporalpractice/bakery/batter/dtos/Batter.java @@ -0,0 +1,10 @@ +package io.takima.temporalpractice.bakery.batter.dtos; + +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.Topping; + +public record Batter( + Topping topping, + int quantityInGrams, + int targetCookieCount +) { +} diff --git a/solutions/day1/part-4.1-start/temporalpractice/bakery/batter/dtos/BatterCreationRequest.java b/solutions/day1/part-4.1-start/temporalpractice/bakery/batter/dtos/BatterCreationRequest.java new file mode 100644 index 0000000..38cc0c1 --- /dev/null +++ b/solutions/day1/part-4.1-start/temporalpractice/bakery/batter/dtos/BatterCreationRequest.java @@ -0,0 +1,11 @@ +package io.takima.temporalpractice.bakery.batter.dtos; + +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.Topping; + +public record BatterCreationRequest( + String orderId, + Topping targetTopping, + int targetCookieCount +) { + +} diff --git a/solutions/day1/part-4.1-start/temporalpractice/bakery/batter/dtos/BatterCreationResult.java b/solutions/day1/part-4.1-start/temporalpractice/bakery/batter/dtos/BatterCreationResult.java new file mode 100644 index 0000000..d75e6ab --- /dev/null +++ b/solutions/day1/part-4.1-start/temporalpractice/bakery/batter/dtos/BatterCreationResult.java @@ -0,0 +1,5 @@ +package io.takima.temporalpractice.bakery.batter.dtos; + +public record BatterCreationResult(Batter batter) { + +} diff --git a/solutions/day1/part-4.1-start/temporalpractice/bakery/kitchen/KitchenApp.java b/solutions/day1/part-4.1-start/temporalpractice/bakery/kitchen/KitchenApp.java new file mode 100644 index 0000000..4c57367 --- /dev/null +++ b/solutions/day1/part-4.1-start/temporalpractice/bakery/kitchen/KitchenApp.java @@ -0,0 +1,13 @@ +package io.takima.temporalpractice.bakery.kitchen; + +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.takima.temporalpractice.bakery.temporal.TemporalUtils; +import io.temporal.worker.Worker; + +public class KitchenApp { + public static void main(String[] args) { + Worker worker = TemporalUtils.newWorker(TemporalQueues.KITCHEN); + worker.registerWorkflowImplementationTypes(KitchenWorkflowImpl.class); + TemporalUtils.startWorkerFactory(); + } +} diff --git a/solutions/day1/part-4.1-start/temporalpractice/bakery/kitchen/KitchenWorkflow.java b/solutions/day1/part-4.1-start/temporalpractice/bakery/kitchen/KitchenWorkflow.java new file mode 100644 index 0000000..ca26635 --- /dev/null +++ b/solutions/day1/part-4.1-start/temporalpractice/bakery/kitchen/KitchenWorkflow.java @@ -0,0 +1,21 @@ +package io.takima.temporalpractice.bakery.kitchen; + +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderRequest; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderResult; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderStatus; +import io.temporal.workflow.QueryMethod; +import io.temporal.workflow.SignalMethod; +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; + +@WorkflowInterface +public interface KitchenWorkflow { + @WorkflowMethod + CookieOrderResult makeCookies(CookieOrderRequest request); + + @SignalMethod + void ovenReady(); + + @QueryMethod + CookieOrderStatus getStatus(); +} diff --git a/solutions/day1/part-4.1-start/temporalpractice/bakery/kitchen/KitchenWorkflowImpl.java b/solutions/day1/part-4.1-start/temporalpractice/bakery/kitchen/KitchenWorkflowImpl.java new file mode 100644 index 0000000..df0bedc --- /dev/null +++ b/solutions/day1/part-4.1-start/temporalpractice/bakery/kitchen/KitchenWorkflowImpl.java @@ -0,0 +1,57 @@ +package io.takima.temporalpractice.bakery.kitchen; + +import io.takima.temporalpractice.bakery.bake.BakeService; +import io.takima.temporalpractice.bakery.bake.dtos.BakeRequest; +import io.takima.temporalpractice.bakery.batter.BatterService; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationRequest; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderRequest; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderResult; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderStatus; +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.takima.temporalpractice.bakery.temporal.TemporalUtils; +import io.temporal.workflow.Workflow; + +import java.time.Duration; + +public class KitchenWorkflowImpl implements KitchenWorkflow { + private final BatterService batterService; + private final BakeService bakeService; + private boolean ovenReady = false; + private CookieOrderRequest cookieOrder; + + public KitchenWorkflowImpl() { + this.batterService = TemporalUtils.newActivityStub(BatterService.class, TemporalQueues.BATTER); + this.bakeService = TemporalUtils.newActivityStub(BakeService.class, TemporalQueues.BAKE); + } + + + @Override + public CookieOrderResult makeCookies(CookieOrderRequest request) { + this.cookieOrder = request; + var timer = Workflow.newTimer(Duration.ofSeconds(30)); + + System.out.println("Order " + request.orderId() + ": Starting to prepare " + request.quantity() + " cookies with topping " + request.topping() + " and baking preference " + request.bakingPreference()); + + var batterCreationResult = batterService.prepareBatter(new BatterCreationRequest(request.orderId(), request.topping(), request.quantity())); + var batter = batterCreationResult.batter(); + + Workflow.await(() -> ovenReady); + + var bakeResult = bakeService.bake(new BakeRequest(request.orderId(), batter, request.bakingPreference())); + + System.out.println("Order " + request.orderId() + ": Your " + bakeResult.cookies().size() + " cookies are ready!"); + + timer.get(); + return new CookieOrderResult(request.orderId(), bakeResult.cookies()); + } + + @Override + public void ovenReady() { + this.ovenReady = true; + } + + @Override + public CookieOrderStatus getStatus() { + return new CookieOrderStatus(ovenReady, cookieOrder); + } +} diff --git a/solutions/day1/part-4.1-start/temporalpractice/bakery/kitchen/dtos/KitchenDtos.java b/solutions/day1/part-4.1-start/temporalpractice/bakery/kitchen/dtos/KitchenDtos.java new file mode 100644 index 0000000..f82ef2e --- /dev/null +++ b/solutions/day1/part-4.1-start/temporalpractice/bakery/kitchen/dtos/KitchenDtos.java @@ -0,0 +1,62 @@ +package io.takima.temporalpractice.bakery.kitchen.dtos; + +import java.util.List; +import java.util.UUID; + +public interface KitchenDtos { + enum BakingPreference { + RAW, + SOFT, + COOKED, + BURNT; + + public static BakingPreference random() { + return values()[(int) (Math.random() * values().length)]; + } + } + + enum Topping { + CHOCOLATE, + NUTS, + NONE; + + public static Topping random() { + return values()[(int) (Math.random() * values().length)]; + } + } + + record Cookie( + BakingPreference bakingPreference, + Topping topping + ) { + } + + record CookieOrderRequest( + String orderId, + int quantity, + BakingPreference bakingPreference, + Topping topping + ) { + + public static CookieOrderRequest random() { + return new CookieOrderRequest( + UUID.randomUUID().toString(), + (int) (Math.random() * 10) + 1, + BakingPreference.random(), + Topping.random() + ); + } + } + + record CookieOrderResult( + String orderId, + List<Cookie> cookies + ) { + } + + record CookieOrderStatus( + boolean ovenReady, + CookieOrderRequest request + ) { + } +} diff --git a/solutions/day1/part-4.1-start/temporalpractice/bakery/temporal/TemporalQueues.java b/solutions/day1/part-4.1-start/temporalpractice/bakery/temporal/TemporalQueues.java new file mode 100644 index 0000000..0c99053 --- /dev/null +++ b/solutions/day1/part-4.1-start/temporalpractice/bakery/temporal/TemporalQueues.java @@ -0,0 +1,7 @@ +package io.takima.temporalpractice.bakery.temporal; + +public class TemporalQueues { + public static final String KITCHEN = "kitchen"; + public static final String BATTER = "batter"; + public static final String BAKE = "bake"; +} diff --git a/solutions/day1/part-4.1-start/temporalpractice/bakery/temporal/TemporalUtils.java b/solutions/day1/part-4.1-start/temporalpractice/bakery/temporal/TemporalUtils.java new file mode 100644 index 0000000..f004ff9 --- /dev/null +++ b/solutions/day1/part-4.1-start/temporalpractice/bakery/temporal/TemporalUtils.java @@ -0,0 +1,51 @@ +package io.takima.temporalpractice.bakery.temporal; + + +import io.temporal.activity.ActivityOptions; +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; +import io.temporal.workflow.Workflow; + +import java.time.Duration; + +public class TemporalUtils { + + // Represents the connection to your local cluster. For now, lets keep it simple + private static final WorkflowServiceStubs SERVICE_STUBS = WorkflowServiceStubs.newLocalServiceStubs(); + + // Your key for interacting with the Temporal world. + public static final WorkflowClient CLIENT = WorkflowClient.newInstance(SERVICE_STUBS); + + private static final WorkerFactory FACTORY = WorkerFactory.newInstance(CLIENT); + + public static Worker newWorker(String queue) { + return FACTORY.newWorker(queue); + } + + public static void startWorkerFactory() { + FACTORY.start(); + } + + public static <T> T newWorkflowStub(Class<T> workflowInterface, String queue, String workflowId) { + return CLIENT.newWorkflowStub( + workflowInterface, + WorkflowOptions.newBuilder() + .setTaskQueue(queue) + .setWorkflowId(workflowId) + .build() + ); + } + + public static <T> T newActivityStub(Class<T> workflowInterface, String queue) { + return Workflow.newActivityStub( + workflowInterface, + ActivityOptions.newBuilder() + .setTaskQueue(queue) + .setStartToCloseTimeout(Duration.ofSeconds(5)) + .build() + ); + } +} diff --git a/solutions/day1/part-5.1-start/temporalpractice/bakery/CookieWorkflowTest.java b/solutions/day1/part-5.1-start/temporalpractice/bakery/CookieWorkflowTest.java new file mode 100644 index 0000000..aa06ecb --- /dev/null +++ b/solutions/day1/part-5.1-start/temporalpractice/bakery/CookieWorkflowTest.java @@ -0,0 +1,36 @@ +package io.takima.temporalpractice.bakery.cookie; + +import io.takima.temporalpractice.bakery.kitchen.KitchenWorkflow; +import io.temporal.testing.TestWorkflowEnvironment; +import io.temporal.testing.TestWorkflowExtension; +import io.temporal.worker.Worker; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class CookieWorkflowTest { + + @RegisterExtension + public static final TestWorkflowExtension testWorkflowExtension = + TestWorkflowExtension.newBuilder() + //FIXME: register the KitchenWorkflowImpl + //FIXME: add doNotStart() + .build(); + + @BeforeEach + void init(TestWorkflowEnvironment testEnv){ + //FIXME: create bake and batter workers + } + + @Test + public void testWorkflowExecution( + // Test environment for configuration + TestWorkflowEnvironment testEnv, + // Worker for registering additional implementations + Worker worker, + // Auto-created Workflow stub + KitchenWorkflow workflow + ) { + //FIXME: Run your workflow and test it! + } +} -- GitLab From 950c29164b753db97d41a2ce7b1361625dbbb981 Mon Sep 17 00:00:00 2001 From: HCaupert <hcaupert@takima.fr> Date: Wed, 21 May 2025 11:02:10 +0200 Subject: [PATCH 2/2] solutions: more solutions --- .../bakery/CookieWorkflowTest.java | 74 +++ .../bakery/CookieWorkflowTest.java | 100 ++++ .../bakery/OrderCookiesApp.java | 25 + .../bakery/OrderManyCookies.java | 70 +++ .../temporalpractice/bakery/bake/BakeApp.java | 12 + .../bakery/bake/BakeService.java | 12 + .../bakery/bake/BakeServiceImpl.java | 22 + .../bakery/bake/dtos/BakeRequest.java | 12 + .../bakery/bake/dtos/BakeResult.java | 8 + .../bakery/batter/BatterApp.java | 12 + .../bakery/batter/BatterService.java | 17 + .../bakery/batter/BatterServiceImpl.java | 27 ++ .../bakery/batter/dtos/Batter.java | 10 + .../batter/dtos/BatterCleanupRequest.java | 4 + .../batter/dtos/BatterCreationRequest.java | 11 + .../batter/dtos/BatterCreationResult.java | 5 + .../bakery/kitchen/KitchenApp.java | 13 + .../bakery/kitchen/KitchenWorkflow.java | 21 + .../bakery/kitchen/KitchenWorkflowImpl.java | 72 +++ .../bakery/kitchen/dtos/KitchenDtos.java | 62 +++ .../temporal/TemporalActivityFactory.java | 20 + .../bakery/temporal/TemporalQueues.java | 7 + .../bakery/temporal/TemporalUtils.java | 36 ++ .../part-1.3/main/resources/application.yml | 3 + .../bakery/cookie/CookieWorkflowTest.java | 122 +++++ .../day2/part-2.1/bakery/OrderCookiesApp.java | 25 + .../part-2.1/bakery/OrderManyCookies.java | 70 +++ .../day2/part-2.1/bakery/bake/BakeApp.java | 12 + .../part-2.1/bakery/bake/BakeService.java | 12 + .../part-2.1/bakery/bake/BakeServiceImpl.java | 22 + .../bakery/bake/dtos/BakeRequest.java | 12 + .../part-2.1/bakery/bake/dtos/BakeResult.java | 8 + .../part-2.1/bakery/batter/BatterApp.java | 12 + .../part-2.1/bakery/batter/BatterService.java | 17 + .../bakery/batter/BatterServiceImpl.java | 27 ++ .../part-2.1/bakery/batter/dtos/Batter.java | 10 + .../batter/dtos/BatterCleanupRequest.java | 4 + .../batter/dtos/BatterCreationRequest.java | 11 + .../batter/dtos/BatterCreationResult.java | 5 + .../part-2.1/bakery/kitchen/KitchenApp.java | 14 + .../bakery/kitchen/KitchenWorkflow.java | 23 + .../bakery/kitchen/KitchenWorkflowImpl.java | 72 +++ .../kitchen/KitchenWorkflowImplLegacy.java | 73 +++ .../bakery/kitchen/KitchenWorkflowLegacy.java | 24 + .../bakery/kitchen/dtos/KitchenDtos.java | 62 +++ .../temporal/TemporalActivityFactory.java | 20 + .../bakery/temporal/TemporalQueues.java | 7 + .../bakery/temporal/TemporalUtils.java | 36 ++ .../java/io/takima/temporalpractice/.gitkeep | 0 .../bakery/cookie/CookieWorkflowTest.java | 130 ++++++ .../day2/part-2.2/test/resources/history.json | 437 ++++++++++++++++++ 51 files changed, 1922 insertions(+) create mode 100644 solutions/day1/part-5.1-end/temporalpractice/bakery/CookieWorkflowTest.java create mode 100644 solutions/day1/part-5.2-end/temporalpractice/bakery/CookieWorkflowTest.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/OrderCookiesApp.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/OrderManyCookies.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/bake/BakeApp.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/bake/BakeService.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/bake/BakeServiceImpl.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/bake/dtos/BakeRequest.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/bake/dtos/BakeResult.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/BatterApp.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/BatterService.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/BatterServiceImpl.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/dtos/Batter.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/dtos/BatterCleanupRequest.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/dtos/BatterCreationRequest.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/dtos/BatterCreationResult.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/kitchen/KitchenApp.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/kitchen/KitchenWorkflow.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/kitchen/KitchenWorkflowImpl.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/kitchen/dtos/KitchenDtos.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/temporal/TemporalActivityFactory.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/temporal/TemporalQueues.java create mode 100644 solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/temporal/TemporalUtils.java create mode 100644 solutions/day2/part-1.3/main/resources/application.yml create mode 100644 solutions/day2/part-1.3/test/java/io/takima/temporalpractice/bakery/cookie/CookieWorkflowTest.java create mode 100644 solutions/day2/part-2.1/bakery/OrderCookiesApp.java create mode 100644 solutions/day2/part-2.1/bakery/OrderManyCookies.java create mode 100644 solutions/day2/part-2.1/bakery/bake/BakeApp.java create mode 100644 solutions/day2/part-2.1/bakery/bake/BakeService.java create mode 100644 solutions/day2/part-2.1/bakery/bake/BakeServiceImpl.java create mode 100644 solutions/day2/part-2.1/bakery/bake/dtos/BakeRequest.java create mode 100644 solutions/day2/part-2.1/bakery/bake/dtos/BakeResult.java create mode 100644 solutions/day2/part-2.1/bakery/batter/BatterApp.java create mode 100644 solutions/day2/part-2.1/bakery/batter/BatterService.java create mode 100644 solutions/day2/part-2.1/bakery/batter/BatterServiceImpl.java create mode 100644 solutions/day2/part-2.1/bakery/batter/dtos/Batter.java create mode 100644 solutions/day2/part-2.1/bakery/batter/dtos/BatterCleanupRequest.java create mode 100644 solutions/day2/part-2.1/bakery/batter/dtos/BatterCreationRequest.java create mode 100644 solutions/day2/part-2.1/bakery/batter/dtos/BatterCreationResult.java create mode 100644 solutions/day2/part-2.1/bakery/kitchen/KitchenApp.java create mode 100644 solutions/day2/part-2.1/bakery/kitchen/KitchenWorkflow.java create mode 100644 solutions/day2/part-2.1/bakery/kitchen/KitchenWorkflowImpl.java create mode 100644 solutions/day2/part-2.1/bakery/kitchen/KitchenWorkflowImplLegacy.java create mode 100644 solutions/day2/part-2.1/bakery/kitchen/KitchenWorkflowLegacy.java create mode 100644 solutions/day2/part-2.1/bakery/kitchen/dtos/KitchenDtos.java create mode 100644 solutions/day2/part-2.1/bakery/temporal/TemporalActivityFactory.java create mode 100644 solutions/day2/part-2.1/bakery/temporal/TemporalQueues.java create mode 100644 solutions/day2/part-2.1/bakery/temporal/TemporalUtils.java create mode 100644 solutions/day2/part-2.2/test/java/io/takima/temporalpractice/.gitkeep create mode 100644 solutions/day2/part-2.2/test/java/io/takima/temporalpractice/bakery/cookie/CookieWorkflowTest.java create mode 100644 solutions/day2/part-2.2/test/resources/history.json diff --git a/solutions/day1/part-5.1-end/temporalpractice/bakery/CookieWorkflowTest.java b/solutions/day1/part-5.1-end/temporalpractice/bakery/CookieWorkflowTest.java new file mode 100644 index 0000000..5de07e2 --- /dev/null +++ b/solutions/day1/part-5.1-end/temporalpractice/bakery/CookieWorkflowTest.java @@ -0,0 +1,74 @@ +package io.takima.temporalpractice.bakery.cookie; + +import io.takima.temporalpractice.bakery.bake.BakeServiceImpl; +import io.takima.temporalpractice.bakery.batter.BatterServiceImpl; +import io.takima.temporalpractice.bakery.kitchen.KitchenWorkflow; +import io.takima.temporalpractice.bakery.kitchen.KitchenWorkflowImpl; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos; +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.temporal.client.WorkflowClient; +import io.temporal.testing.TestWorkflowEnvironment; +import io.temporal.testing.TestWorkflowExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import java.time.Duration; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CookieWorkflowTest { + + @RegisterExtension + public static final TestWorkflowExtension testWorkflowExtension = + TestWorkflowExtension.newBuilder() + .registerWorkflowImplementationTypes(KitchenWorkflowImpl.class) + .setDoNotStart(true) + .build(); + + @BeforeEach + void init(TestWorkflowEnvironment testEnv){ + testEnv.newWorker(TemporalQueues.BAKE) + .registerActivitiesImplementations(new BakeServiceImpl()); + testEnv.newWorker(TemporalQueues.BATTER) + .registerActivitiesImplementations(new BatterServiceImpl()); + testEnv.start(); + } + + @Test + public void shouldMakeCookies( + // Test environment for configuration + TestWorkflowEnvironment testEnv, + // Auto-created Workflow stub + KitchenWorkflow workflow + ) { + // get the env starting time + var startTime = testEnv.currentTimeMillis(); + + // Start the workflow asynchronously + var request = KitchenDtos.CookieOrderRequest.random(); + WorkflowClient.start(workflow::makeCookies, request); + + var status = workflow.getStatus(); + assertThat(status.ovenReady()).isFalse(); + assertThat(status.request()).isEqualTo(request); + + workflow.ovenReady(); + status = workflow.getStatus(); + assertThat(status.ovenReady()).isTrue(); + + // Get the workflow result + var result = workflow.makeCookies(request); + + assertThat(result.orderId()).isEqualTo(request.orderId()); + assertThat(result.cookies()) + .hasSize(request.quantity()) + .allSatisfy(cookie -> { + assertThat(cookie.bakingPreference()).isEqualTo(request.bakingPreference()); + assertThat(cookie.topping()).isEqualTo(request.topping()); + }); + + var duration = Duration.ofMillis(testEnv.currentTimeMillis() - startTime); + assertThat(duration).isGreaterThan(Duration.ofSeconds(30)); + } +} diff --git a/solutions/day1/part-5.2-end/temporalpractice/bakery/CookieWorkflowTest.java b/solutions/day1/part-5.2-end/temporalpractice/bakery/CookieWorkflowTest.java new file mode 100644 index 0000000..9d37dd4 --- /dev/null +++ b/solutions/day1/part-5.2-end/temporalpractice/bakery/CookieWorkflowTest.java @@ -0,0 +1,100 @@ +package io.takima.temporalpractice.bakery.cookie; + +import io.takima.temporalpractice.bakery.bake.BakeService; +import io.takima.temporalpractice.bakery.bake.dtos.BakeRequest; +import io.takima.temporalpractice.bakery.bake.dtos.BakeResult; +import io.takima.temporalpractice.bakery.batter.BatterService; +import io.takima.temporalpractice.bakery.batter.dtos.Batter; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationRequest; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationResult; +import io.takima.temporalpractice.bakery.kitchen.KitchenWorkflow; +import io.takima.temporalpractice.bakery.kitchen.KitchenWorkflowImpl; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos; +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.temporal.client.WorkflowClient; +import io.temporal.testing.TestWorkflowEnvironment; +import io.temporal.testing.TestWorkflowExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.Duration; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class CookieWorkflowTest { + + @Mock(withoutAnnotations = true) + private BakeService bakeService; + + @Mock(withoutAnnotations = true) + private BatterService batterService; + + + @RegisterExtension + public static final TestWorkflowExtension testWorkflowExtension = + TestWorkflowExtension.newBuilder() + .registerWorkflowImplementationTypes(KitchenWorkflowImpl.class) + .setDoNotStart(true) + .build(); + + @BeforeEach + void init(TestWorkflowEnvironment testEnv) { + testEnv.newWorker(TemporalQueues.BAKE) + .registerActivitiesImplementations(bakeService); + testEnv.newWorker(TemporalQueues.BATTER) + .registerActivitiesImplementations(batterService); + testEnv.start(); + } + + KitchenDtos.CookieOrderRequest request = KitchenDtos.CookieOrderRequest.random(); + BatterCreationRequest batterCreationRequest = new BatterCreationRequest(request.orderId(), request.topping(), request.quantity()); + BatterCreationResult batterCreationResult = new BatterCreationResult(new Batter(request.topping(), 30 * request.quantity(), request.quantity())); + BakeRequest bakeRequest = new BakeRequest(request.orderId(), batterCreationResult.batter(), request.bakingPreference()); + BakeResult bakeResult = new BakeResult(List.of(new KitchenDtos.Cookie(request.bakingPreference(), request.topping()))); + + @Test + public void shouldMakeCookies( + // Test environment for configuration + TestWorkflowEnvironment testEnv, + // Auto-created Workflow stub + KitchenWorkflow workflow + ) { + // get the env starting time + var startTime = testEnv.currentTimeMillis(); + + // Start the workflow asynchronously + WorkflowClient.start(workflow::makeCookies, request); + when(batterService.prepareBatter(batterCreationRequest)) + .thenReturn(batterCreationResult); + // Make sure we dont bake before the oven is ready + verifyNoInteractions(bakeService); + + var status = workflow.getStatus(); + assertThat(status.ovenReady()).isFalse(); + assertThat(status.request()).isEqualTo(request); + + workflow.ovenReady(); + status = workflow.getStatus(); + assertThat(status.ovenReady()).isTrue(); + when(bakeService.bake(bakeRequest)).thenReturn(bakeResult); + + // Get the workflow result + var result = workflow.makeCookies(request); + + assertThat(result.orderId()).isEqualTo(request.orderId()); + assertThat(result.cookies()) + .isEqualTo(bakeResult.cookies()); + + var duration = Duration.ofMillis(testEnv.currentTimeMillis() - startTime); + assertThat(duration).isGreaterThan(Duration.ofSeconds(30)); + } +} diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/OrderCookiesApp.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/OrderCookiesApp.java new file mode 100644 index 0000000..2ff2b24 --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/OrderCookiesApp.java @@ -0,0 +1,25 @@ +package io.takima.temporalpractice.bakery; + +import io.takima.temporalpractice.bakery.kitchen.KitchenWorkflow; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderRequest; +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.takima.temporalpractice.bakery.temporal.TemporalUtils; +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; + +public class OrderCookiesApp { + public static void main(String[] args) { + // This app simulates an order of cookies + // It should call the workflow which makes cookies + var orderRequest = CookieOrderRequest.random(); + + KitchenWorkflow workflow = TemporalUtils.newWorkflowStub( + KitchenWorkflow.class, + TemporalQueues.KITCHEN, + "kitchen-" + orderRequest.orderId() + ); + + workflow.makeCookies(orderRequest); // Start the Workflow Execution + } +} diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/OrderManyCookies.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/OrderManyCookies.java new file mode 100644 index 0000000..6421088 --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/OrderManyCookies.java @@ -0,0 +1,70 @@ +package io.takima.temporalpractice.bakery; + +import io.takima.temporalpractice.bakery.kitchen.KitchenWorkflow; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos; +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.takima.temporalpractice.bakery.temporal.TemporalUtils; +import io.temporal.api.enums.v1.ParentClosePolicy; +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.workflow.*; +import org.slf4j.Logger; + +import java.time.Duration; + +public class OrderManyCookies { + public static void main(String[] args) { + var queue = "manycookies"; + + TemporalUtils.newWorker(queue) + .registerWorkflowImplementationTypes(ManyCookiesImpl.class); + + TemporalUtils.startWorkerFactory(); + + try { + ManyCookies manyCookies = TemporalUtils.CLIENT.newWorkflowStub( + ManyCookies.class, + WorkflowOptions.newBuilder().setWorkflowId("cookie-orderer").setTaskQueue(queue).build() + ); + WorkflowClient.start(manyCookies::continuousOrders); + } catch (Exception e) { + System.out.println("Many cookies already running, skipping..."); + } + } + + public static class ManyCookiesImpl implements ManyCookies { + + private static final Logger logger = Workflow.getLogger(ManyCookiesImpl.class); + + @Override + public void continuousOrders() { + + var options = ChildWorkflowOptions.newBuilder() + .setWorkflowId("order-" + Workflow.randomUUID()) + .setTaskQueue(TemporalQueues.KITCHEN) + .setParentClosePolicy(ParentClosePolicy.PARENT_CLOSE_POLICY_ABANDON) + .build(); + var kitchenWorkflow = Workflow.newChildWorkflowStub(KitchenWorkflow.class, options); + + var quantity = Workflow.newRandom().nextInt(1, 5); + var baking = KitchenDtos.BakingPreference.values()[Workflow.newRandom().nextInt(KitchenDtos.BakingPreference.values().length)]; + var topping = KitchenDtos.Topping.values()[Workflow.newRandom().nextInt(KitchenDtos.Topping.values().length)]; + + var cookiesPreferences = new KitchenDtos.CookieOrderRequest(Workflow.randomUUID().toString(), quantity, baking, topping); + + logger.info("Ordering cookies\n{}", cookiesPreferences); + Async.function(kitchenWorkflow::makeCookies, cookiesPreferences); + + Workflow.sleep(Duration.ofSeconds(10)); + kitchenWorkflow.ovenReady(); + + Workflow.continueAsNew(); + } + } + + @WorkflowInterface + public interface ManyCookies { + @WorkflowMethod + void continuousOrders(); + } +} diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/bake/BakeApp.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/bake/BakeApp.java new file mode 100644 index 0000000..2d1cdb1 --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/bake/BakeApp.java @@ -0,0 +1,12 @@ +package io.takima.temporalpractice.bakery.bake; + +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.takima.temporalpractice.bakery.temporal.TemporalUtils; + +public class BakeApp { + public static void main(String[] args) { + var worker = TemporalUtils.newWorker(TemporalQueues.BAKE); + worker.registerActivitiesImplementations(new BakeServiceImpl()); + TemporalUtils.startWorkerFactory(); + } +} diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/bake/BakeService.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/bake/BakeService.java new file mode 100644 index 0000000..36c0d02 --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/bake/BakeService.java @@ -0,0 +1,12 @@ +package io.takima.temporalpractice.bakery.bake; + +import io.takima.temporalpractice.bakery.bake.dtos.BakeRequest; +import io.takima.temporalpractice.bakery.bake.dtos.BakeResult; +import io.temporal.activity.ActivityInterface; +import io.temporal.activity.ActivityMethod; + +@ActivityInterface +public interface BakeService { + @ActivityMethod + BakeResult bake(BakeRequest request); +} diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/bake/BakeServiceImpl.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/bake/BakeServiceImpl.java new file mode 100644 index 0000000..22509b6 --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/bake/BakeServiceImpl.java @@ -0,0 +1,22 @@ +package io.takima.temporalpractice.bakery.bake; + +import io.takima.temporalpractice.bakery.bake.dtos.BakeRequest; +import io.takima.temporalpractice.bakery.bake.dtos.BakeResult; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.Cookie; + +import java.util.ArrayList; + +public class BakeServiceImpl implements BakeService { + @Override + public BakeResult bake(BakeRequest request) { + System.out.println("Will bake those cookies " + request.bakingPreference()); + var cookies = new ArrayList<Cookie>(); + for (int i = 0; i < request.batter().targetCookieCount(); i++) { + // Making cookies out of batter + var cookie = new Cookie(request.bakingPreference(), request.batter().topping()); + // Add it to the cookies after it is baked + cookies.add(cookie); + } + return new BakeResult(cookies); + } +} diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/bake/dtos/BakeRequest.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/bake/dtos/BakeRequest.java new file mode 100644 index 0000000..42701fe --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/bake/dtos/BakeRequest.java @@ -0,0 +1,12 @@ +package io.takima.temporalpractice.bakery.bake.dtos; + +import io.takima.temporalpractice.bakery.batter.dtos.Batter; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.BakingPreference; + +public record BakeRequest( + String orderId, + Batter batter, + BakingPreference bakingPreference +) { +} + diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/bake/dtos/BakeResult.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/bake/dtos/BakeResult.java new file mode 100644 index 0000000..f9afcf7 --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/bake/dtos/BakeResult.java @@ -0,0 +1,8 @@ +package io.takima.temporalpractice.bakery.bake.dtos; + +import java.util.List; + +import static io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.Cookie; + +public record BakeResult(List<Cookie> cookies) { +} diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/BatterApp.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/BatterApp.java new file mode 100644 index 0000000..8b4bdb9 --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/BatterApp.java @@ -0,0 +1,12 @@ +package io.takima.temporalpractice.bakery.batter; + +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.takima.temporalpractice.bakery.temporal.TemporalUtils; + +public class BatterApp { + public static void main(String[] args) { + var worker = TemporalUtils.newWorker(TemporalQueues.BATTER); + worker.registerActivitiesImplementations(new BatterServiceImpl()); + TemporalUtils.startWorkerFactory(); + } +} diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/BatterService.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/BatterService.java new file mode 100644 index 0000000..1353c54 --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/BatterService.java @@ -0,0 +1,17 @@ +package io.takima.temporalpractice.bakery.batter; + +import io.takima.temporalpractice.bakery.batter.dtos.BatterCleanupRequest; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationRequest; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationResult; +import io.temporal.activity.ActivityInterface; +import io.temporal.activity.ActivityMethod; + +@ActivityInterface +public interface BatterService { + + @ActivityMethod + BatterCreationResult prepareBatter(BatterCreationRequest request); + + @ActivityMethod + void cleanupBatterIfNeeded(BatterCleanupRequest request); +} diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/BatterServiceImpl.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/BatterServiceImpl.java new file mode 100644 index 0000000..17a2e83 --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/BatterServiceImpl.java @@ -0,0 +1,27 @@ +package io.takima.temporalpractice.bakery.batter; + +import io.takima.temporalpractice.bakery.batter.dtos.Batter; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCleanupRequest; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationRequest; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationResult; + +public class BatterServiceImpl implements BatterService { + + static final int BATTER_QTY_PER_COOKIE_IN_GRAMS = 30; // in grams + + @Override + public BatterCreationResult prepareBatter(BatterCreationRequest request) { + System.out.println("Mixing flour, sugar, and love for " + request.targetCookieCount() + " cookies with topping " + request.targetTopping()); + return new BatterCreationResult( + new Batter( + request.targetTopping(), + BATTER_QTY_PER_COOKIE_IN_GRAMS * request.targetCookieCount(), + request.targetCookieCount() + )); + } + + @Override + public void cleanupBatterIfNeeded(BatterCleanupRequest request) { + System.out.println("Cleaning up batter of order " + request.orderId()); + } +} diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/dtos/Batter.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/dtos/Batter.java new file mode 100644 index 0000000..8608c0e --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/dtos/Batter.java @@ -0,0 +1,10 @@ +package io.takima.temporalpractice.bakery.batter.dtos; + +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.Topping; + +public record Batter( + Topping topping, + int quantityInGrams, + int targetCookieCount +) { +} diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/dtos/BatterCleanupRequest.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/dtos/BatterCleanupRequest.java new file mode 100644 index 0000000..26a9ea7 --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/dtos/BatterCleanupRequest.java @@ -0,0 +1,4 @@ +package io.takima.temporalpractice.bakery.batter.dtos; + +public record BatterCleanupRequest(String orderId) { +} diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/dtos/BatterCreationRequest.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/dtos/BatterCreationRequest.java new file mode 100644 index 0000000..38cc0c1 --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/dtos/BatterCreationRequest.java @@ -0,0 +1,11 @@ +package io.takima.temporalpractice.bakery.batter.dtos; + +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.Topping; + +public record BatterCreationRequest( + String orderId, + Topping targetTopping, + int targetCookieCount +) { + +} diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/dtos/BatterCreationResult.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/dtos/BatterCreationResult.java new file mode 100644 index 0000000..d75e6ab --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/batter/dtos/BatterCreationResult.java @@ -0,0 +1,5 @@ +package io.takima.temporalpractice.bakery.batter.dtos; + +public record BatterCreationResult(Batter batter) { + +} diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/kitchen/KitchenApp.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/kitchen/KitchenApp.java new file mode 100644 index 0000000..4c57367 --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/kitchen/KitchenApp.java @@ -0,0 +1,13 @@ +package io.takima.temporalpractice.bakery.kitchen; + +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.takima.temporalpractice.bakery.temporal.TemporalUtils; +import io.temporal.worker.Worker; + +public class KitchenApp { + public static void main(String[] args) { + Worker worker = TemporalUtils.newWorker(TemporalQueues.KITCHEN); + worker.registerWorkflowImplementationTypes(KitchenWorkflowImpl.class); + TemporalUtils.startWorkerFactory(); + } +} diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/kitchen/KitchenWorkflow.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/kitchen/KitchenWorkflow.java new file mode 100644 index 0000000..ca26635 --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/kitchen/KitchenWorkflow.java @@ -0,0 +1,21 @@ +package io.takima.temporalpractice.bakery.kitchen; + +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderRequest; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderResult; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderStatus; +import io.temporal.workflow.QueryMethod; +import io.temporal.workflow.SignalMethod; +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; + +@WorkflowInterface +public interface KitchenWorkflow { + @WorkflowMethod + CookieOrderResult makeCookies(CookieOrderRequest request); + + @SignalMethod + void ovenReady(); + + @QueryMethod + CookieOrderStatus getStatus(); +} diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/kitchen/KitchenWorkflowImpl.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/kitchen/KitchenWorkflowImpl.java new file mode 100644 index 0000000..ef842cf --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/kitchen/KitchenWorkflowImpl.java @@ -0,0 +1,72 @@ +package io.takima.temporalpractice.bakery.kitchen; + +import io.takima.temporalpractice.bakery.bake.BakeService; +import io.takima.temporalpractice.bakery.bake.dtos.BakeRequest; +import io.takima.temporalpractice.bakery.batter.BatterService; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCleanupRequest; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationRequest; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderRequest; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderResult; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderStatus; +import io.takima.temporalpractice.bakery.temporal.TemporalActivityFactory; +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.temporal.failure.ActivityFailure; +import io.temporal.workflow.Saga; +import io.temporal.workflow.Workflow; +import org.slf4j.Logger; + +import java.time.Duration; +import java.util.List; + +public class KitchenWorkflowImpl implements KitchenWorkflow { + private final BatterService batterService; + private final BakeService bakeService; + private boolean ovenReady = false; + private CookieOrderRequest cookieOrder; + + private final Logger logger = Workflow.getLogger(KitchenWorkflowImpl.class); + + private final Saga saga = new Saga(new Saga.Options.Builder().build()); + + public KitchenWorkflowImpl() { + this.batterService = TemporalActivityFactory.newActivityStub(BatterService.class, TemporalQueues.BATTER); + this.bakeService = TemporalActivityFactory.newActivityStub(BakeService.class, TemporalQueues.BAKE); + } + + + @Override + public CookieOrderResult makeCookies(CookieOrderRequest request) { + this.cookieOrder = request; + var timer = Workflow.newTimer(Duration.ofSeconds(30)); + + try { + saga.addCompensation(batterService::cleanupBatterIfNeeded, new BatterCleanupRequest(request.orderId())); + + logger.info("Order {}: Starting to prepare {} cookies with topping {} and baking preference {}", request.orderId(), request.quantity(), request.topping(), request.bakingPreference()); + var batterCreationResult = batterService.prepareBatter(new BatterCreationRequest(request.orderId(), request.topping(), request.quantity())); + var batter = batterCreationResult.batter(); + + Workflow.await(() -> ovenReady); + + var bakeResult = bakeService.bake(new BakeRequest(request.orderId(), batter, request.bakingPreference())); + + logger.info("Order {}: Your {} cookies are ready!", request.orderId(), bakeResult.cookies().size()); + + timer.get(); + return new CookieOrderResult(request.orderId(), bakeResult.cookies()); + } catch (ActivityFailure failures) { + saga.compensate(); + return new CookieOrderResult(request.orderId(), List.of()); + } + } + + @Override + public void ovenReady() { + this.ovenReady = true; + } + + @Override + public CookieOrderStatus getStatus() { + return new CookieOrderStatus(ovenReady, cookieOrder); + } +} diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/kitchen/dtos/KitchenDtos.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/kitchen/dtos/KitchenDtos.java new file mode 100644 index 0000000..f82ef2e --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/kitchen/dtos/KitchenDtos.java @@ -0,0 +1,62 @@ +package io.takima.temporalpractice.bakery.kitchen.dtos; + +import java.util.List; +import java.util.UUID; + +public interface KitchenDtos { + enum BakingPreference { + RAW, + SOFT, + COOKED, + BURNT; + + public static BakingPreference random() { + return values()[(int) (Math.random() * values().length)]; + } + } + + enum Topping { + CHOCOLATE, + NUTS, + NONE; + + public static Topping random() { + return values()[(int) (Math.random() * values().length)]; + } + } + + record Cookie( + BakingPreference bakingPreference, + Topping topping + ) { + } + + record CookieOrderRequest( + String orderId, + int quantity, + BakingPreference bakingPreference, + Topping topping + ) { + + public static CookieOrderRequest random() { + return new CookieOrderRequest( + UUID.randomUUID().toString(), + (int) (Math.random() * 10) + 1, + BakingPreference.random(), + Topping.random() + ); + } + } + + record CookieOrderResult( + String orderId, + List<Cookie> cookies + ) { + } + + record CookieOrderStatus( + boolean ovenReady, + CookieOrderRequest request + ) { + } +} diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/temporal/TemporalActivityFactory.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/temporal/TemporalActivityFactory.java new file mode 100644 index 0000000..ec51a0e --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/temporal/TemporalActivityFactory.java @@ -0,0 +1,20 @@ +package io.takima.temporalpractice.bakery.temporal; + +import io.temporal.activity.ActivityOptions; +import io.temporal.common.RetryOptions; +import io.temporal.workflow.Workflow; + +import java.time.Duration; + +public class TemporalActivityFactory { + public static <T> T newActivityStub(Class<T> workflowInterface, String queue) { + return Workflow.newActivityStub( + workflowInterface, + ActivityOptions.newBuilder() + .setTaskQueue(queue) + .setRetryOptions(RetryOptions.newBuilder().setMaximumAttempts(2).build()) + .setStartToCloseTimeout(Duration.ofSeconds(5)) + .build() + ); + } +} diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/temporal/TemporalQueues.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/temporal/TemporalQueues.java new file mode 100644 index 0000000..0c99053 --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/temporal/TemporalQueues.java @@ -0,0 +1,7 @@ +package io.takima.temporalpractice.bakery.temporal; + +public class TemporalQueues { + public static final String KITCHEN = "kitchen"; + public static final String BATTER = "batter"; + public static final String BAKE = "bake"; +} diff --git a/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/temporal/TemporalUtils.java b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/temporal/TemporalUtils.java new file mode 100644 index 0000000..101f7e6 --- /dev/null +++ b/solutions/day2/part-1.3/main/java/io/takima/temporalpractice/bakery/temporal/TemporalUtils.java @@ -0,0 +1,36 @@ +package io.takima.temporalpractice.bakery.temporal; + +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; + +public class TemporalUtils { + + // Represents the connection to your local cluster. For now, lets keep it simple + private static final WorkflowServiceStubs SERVICE_STUBS = WorkflowServiceStubs.newLocalServiceStubs(); + + // Your key for interacting with the Temporal world. + public static final WorkflowClient CLIENT = WorkflowClient.newInstance(SERVICE_STUBS); + + private static final WorkerFactory FACTORY = WorkerFactory.newInstance(CLIENT); + + public static Worker newWorker(String queue) { + return FACTORY.newWorker(queue); + } + + public static void startWorkerFactory() { + FACTORY.start(); + } + + public static <T> T newWorkflowStub(Class<T> workflowInterface, String queue, String workflowId) { + return CLIENT.newWorkflowStub( + workflowInterface, + WorkflowOptions.newBuilder() + .setTaskQueue(queue) + .setWorkflowId(workflowId) + .build() + ); + } +} diff --git a/solutions/day2/part-1.3/main/resources/application.yml b/solutions/day2/part-1.3/main/resources/application.yml new file mode 100644 index 0000000..13906e5 --- /dev/null +++ b/solutions/day2/part-1.3/main/resources/application.yml @@ -0,0 +1,3 @@ +spring: + application: + name: Temporal Practice diff --git a/solutions/day2/part-1.3/test/java/io/takima/temporalpractice/bakery/cookie/CookieWorkflowTest.java b/solutions/day2/part-1.3/test/java/io/takima/temporalpractice/bakery/cookie/CookieWorkflowTest.java new file mode 100644 index 0000000..748902f --- /dev/null +++ b/solutions/day2/part-1.3/test/java/io/takima/temporalpractice/bakery/cookie/CookieWorkflowTest.java @@ -0,0 +1,122 @@ +package io.takima.temporalpractice.bakery.cookie; + +import io.takima.temporalpractice.bakery.bake.BakeService; +import io.takima.temporalpractice.bakery.bake.dtos.BakeRequest; +import io.takima.temporalpractice.bakery.bake.dtos.BakeResult; +import io.takima.temporalpractice.bakery.batter.BatterService; +import io.takima.temporalpractice.bakery.batter.dtos.Batter; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCleanupRequest; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationRequest; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationResult; +import io.takima.temporalpractice.bakery.kitchen.KitchenWorkflow; +import io.takima.temporalpractice.bakery.kitchen.KitchenWorkflowImpl; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos; +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.temporal.client.WorkflowClient; +import io.temporal.testing.TestWorkflowEnvironment; +import io.temporal.testing.TestWorkflowExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.Duration; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class CookieWorkflowTest { + + @Mock(withoutAnnotations = true) + private BakeService bakeService; + + @Mock(withoutAnnotations = true) + private BatterService batterService; + + + @RegisterExtension + public static final TestWorkflowExtension testWorkflowExtension = + TestWorkflowExtension.newBuilder() + .registerWorkflowImplementationTypes(KitchenWorkflowImpl.class) + .setDoNotStart(true) + .build(); + + @BeforeEach + void init(TestWorkflowEnvironment testEnv) { + testEnv.newWorker(TemporalQueues.BAKE) + .registerActivitiesImplementations(bakeService); + testEnv.newWorker(TemporalQueues.BATTER) + .registerActivitiesImplementations(batterService); + testEnv.start(); + } + + KitchenDtos.CookieOrderRequest request = KitchenDtos.CookieOrderRequest.random(); + BatterCreationRequest batterCreationRequest = new BatterCreationRequest(request.orderId(), request.topping(), request.quantity()); + BatterCreationResult batterCreationResult = new BatterCreationResult(new Batter(request.topping(), 30 * request.quantity(), request.quantity())); + BakeRequest bakeRequest = new BakeRequest(request.orderId(), batterCreationResult.batter(), request.bakingPreference()); + BakeResult bakeResult = new BakeResult(List.of(new KitchenDtos.Cookie(request.bakingPreference(), request.topping()))); + + @Test + public void shouldCleanupBatter( + // Test environment for configuration + TestWorkflowEnvironment testEnv, + // Auto-created Workflow stub + KitchenWorkflow workflow + ) { + WorkflowClient.start(workflow::makeCookies, request); + + when(batterService.prepareBatter(batterCreationRequest)) + .thenReturn(batterCreationResult); + workflow.ovenReady(); + when(bakeService.bake(bakeRequest)) + .thenThrow(new RuntimeException("I will always fail")); + + var result = workflow.makeCookies(request); + + verify(batterService).cleanupBatterIfNeeded(new BatterCleanupRequest(request.orderId())); + assertThat(result.orderId()).isEqualTo(request.orderId()); + assertThat(result.cookies()).isEmpty(); + } + + @Test + public void shouldMakeCookies( + // Test environment for configuration + TestWorkflowEnvironment testEnv, + // Auto-created Workflow stub + KitchenWorkflow workflow + ) { + // get the env starting time + var startTime = testEnv.currentTimeMillis(); + + // Start the workflow asynchronously + WorkflowClient.start(workflow::makeCookies, request); + when(batterService.prepareBatter(batterCreationRequest)) + .thenThrow(new RuntimeException("I failed on first try")) + .thenReturn(batterCreationResult); + // Make sure we dont bake before the oven is ready + verifyNoInteractions(bakeService); + + var status = workflow.getStatus(); + assertThat(status.ovenReady()).isFalse(); + assertThat(status.request()).isEqualTo(request); + + workflow.ovenReady(); + status = workflow.getStatus(); + assertThat(status.ovenReady()).isTrue(); + when(bakeService.bake(bakeRequest)).thenReturn(bakeResult); + + // Get the workflow result + var result = workflow.makeCookies(request); + + assertThat(result.orderId()).isEqualTo(request.orderId()); + assertThat(result.cookies()) + .isEqualTo(bakeResult.cookies()); + + var duration = Duration.ofMillis(testEnv.currentTimeMillis() - startTime); + assertThat(duration).isGreaterThan(Duration.ofSeconds(30)); + } +} diff --git a/solutions/day2/part-2.1/bakery/OrderCookiesApp.java b/solutions/day2/part-2.1/bakery/OrderCookiesApp.java new file mode 100644 index 0000000..2ff2b24 --- /dev/null +++ b/solutions/day2/part-2.1/bakery/OrderCookiesApp.java @@ -0,0 +1,25 @@ +package io.takima.temporalpractice.bakery; + +import io.takima.temporalpractice.bakery.kitchen.KitchenWorkflow; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderRequest; +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.takima.temporalpractice.bakery.temporal.TemporalUtils; +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; + +public class OrderCookiesApp { + public static void main(String[] args) { + // This app simulates an order of cookies + // It should call the workflow which makes cookies + var orderRequest = CookieOrderRequest.random(); + + KitchenWorkflow workflow = TemporalUtils.newWorkflowStub( + KitchenWorkflow.class, + TemporalQueues.KITCHEN, + "kitchen-" + orderRequest.orderId() + ); + + workflow.makeCookies(orderRequest); // Start the Workflow Execution + } +} diff --git a/solutions/day2/part-2.1/bakery/OrderManyCookies.java b/solutions/day2/part-2.1/bakery/OrderManyCookies.java new file mode 100644 index 0000000..6421088 --- /dev/null +++ b/solutions/day2/part-2.1/bakery/OrderManyCookies.java @@ -0,0 +1,70 @@ +package io.takima.temporalpractice.bakery; + +import io.takima.temporalpractice.bakery.kitchen.KitchenWorkflow; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos; +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.takima.temporalpractice.bakery.temporal.TemporalUtils; +import io.temporal.api.enums.v1.ParentClosePolicy; +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.workflow.*; +import org.slf4j.Logger; + +import java.time.Duration; + +public class OrderManyCookies { + public static void main(String[] args) { + var queue = "manycookies"; + + TemporalUtils.newWorker(queue) + .registerWorkflowImplementationTypes(ManyCookiesImpl.class); + + TemporalUtils.startWorkerFactory(); + + try { + ManyCookies manyCookies = TemporalUtils.CLIENT.newWorkflowStub( + ManyCookies.class, + WorkflowOptions.newBuilder().setWorkflowId("cookie-orderer").setTaskQueue(queue).build() + ); + WorkflowClient.start(manyCookies::continuousOrders); + } catch (Exception e) { + System.out.println("Many cookies already running, skipping..."); + } + } + + public static class ManyCookiesImpl implements ManyCookies { + + private static final Logger logger = Workflow.getLogger(ManyCookiesImpl.class); + + @Override + public void continuousOrders() { + + var options = ChildWorkflowOptions.newBuilder() + .setWorkflowId("order-" + Workflow.randomUUID()) + .setTaskQueue(TemporalQueues.KITCHEN) + .setParentClosePolicy(ParentClosePolicy.PARENT_CLOSE_POLICY_ABANDON) + .build(); + var kitchenWorkflow = Workflow.newChildWorkflowStub(KitchenWorkflow.class, options); + + var quantity = Workflow.newRandom().nextInt(1, 5); + var baking = KitchenDtos.BakingPreference.values()[Workflow.newRandom().nextInt(KitchenDtos.BakingPreference.values().length)]; + var topping = KitchenDtos.Topping.values()[Workflow.newRandom().nextInt(KitchenDtos.Topping.values().length)]; + + var cookiesPreferences = new KitchenDtos.CookieOrderRequest(Workflow.randomUUID().toString(), quantity, baking, topping); + + logger.info("Ordering cookies\n{}", cookiesPreferences); + Async.function(kitchenWorkflow::makeCookies, cookiesPreferences); + + Workflow.sleep(Duration.ofSeconds(10)); + kitchenWorkflow.ovenReady(); + + Workflow.continueAsNew(); + } + } + + @WorkflowInterface + public interface ManyCookies { + @WorkflowMethod + void continuousOrders(); + } +} diff --git a/solutions/day2/part-2.1/bakery/bake/BakeApp.java b/solutions/day2/part-2.1/bakery/bake/BakeApp.java new file mode 100644 index 0000000..2d1cdb1 --- /dev/null +++ b/solutions/day2/part-2.1/bakery/bake/BakeApp.java @@ -0,0 +1,12 @@ +package io.takima.temporalpractice.bakery.bake; + +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.takima.temporalpractice.bakery.temporal.TemporalUtils; + +public class BakeApp { + public static void main(String[] args) { + var worker = TemporalUtils.newWorker(TemporalQueues.BAKE); + worker.registerActivitiesImplementations(new BakeServiceImpl()); + TemporalUtils.startWorkerFactory(); + } +} diff --git a/solutions/day2/part-2.1/bakery/bake/BakeService.java b/solutions/day2/part-2.1/bakery/bake/BakeService.java new file mode 100644 index 0000000..36c0d02 --- /dev/null +++ b/solutions/day2/part-2.1/bakery/bake/BakeService.java @@ -0,0 +1,12 @@ +package io.takima.temporalpractice.bakery.bake; + +import io.takima.temporalpractice.bakery.bake.dtos.BakeRequest; +import io.takima.temporalpractice.bakery.bake.dtos.BakeResult; +import io.temporal.activity.ActivityInterface; +import io.temporal.activity.ActivityMethod; + +@ActivityInterface +public interface BakeService { + @ActivityMethod + BakeResult bake(BakeRequest request); +} diff --git a/solutions/day2/part-2.1/bakery/bake/BakeServiceImpl.java b/solutions/day2/part-2.1/bakery/bake/BakeServiceImpl.java new file mode 100644 index 0000000..22509b6 --- /dev/null +++ b/solutions/day2/part-2.1/bakery/bake/BakeServiceImpl.java @@ -0,0 +1,22 @@ +package io.takima.temporalpractice.bakery.bake; + +import io.takima.temporalpractice.bakery.bake.dtos.BakeRequest; +import io.takima.temporalpractice.bakery.bake.dtos.BakeResult; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.Cookie; + +import java.util.ArrayList; + +public class BakeServiceImpl implements BakeService { + @Override + public BakeResult bake(BakeRequest request) { + System.out.println("Will bake those cookies " + request.bakingPreference()); + var cookies = new ArrayList<Cookie>(); + for (int i = 0; i < request.batter().targetCookieCount(); i++) { + // Making cookies out of batter + var cookie = new Cookie(request.bakingPreference(), request.batter().topping()); + // Add it to the cookies after it is baked + cookies.add(cookie); + } + return new BakeResult(cookies); + } +} diff --git a/solutions/day2/part-2.1/bakery/bake/dtos/BakeRequest.java b/solutions/day2/part-2.1/bakery/bake/dtos/BakeRequest.java new file mode 100644 index 0000000..42701fe --- /dev/null +++ b/solutions/day2/part-2.1/bakery/bake/dtos/BakeRequest.java @@ -0,0 +1,12 @@ +package io.takima.temporalpractice.bakery.bake.dtos; + +import io.takima.temporalpractice.bakery.batter.dtos.Batter; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.BakingPreference; + +public record BakeRequest( + String orderId, + Batter batter, + BakingPreference bakingPreference +) { +} + diff --git a/solutions/day2/part-2.1/bakery/bake/dtos/BakeResult.java b/solutions/day2/part-2.1/bakery/bake/dtos/BakeResult.java new file mode 100644 index 0000000..f9afcf7 --- /dev/null +++ b/solutions/day2/part-2.1/bakery/bake/dtos/BakeResult.java @@ -0,0 +1,8 @@ +package io.takima.temporalpractice.bakery.bake.dtos; + +import java.util.List; + +import static io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.Cookie; + +public record BakeResult(List<Cookie> cookies) { +} diff --git a/solutions/day2/part-2.1/bakery/batter/BatterApp.java b/solutions/day2/part-2.1/bakery/batter/BatterApp.java new file mode 100644 index 0000000..8b4bdb9 --- /dev/null +++ b/solutions/day2/part-2.1/bakery/batter/BatterApp.java @@ -0,0 +1,12 @@ +package io.takima.temporalpractice.bakery.batter; + +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.takima.temporalpractice.bakery.temporal.TemporalUtils; + +public class BatterApp { + public static void main(String[] args) { + var worker = TemporalUtils.newWorker(TemporalQueues.BATTER); + worker.registerActivitiesImplementations(new BatterServiceImpl()); + TemporalUtils.startWorkerFactory(); + } +} diff --git a/solutions/day2/part-2.1/bakery/batter/BatterService.java b/solutions/day2/part-2.1/bakery/batter/BatterService.java new file mode 100644 index 0000000..1353c54 --- /dev/null +++ b/solutions/day2/part-2.1/bakery/batter/BatterService.java @@ -0,0 +1,17 @@ +package io.takima.temporalpractice.bakery.batter; + +import io.takima.temporalpractice.bakery.batter.dtos.BatterCleanupRequest; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationRequest; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationResult; +import io.temporal.activity.ActivityInterface; +import io.temporal.activity.ActivityMethod; + +@ActivityInterface +public interface BatterService { + + @ActivityMethod + BatterCreationResult prepareBatter(BatterCreationRequest request); + + @ActivityMethod + void cleanupBatterIfNeeded(BatterCleanupRequest request); +} diff --git a/solutions/day2/part-2.1/bakery/batter/BatterServiceImpl.java b/solutions/day2/part-2.1/bakery/batter/BatterServiceImpl.java new file mode 100644 index 0000000..17a2e83 --- /dev/null +++ b/solutions/day2/part-2.1/bakery/batter/BatterServiceImpl.java @@ -0,0 +1,27 @@ +package io.takima.temporalpractice.bakery.batter; + +import io.takima.temporalpractice.bakery.batter.dtos.Batter; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCleanupRequest; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationRequest; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationResult; + +public class BatterServiceImpl implements BatterService { + + static final int BATTER_QTY_PER_COOKIE_IN_GRAMS = 30; // in grams + + @Override + public BatterCreationResult prepareBatter(BatterCreationRequest request) { + System.out.println("Mixing flour, sugar, and love for " + request.targetCookieCount() + " cookies with topping " + request.targetTopping()); + return new BatterCreationResult( + new Batter( + request.targetTopping(), + BATTER_QTY_PER_COOKIE_IN_GRAMS * request.targetCookieCount(), + request.targetCookieCount() + )); + } + + @Override + public void cleanupBatterIfNeeded(BatterCleanupRequest request) { + System.out.println("Cleaning up batter of order " + request.orderId()); + } +} diff --git a/solutions/day2/part-2.1/bakery/batter/dtos/Batter.java b/solutions/day2/part-2.1/bakery/batter/dtos/Batter.java new file mode 100644 index 0000000..8608c0e --- /dev/null +++ b/solutions/day2/part-2.1/bakery/batter/dtos/Batter.java @@ -0,0 +1,10 @@ +package io.takima.temporalpractice.bakery.batter.dtos; + +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.Topping; + +public record Batter( + Topping topping, + int quantityInGrams, + int targetCookieCount +) { +} diff --git a/solutions/day2/part-2.1/bakery/batter/dtos/BatterCleanupRequest.java b/solutions/day2/part-2.1/bakery/batter/dtos/BatterCleanupRequest.java new file mode 100644 index 0000000..26a9ea7 --- /dev/null +++ b/solutions/day2/part-2.1/bakery/batter/dtos/BatterCleanupRequest.java @@ -0,0 +1,4 @@ +package io.takima.temporalpractice.bakery.batter.dtos; + +public record BatterCleanupRequest(String orderId) { +} diff --git a/solutions/day2/part-2.1/bakery/batter/dtos/BatterCreationRequest.java b/solutions/day2/part-2.1/bakery/batter/dtos/BatterCreationRequest.java new file mode 100644 index 0000000..38cc0c1 --- /dev/null +++ b/solutions/day2/part-2.1/bakery/batter/dtos/BatterCreationRequest.java @@ -0,0 +1,11 @@ +package io.takima.temporalpractice.bakery.batter.dtos; + +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.Topping; + +public record BatterCreationRequest( + String orderId, + Topping targetTopping, + int targetCookieCount +) { + +} diff --git a/solutions/day2/part-2.1/bakery/batter/dtos/BatterCreationResult.java b/solutions/day2/part-2.1/bakery/batter/dtos/BatterCreationResult.java new file mode 100644 index 0000000..d75e6ab --- /dev/null +++ b/solutions/day2/part-2.1/bakery/batter/dtos/BatterCreationResult.java @@ -0,0 +1,5 @@ +package io.takima.temporalpractice.bakery.batter.dtos; + +public record BatterCreationResult(Batter batter) { + +} diff --git a/solutions/day2/part-2.1/bakery/kitchen/KitchenApp.java b/solutions/day2/part-2.1/bakery/kitchen/KitchenApp.java new file mode 100644 index 0000000..52854d1 --- /dev/null +++ b/solutions/day2/part-2.1/bakery/kitchen/KitchenApp.java @@ -0,0 +1,14 @@ +package io.takima.temporalpractice.bakery.kitchen; + +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.takima.temporalpractice.bakery.temporal.TemporalUtils; +import io.temporal.worker.Worker; + +public class KitchenApp { + public static void main(String[] args) { + Worker worker = TemporalUtils.newWorker(TemporalQueues.KITCHEN); + worker.registerWorkflowImplementationTypes(KitchenWorkflowImpl.class); + worker.registerWorkflowImplementationTypes(KitchenWorkflowImplLegacy.class); + TemporalUtils.startWorkerFactory(); + } +} diff --git a/solutions/day2/part-2.1/bakery/kitchen/KitchenWorkflow.java b/solutions/day2/part-2.1/bakery/kitchen/KitchenWorkflow.java new file mode 100644 index 0000000..4a48a8e --- /dev/null +++ b/solutions/day2/part-2.1/bakery/kitchen/KitchenWorkflow.java @@ -0,0 +1,23 @@ +package io.takima.temporalpractice.bakery.kitchen; + +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderRequest; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderResult; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderStatus; +import io.temporal.workflow.QueryMethod; +import io.temporal.workflow.SignalMethod; +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; + +@WorkflowInterface +public interface KitchenWorkflow { + String Type = "CookieOrderWorkflowV2"; + + @WorkflowMethod(name = Type) + CookieOrderResult makeCookies(CookieOrderRequest request); + + @SignalMethod + void ovenReady(); + + @QueryMethod + CookieOrderStatus getStatus(); +} diff --git a/solutions/day2/part-2.1/bakery/kitchen/KitchenWorkflowImpl.java b/solutions/day2/part-2.1/bakery/kitchen/KitchenWorkflowImpl.java new file mode 100644 index 0000000..ef842cf --- /dev/null +++ b/solutions/day2/part-2.1/bakery/kitchen/KitchenWorkflowImpl.java @@ -0,0 +1,72 @@ +package io.takima.temporalpractice.bakery.kitchen; + +import io.takima.temporalpractice.bakery.bake.BakeService; +import io.takima.temporalpractice.bakery.bake.dtos.BakeRequest; +import io.takima.temporalpractice.bakery.batter.BatterService; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCleanupRequest; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationRequest; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderRequest; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderResult; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderStatus; +import io.takima.temporalpractice.bakery.temporal.TemporalActivityFactory; +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.temporal.failure.ActivityFailure; +import io.temporal.workflow.Saga; +import io.temporal.workflow.Workflow; +import org.slf4j.Logger; + +import java.time.Duration; +import java.util.List; + +public class KitchenWorkflowImpl implements KitchenWorkflow { + private final BatterService batterService; + private final BakeService bakeService; + private boolean ovenReady = false; + private CookieOrderRequest cookieOrder; + + private final Logger logger = Workflow.getLogger(KitchenWorkflowImpl.class); + + private final Saga saga = new Saga(new Saga.Options.Builder().build()); + + public KitchenWorkflowImpl() { + this.batterService = TemporalActivityFactory.newActivityStub(BatterService.class, TemporalQueues.BATTER); + this.bakeService = TemporalActivityFactory.newActivityStub(BakeService.class, TemporalQueues.BAKE); + } + + + @Override + public CookieOrderResult makeCookies(CookieOrderRequest request) { + this.cookieOrder = request; + var timer = Workflow.newTimer(Duration.ofSeconds(30)); + + try { + saga.addCompensation(batterService::cleanupBatterIfNeeded, new BatterCleanupRequest(request.orderId())); + + logger.info("Order {}: Starting to prepare {} cookies with topping {} and baking preference {}", request.orderId(), request.quantity(), request.topping(), request.bakingPreference()); + var batterCreationResult = batterService.prepareBatter(new BatterCreationRequest(request.orderId(), request.topping(), request.quantity())); + var batter = batterCreationResult.batter(); + + Workflow.await(() -> ovenReady); + + var bakeResult = bakeService.bake(new BakeRequest(request.orderId(), batter, request.bakingPreference())); + + logger.info("Order {}: Your {} cookies are ready!", request.orderId(), bakeResult.cookies().size()); + + timer.get(); + return new CookieOrderResult(request.orderId(), bakeResult.cookies()); + } catch (ActivityFailure failures) { + saga.compensate(); + return new CookieOrderResult(request.orderId(), List.of()); + } + } + + @Override + public void ovenReady() { + this.ovenReady = true; + } + + @Override + public CookieOrderStatus getStatus() { + return new CookieOrderStatus(ovenReady, cookieOrder); + } +} diff --git a/solutions/day2/part-2.1/bakery/kitchen/KitchenWorkflowImplLegacy.java b/solutions/day2/part-2.1/bakery/kitchen/KitchenWorkflowImplLegacy.java new file mode 100644 index 0000000..e58630f --- /dev/null +++ b/solutions/day2/part-2.1/bakery/kitchen/KitchenWorkflowImplLegacy.java @@ -0,0 +1,73 @@ +package io.takima.temporalpractice.bakery.kitchen; + +import io.takima.temporalpractice.bakery.bake.BakeService; +import io.takima.temporalpractice.bakery.bake.dtos.BakeRequest; +import io.takima.temporalpractice.bakery.batter.BatterService; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCleanupRequest; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationRequest; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderRequest; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderResult; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderStatus; +import io.takima.temporalpractice.bakery.temporal.TemporalActivityFactory; +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.temporal.failure.ActivityFailure; +import io.temporal.workflow.Saga; +import io.temporal.workflow.Workflow; +import org.slf4j.Logger; + +import java.time.Duration; +import java.util.List; + +@Deprecated +public class KitchenWorkflowImplLegacy implements KitchenWorkflowLegacy { + private final BatterService batterService; + private final BakeService bakeService; + private boolean ovenReady = false; + private CookieOrderRequest cookieOrder; + + private final Logger logger = Workflow.getLogger(KitchenWorkflowImplLegacy.class); + + private final Saga saga = new Saga(new Saga.Options.Builder().build()); + + public KitchenWorkflowImplLegacy() { + this.batterService = TemporalActivityFactory.newActivityStub(BatterService.class, TemporalQueues.BATTER); + this.bakeService = TemporalActivityFactory.newActivityStub(BakeService.class, TemporalQueues.BAKE); + } + + + @Override + public CookieOrderResult makeCookies(CookieOrderRequest request) { + this.cookieOrder = request; + var timer = Workflow.newTimer(Duration.ofSeconds(30)); + + try { + saga.addCompensation(batterService::cleanupBatterIfNeeded, new BatterCleanupRequest(request.orderId())); + + logger.info("Order {}: Starting to prepare {} cookies with topping {} and baking preference {}", request.orderId(), request.quantity(), request.topping(), request.bakingPreference()); + var batterCreationResult = batterService.prepareBatter(new BatterCreationRequest(request.orderId(), request.topping(), request.quantity())); + var batter = batterCreationResult.batter(); + + Workflow.await(() -> ovenReady); + + var bakeResult = bakeService.bake(new BakeRequest(request.orderId(), batter, request.bakingPreference())); + + logger.info("Order {}: Your {} cookies are ready!", request.orderId(), bakeResult.cookies().size()); + + timer.get(); + return new CookieOrderResult(request.orderId(), bakeResult.cookies()); + } catch (ActivityFailure failures) { + saga.compensate(); + return new CookieOrderResult(request.orderId(), List.of()); + } + } + + @Override + public void ovenReady() { + this.ovenReady = true; + } + + @Override + public CookieOrderStatus getStatus() { + return new CookieOrderStatus(ovenReady, cookieOrder); + } +} diff --git a/solutions/day2/part-2.1/bakery/kitchen/KitchenWorkflowLegacy.java b/solutions/day2/part-2.1/bakery/kitchen/KitchenWorkflowLegacy.java new file mode 100644 index 0000000..a6c3f98 --- /dev/null +++ b/solutions/day2/part-2.1/bakery/kitchen/KitchenWorkflowLegacy.java @@ -0,0 +1,24 @@ +package io.takima.temporalpractice.bakery.kitchen; + +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderRequest; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderResult; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos.CookieOrderStatus; +import io.temporal.workflow.QueryMethod; +import io.temporal.workflow.SignalMethod; +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; + +@Deprecated +@WorkflowInterface +public interface KitchenWorkflowLegacy { + String Type = "CookieOrderWorkflow"; + + @WorkflowMethod(name = Type) + CookieOrderResult makeCookies(CookieOrderRequest request); + + @SignalMethod + void ovenReady(); + + @QueryMethod + CookieOrderStatus getStatus(); +} diff --git a/solutions/day2/part-2.1/bakery/kitchen/dtos/KitchenDtos.java b/solutions/day2/part-2.1/bakery/kitchen/dtos/KitchenDtos.java new file mode 100644 index 0000000..f82ef2e --- /dev/null +++ b/solutions/day2/part-2.1/bakery/kitchen/dtos/KitchenDtos.java @@ -0,0 +1,62 @@ +package io.takima.temporalpractice.bakery.kitchen.dtos; + +import java.util.List; +import java.util.UUID; + +public interface KitchenDtos { + enum BakingPreference { + RAW, + SOFT, + COOKED, + BURNT; + + public static BakingPreference random() { + return values()[(int) (Math.random() * values().length)]; + } + } + + enum Topping { + CHOCOLATE, + NUTS, + NONE; + + public static Topping random() { + return values()[(int) (Math.random() * values().length)]; + } + } + + record Cookie( + BakingPreference bakingPreference, + Topping topping + ) { + } + + record CookieOrderRequest( + String orderId, + int quantity, + BakingPreference bakingPreference, + Topping topping + ) { + + public static CookieOrderRequest random() { + return new CookieOrderRequest( + UUID.randomUUID().toString(), + (int) (Math.random() * 10) + 1, + BakingPreference.random(), + Topping.random() + ); + } + } + + record CookieOrderResult( + String orderId, + List<Cookie> cookies + ) { + } + + record CookieOrderStatus( + boolean ovenReady, + CookieOrderRequest request + ) { + } +} diff --git a/solutions/day2/part-2.1/bakery/temporal/TemporalActivityFactory.java b/solutions/day2/part-2.1/bakery/temporal/TemporalActivityFactory.java new file mode 100644 index 0000000..ec51a0e --- /dev/null +++ b/solutions/day2/part-2.1/bakery/temporal/TemporalActivityFactory.java @@ -0,0 +1,20 @@ +package io.takima.temporalpractice.bakery.temporal; + +import io.temporal.activity.ActivityOptions; +import io.temporal.common.RetryOptions; +import io.temporal.workflow.Workflow; + +import java.time.Duration; + +public class TemporalActivityFactory { + public static <T> T newActivityStub(Class<T> workflowInterface, String queue) { + return Workflow.newActivityStub( + workflowInterface, + ActivityOptions.newBuilder() + .setTaskQueue(queue) + .setRetryOptions(RetryOptions.newBuilder().setMaximumAttempts(2).build()) + .setStartToCloseTimeout(Duration.ofSeconds(5)) + .build() + ); + } +} diff --git a/solutions/day2/part-2.1/bakery/temporal/TemporalQueues.java b/solutions/day2/part-2.1/bakery/temporal/TemporalQueues.java new file mode 100644 index 0000000..0c99053 --- /dev/null +++ b/solutions/day2/part-2.1/bakery/temporal/TemporalQueues.java @@ -0,0 +1,7 @@ +package io.takima.temporalpractice.bakery.temporal; + +public class TemporalQueues { + public static final String KITCHEN = "kitchen"; + public static final String BATTER = "batter"; + public static final String BAKE = "bake"; +} diff --git a/solutions/day2/part-2.1/bakery/temporal/TemporalUtils.java b/solutions/day2/part-2.1/bakery/temporal/TemporalUtils.java new file mode 100644 index 0000000..101f7e6 --- /dev/null +++ b/solutions/day2/part-2.1/bakery/temporal/TemporalUtils.java @@ -0,0 +1,36 @@ +package io.takima.temporalpractice.bakery.temporal; + +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; + +public class TemporalUtils { + + // Represents the connection to your local cluster. For now, lets keep it simple + private static final WorkflowServiceStubs SERVICE_STUBS = WorkflowServiceStubs.newLocalServiceStubs(); + + // Your key for interacting with the Temporal world. + public static final WorkflowClient CLIENT = WorkflowClient.newInstance(SERVICE_STUBS); + + private static final WorkerFactory FACTORY = WorkerFactory.newInstance(CLIENT); + + public static Worker newWorker(String queue) { + return FACTORY.newWorker(queue); + } + + public static void startWorkerFactory() { + FACTORY.start(); + } + + public static <T> T newWorkflowStub(Class<T> workflowInterface, String queue, String workflowId) { + return CLIENT.newWorkflowStub( + workflowInterface, + WorkflowOptions.newBuilder() + .setTaskQueue(queue) + .setWorkflowId(workflowId) + .build() + ); + } +} diff --git a/solutions/day2/part-2.2/test/java/io/takima/temporalpractice/.gitkeep b/solutions/day2/part-2.2/test/java/io/takima/temporalpractice/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/solutions/day2/part-2.2/test/java/io/takima/temporalpractice/bakery/cookie/CookieWorkflowTest.java b/solutions/day2/part-2.2/test/java/io/takima/temporalpractice/bakery/cookie/CookieWorkflowTest.java new file mode 100644 index 0000000..aed1a85 --- /dev/null +++ b/solutions/day2/part-2.2/test/java/io/takima/temporalpractice/bakery/cookie/CookieWorkflowTest.java @@ -0,0 +1,130 @@ +package io.takima.temporalpractice.bakery.cookie; + +import io.takima.temporalpractice.bakery.bake.BakeService; +import io.takima.temporalpractice.bakery.bake.dtos.BakeRequest; +import io.takima.temporalpractice.bakery.bake.dtos.BakeResult; +import io.takima.temporalpractice.bakery.batter.BatterService; +import io.takima.temporalpractice.bakery.batter.dtos.Batter; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCleanupRequest; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationRequest; +import io.takima.temporalpractice.bakery.batter.dtos.BatterCreationResult; +import io.takima.temporalpractice.bakery.kitchen.KitchenWorkflow; +import io.takima.temporalpractice.bakery.kitchen.KitchenWorkflowImpl; +import io.takima.temporalpractice.bakery.kitchen.dtos.KitchenDtos; +import io.takima.temporalpractice.bakery.temporal.TemporalQueues; +import io.temporal.client.WorkflowClient; +import io.temporal.testing.TestWorkflowEnvironment; +import io.temporal.testing.TestWorkflowExtension; +import io.temporal.testing.WorkflowReplayer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.File; +import java.time.Duration; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class CookieWorkflowTest { + + @Mock(withoutAnnotations = true) + private BakeService bakeService; + + @Mock(withoutAnnotations = true) + private BatterService batterService; + + + @RegisterExtension + public static final TestWorkflowExtension testWorkflowExtension = + TestWorkflowExtension.newBuilder() + .registerWorkflowImplementationTypes(KitchenWorkflowImpl.class) + .setDoNotStart(true) + .build(); + + @BeforeEach + void init(TestWorkflowEnvironment testEnv) { + testEnv.newWorker(TemporalQueues.BAKE) + .registerActivitiesImplementations(bakeService); + testEnv.newWorker(TemporalQueues.BATTER) + .registerActivitiesImplementations(batterService); + testEnv.start(); + } + + KitchenDtos.CookieOrderRequest request = KitchenDtos.CookieOrderRequest.random(); + BatterCreationRequest batterCreationRequest = new BatterCreationRequest(request.orderId(), request.topping(), request.quantity()); + BatterCreationResult batterCreationResult = new BatterCreationResult(new Batter(request.topping(), 30 * request.quantity(), request.quantity())); + BakeRequest bakeRequest = new BakeRequest(request.orderId(), batterCreationResult.batter(), request.bakingPreference()); + BakeResult bakeResult = new BakeResult(List.of(new KitchenDtos.Cookie(request.bakingPreference(), request.topping()))); + + @Test + public void shouldCleanupBatter( + // Test environment for configuration + TestWorkflowEnvironment testEnv, + // Auto-created Workflow stub + KitchenWorkflow workflow + ) { + WorkflowClient.start(workflow::makeCookies, request); + + when(batterService.prepareBatter(batterCreationRequest)) + .thenReturn(batterCreationResult); + workflow.ovenReady(); + when(bakeService.bake(bakeRequest)) + .thenThrow(new RuntimeException("I will always fail")); + + var result = workflow.makeCookies(request); + + verify(batterService).cleanupBatterIfNeeded(new BatterCleanupRequest(request.orderId())); + assertThat(result.orderId()).isEqualTo(request.orderId()); + assertThat(result.cookies()).isEmpty(); + } + + @Test + public void shouldMakeCookies( + // Test environment for configuration + TestWorkflowEnvironment testEnv, + // Auto-created Workflow stub + KitchenWorkflow workflow + ) { + // get the env starting time + var startTime = testEnv.currentTimeMillis(); + + // Start the workflow asynchronously + WorkflowClient.start(workflow::makeCookies, request); + when(batterService.prepareBatter(batterCreationRequest)) + .thenThrow(new RuntimeException("I failed on first try")) + .thenReturn(batterCreationResult); + // Make sure we dont bake before the oven is ready + verifyNoInteractions(bakeService); + + var status = workflow.getStatus(); + assertThat(status.ovenReady()).isFalse(); + assertThat(status.request()).isEqualTo(request); + + workflow.ovenReady(); + status = workflow.getStatus(); + assertThat(status.ovenReady()).isTrue(); + when(bakeService.bake(bakeRequest)).thenReturn(bakeResult); + + // Get the workflow result + var result = workflow.makeCookies(request); + + assertThat(result.orderId()).isEqualTo(request.orderId()); + assertThat(result.cookies()) + .isEqualTo(bakeResult.cookies()); + + var duration = Duration.ofMillis(testEnv.currentTimeMillis() - startTime); + assertThat(duration).isGreaterThan(Duration.ofSeconds(30)); + } + + @Test + void shouldNotBreakDeterminism() throws Exception { + var historyFile = new File("src/test/resources/history.json"); + WorkflowReplayer.replayWorkflowExecution(historyFile, KitchenWorkflowImpl.class); + } +} diff --git a/solutions/day2/part-2.2/test/resources/history.json b/solutions/day2/part-2.2/test/resources/history.json new file mode 100644 index 0000000..5e73fd1 --- /dev/null +++ b/solutions/day2/part-2.2/test/resources/history.json @@ -0,0 +1,437 @@ +{ + "events": [ + { + "eventId": "1", + "eventTime": "2025-05-20T09:21:02.200461472Z", + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "taskId": "3148028", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "CookieOrderWorkflowV2" + }, + "taskQueue": { + "name": "kitchen", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJvcmRlcklkIjoiNzAyY2E1NDEtMzdiYi00YzI4LTg0NGMtZDc1OWY0ZDIyNTZjIiwicXVhbnRpdHkiOjEsImJha2luZ1ByZWZlcmVuY2UiOiJTT0ZUIiwidG9wcGluZyI6IkNIT0NPTEFURSJ9" + } + ] + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "0s", + "workflowTaskTimeout": "10s", + "originalExecutionRunId": "0196ecff-14f8-7705-af1f-58e18b61f61d", + "identity": "36905@MacBookPro.takima.home", + "firstExecutionRunId": "0196ecff-14f8-7705-af1f-58e18b61f61d", + "attempt": 1, + "firstWorkflowTaskBackoff": "0s", + "header": {}, + "workflowId": "kitchen-702ca541-37bb-4c28-844c-d759f4d2256c" + } + }, + { + "eventId": "2", + "eventTime": "2025-05-20T09:21:02.200563764Z", + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "taskId": "3148029", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "kitchen", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": "10s", + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": "2025-05-20T09:21:02.209216055Z", + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "taskId": "3148034", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "36902@MacBookPro.takima.home", + "requestId": "e9bb9cdd-4427-4645-9cf4-07a8a2560054", + "historySizeBytes": "425" + } + }, + { + "eventId": "4", + "eventTime": "2025-05-20T09:21:02.388671041Z", + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "taskId": "3148038", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "startedEventId": "3", + "identity": "36902@MacBookPro.takima.home", + "workerVersion": {}, + "sdkMetadata": { + "langUsedFlags": [ + 1 + ] + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": "2025-05-20T09:21:02.388723958Z", + "eventType": "EVENT_TYPE_TIMER_STARTED", + "taskId": "3148039", + "timerStartedEventAttributes": { + "timerId": "98d73a02-7edb-39dd-a643-79005e644b64", + "startToFireTimeout": "30s", + "workflowTaskCompletedEventId": "4" + } + }, + { + "eventId": "6", + "eventTime": "2025-05-20T09:21:02.388758875Z", + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "taskId": "3148040", + "activityTaskScheduledEventAttributes": { + "activityId": "92159483-48ab-3725-862f-7bf8a043d767", + "activityType": { + "name": "PrepareBatter" + }, + "taskQueue": { + "name": "batter", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJvcmRlcklkIjoiNzAyY2E1NDEtMzdiYi00YzI4LTg0NGMtZDc1OWY0ZDIyNTZjIiwidGFyZ2V0VG9wcGluZyI6IkNIT0NPTEFURSIsInRhcmdldENvb2tpZUNvdW50IjoxfQ==" + } + ] + }, + "scheduleToCloseTimeout": "0s", + "scheduleToStartTimeout": "0s", + "startToCloseTimeout": "5s", + "heartbeatTimeout": "0s", + "workflowTaskCompletedEventId": "4", + "retryPolicy": { + "initialInterval": "1s", + "backoffCoefficient": 2, + "maximumInterval": "100s", + "maximumAttempts": 2 + } + } + }, + { + "eventId": "7", + "eventTime": "2025-05-20T09:21:02.394468666Z", + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "taskId": "3148047", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "6", + "identity": "36781@MacBookPro.takima.home", + "requestId": "732e3649-f870-499a-84ef-cde1ed52afc2", + "attempt": 1, + "workerVersion": {} + } + }, + { + "eventId": "8", + "eventTime": "2025-05-20T09:21:02.400925625Z", + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "taskId": "3148048", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJiYXR0ZXIiOnsidG9wcGluZyI6IkNIT0NPTEFURSIsInF1YW50aXR5SW5HcmFtcyI6MzAsInRhcmdldENvb2tpZUNvdW50IjoxfX0=" + } + ] + }, + "scheduledEventId": "6", + "startedEventId": "7", + "identity": "36781@MacBookPro.takima.home" + } + }, + { + "eventId": "9", + "eventTime": "2025-05-20T09:21:02.400931125Z", + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "taskId": "3148049", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "36902@MacBookPro.takima.home:27069735-1e75-4a62-9677-69417d0e5b39", + "kind": "TASK_QUEUE_KIND_STICKY", + "normalName": "kitchen" + }, + "startToCloseTimeout": "10s", + "attempt": 1 + } + }, + { + "eventId": "10", + "eventTime": "2025-05-20T09:21:02.404223166Z", + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "taskId": "3148053", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "9", + "identity": "36902@MacBookPro.takima.home", + "requestId": "a1fb1d18-2dfe-4ec1-b812-3135bdf6229b", + "historySizeBytes": "1314" + } + }, + { + "eventId": "11", + "eventTime": "2025-05-20T09:21:02.418556Z", + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "taskId": "3148057", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "9", + "startedEventId": "10", + "identity": "36902@MacBookPro.takima.home", + "workerVersion": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "12", + "eventTime": "2025-05-20T09:21:32.393183416Z", + "eventType": "EVENT_TYPE_TIMER_FIRED", + "taskId": "3148060", + "timerFiredEventAttributes": { + "timerId": "98d73a02-7edb-39dd-a643-79005e644b64", + "startedEventId": "5" + } + }, + { + "eventId": "13", + "eventTime": "2025-05-20T09:21:32.393217500Z", + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "taskId": "3148061", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "36902@MacBookPro.takima.home:27069735-1e75-4a62-9677-69417d0e5b39", + "kind": "TASK_QUEUE_KIND_STICKY", + "normalName": "kitchen" + }, + "startToCloseTimeout": "10s", + "attempt": 1 + } + }, + { + "eventId": "14", + "eventTime": "2025-05-20T09:21:32.404506541Z", + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "taskId": "3148065", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "13", + "identity": "36902@MacBookPro.takima.home", + "requestId": "8a083d1c-0ba7-4ca9-ae54-03e303b4d1df", + "historySizeBytes": "1660" + } + }, + { + "eventId": "15", + "eventTime": "2025-05-20T09:21:32.421678958Z", + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "taskId": "3148069", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "13", + "startedEventId": "14", + "identity": "36902@MacBookPro.takima.home", + "workerVersion": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "16", + "eventTime": "2025-05-20T09:31:25.471000502Z", + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_SIGNALED", + "taskId": "3148071", + "workflowExecutionSignaledEventAttributes": { + "signalName": "ovenReady", + "input": {} + } + }, + { + "eventId": "17", + "eventTime": "2025-05-20T09:31:25.471008835Z", + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "taskId": "3148072", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "36902@MacBookPro.takima.home:27069735-1e75-4a62-9677-69417d0e5b39", + "kind": "TASK_QUEUE_KIND_STICKY", + "normalName": "kitchen" + }, + "startToCloseTimeout": "10s", + "attempt": 1 + } + }, + { + "eventId": "18", + "eventTime": "2025-05-20T09:31:25.478133502Z", + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "taskId": "3148076", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "17", + "identity": "36902@MacBookPro.takima.home", + "requestId": "14720a8e-9b34-419c-a8a9-559b40c19f8c", + "historySizeBytes": "1979" + } + }, + { + "eventId": "19", + "eventTime": "2025-05-20T09:31:25.494807919Z", + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "taskId": "3148080", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "17", + "startedEventId": "18", + "identity": "36902@MacBookPro.takima.home", + "workerVersion": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "20", + "eventTime": "2025-05-20T09:31:25.494843794Z", + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "taskId": "3148081", + "activityTaskScheduledEventAttributes": { + "activityId": "23d7c2c4-cb8c-3dee-b3d2-e8300ca29511", + "activityType": { + "name": "Bake" + }, + "taskQueue": { + "name": "bake", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJvcmRlcklkIjoiNzAyY2E1NDEtMzdiYi00YzI4LTg0NGMtZDc1OWY0ZDIyNTZjIiwiYmF0dGVyIjp7InRvcHBpbmciOiJDSE9DT0xBVEUiLCJxdWFudGl0eUluR3JhbXMiOjMwLCJ0YXJnZXRDb29raWVDb3VudCI6MX0sImJha2luZ1ByZWZlcmVuY2UiOiJTT0ZUIn0=" + } + ] + }, + "scheduleToCloseTimeout": "0s", + "scheduleToStartTimeout": "0s", + "startToCloseTimeout": "5s", + "heartbeatTimeout": "0s", + "workflowTaskCompletedEventId": "19", + "retryPolicy": { + "initialInterval": "1s", + "backoffCoefficient": 2, + "maximumInterval": "100s", + "maximumAttempts": 2 + } + } + }, + { + "eventId": "21", + "eventTime": "2025-05-20T09:31:25.498424877Z", + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "taskId": "3148086", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "20", + "identity": "36783@MacBookPro.takima.home", + "requestId": "31445516-84c7-4cb3-8d81-95c62fba17a3", + "attempt": 1, + "workerVersion": {} + } + }, + { + "eventId": "22", + "eventTime": "2025-05-20T09:31:25.582749377Z", + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "taskId": "3148087", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJjb29raWVzIjpbeyJiYWtpbmdQcmVmZXJlbmNlIjoiU09GVCIsInRvcHBpbmciOiJDSE9DT0xBVEUifV19" + } + ] + }, + "scheduledEventId": "20", + "startedEventId": "21", + "identity": "36783@MacBookPro.takima.home" + } + }, + { + "eventId": "23", + "eventTime": "2025-05-20T09:31:25.582760710Z", + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "taskId": "3148088", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "36902@MacBookPro.takima.home:27069735-1e75-4a62-9677-69417d0e5b39", + "kind": "TASK_QUEUE_KIND_STICKY", + "normalName": "kitchen" + }, + "startToCloseTimeout": "10s", + "attempt": 1 + } + }, + { + "eventId": "24", + "eventTime": "2025-05-20T09:31:25.589439919Z", + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "taskId": "3148092", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "23", + "identity": "36902@MacBookPro.takima.home", + "requestId": "e6cd5cd1-4f48-4d44-86b3-de2543e020c3", + "historySizeBytes": "2820" + } + }, + { + "eventId": "25", + "eventTime": "2025-05-20T09:31:25.603636960Z", + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "taskId": "3148096", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "23", + "startedEventId": "24", + "identity": "36902@MacBookPro.takima.home", + "workerVersion": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "26", + "eventTime": "2025-05-20T09:31:25.603693210Z", + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "taskId": "3148097", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJvcmRlcklkIjoiNzAyY2E1NDEtMzdiYi00YzI4LTg0NGMtZDc1OWY0ZDIyNTZjIiwiY29va2llcyI6W3siYmFraW5nUHJlZmVyZW5jZSI6IlNPRlQiLCJ0b3BwaW5nIjoiQ0hPQ09MQVRFIn1dfQ==" + } + ] + }, + "workflowTaskCompletedEventId": "25" + } + } + ] +} \ No newline at end of file -- GitLab