diff --git a/solutions/day1/part-2/temporalpractice/bakery/kitchen/KitchenWorkflowImpl.java b/solutions/day1/part-2/temporalpractice/bakery/kitchen/KitchenWorkflowImpl.java index 529793a0698ac3b8fb3eec68e752c4e917a04141..85733609603194432505363aa4ec24df153ec6ce 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 2ea7f9b217a272ead8530b059fc580bfa77b3a09..0000000000000000000000000000000000000000 --- 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 4dd036ce56e5b9d22fdacb22a3084b9d6044749d..2ff2b24826e205b1abc28eb86c4dbcc695ee210b 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 0000000000000000000000000000000000000000..2d1cdb1f4cbfb9d2d79510e53c2e3534ff4eafc7 --- /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 a0e42de501d8870c5183da0774220633d2291f9e..36c0d02dba3dde7a8b166e68a8e3ff9c471870d6 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 4924604dc60ce82a1b4969620759f2fd32ae9cb2..22509b663a08745f19b2bb4b3fb78915c9fe50c9 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 0000000000000000000000000000000000000000..42701fec06fef7cac59011057af8dce46f4eba33 --- /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 3bdca8f02a29498e2fdb5ad7713dd74f8b856463..f9afcf709a517afd86c44140f17877a9c061aac8 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 0000000000000000000000000000000000000000..8b4bdb903208cebcca21d9fe8a357a4795f82431 --- /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 e9f743422a35d5401c5c1e09615b74d89bf68b50..7da3e9f7fe2514499e1e382951995bba6e9b578e 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 0000000000000000000000000000000000000000..b594d07e43be3d63e8af4776ada319ed34944fe1 --- /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 0000000000000000000000000000000000000000..8608c0e5eac1ddbee9e4d7c20da2549cc9ebfb21 --- /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 0000000000000000000000000000000000000000..38cc0c1cd6279c7bee0d6662202b4bd378b31084 --- /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 0000000000000000000000000000000000000000..d75e6ab838e3fb45fb53ab2e0870d3a33a5ad975 --- /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 0000000000000000000000000000000000000000..4c5736750e2d894e4cb66b8c39bfb94817d61c2c --- /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 fbc3ed7c70efe6fd8ca8a524bc2747445103d8bb..0000000000000000000000000000000000000000 --- 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 9f7fb09e3ef4c29ca6a9026a4154b0f1eb98bdb3..ca266350468651eaee9fd8a2800d14ec2dfa8632 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 e4e5026e5c342a333d6d7d7ab2a1bfd1c1b7b39f..df0bedc0c840318a4fb488a51d9ab926b5324c46 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 ee64b4211397535012145453519cfae1f8712c79..0000000000000000000000000000000000000000 --- 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 2e000fe1fa44ab7241895eca451af2078c6ee004..0000000000000000000000000000000000000000 --- 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 0a703c214648ab589aae2c43541fd97f731515ec..0000000000000000000000000000000000000000 --- 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 43d419cb637d10778fa1d15e0f91c3eb058b2624..0000000000000000000000000000000000000000 --- 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 d350d8093d85944348963d98bd4ff8e5783dd40b..0000000000000000000000000000000000000000 --- 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 1e90f2313c3ce83f381dc71c883bc7c198912980..f82ef2e5c2ec62f10fb03b9b817cbdb4b39962a1 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 0d1513798f4f5e54376b9dd96cda6d80919083ce..0c99053e91384d67bce35b927bd47b3ce58444b3 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 ea0bfa389b2a6b6efde5b39b56ab401dfffcd22d..f004ff98a03d55193e0fb9d45026528ec7dffed8 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 0000000000000000000000000000000000000000..2ff2b24826e205b1abc28eb86c4dbcc695ee210b --- /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 0000000000000000000000000000000000000000..6421088ad913c656bd3133c5d715d182328f5959 --- /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 0000000000000000000000000000000000000000..2d1cdb1f4cbfb9d2d79510e53c2e3534ff4eafc7 --- /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 0000000000000000000000000000000000000000..36c0d02dba3dde7a8b166e68a8e3ff9c471870d6 --- /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 0000000000000000000000000000000000000000..22509b663a08745f19b2bb4b3fb78915c9fe50c9 --- /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 0000000000000000000000000000000000000000..42701fec06fef7cac59011057af8dce46f4eba33 --- /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 0000000000000000000000000000000000000000..f9afcf709a517afd86c44140f17877a9c061aac8 --- /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 0000000000000000000000000000000000000000..8b4bdb903208cebcca21d9fe8a357a4795f82431 --- /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 0000000000000000000000000000000000000000..7da3e9f7fe2514499e1e382951995bba6e9b578e --- /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 0000000000000000000000000000000000000000..b594d07e43be3d63e8af4776ada319ed34944fe1 --- /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 0000000000000000000000000000000000000000..8608c0e5eac1ddbee9e4d7c20da2549cc9ebfb21 --- /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 0000000000000000000000000000000000000000..38cc0c1cd6279c7bee0d6662202b4bd378b31084 --- /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 0000000000000000000000000000000000000000..d75e6ab838e3fb45fb53ab2e0870d3a33a5ad975 --- /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 0000000000000000000000000000000000000000..4c5736750e2d894e4cb66b8c39bfb94817d61c2c --- /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 0000000000000000000000000000000000000000..ca266350468651eaee9fd8a2800d14ec2dfa8632 --- /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 0000000000000000000000000000000000000000..df0bedc0c840318a4fb488a51d9ab926b5324c46 --- /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 0000000000000000000000000000000000000000..f82ef2e5c2ec62f10fb03b9b817cbdb4b39962a1 --- /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 0000000000000000000000000000000000000000..0c99053e91384d67bce35b927bd47b3ce58444b3 --- /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 0000000000000000000000000000000000000000..f004ff98a03d55193e0fb9d45026528ec7dffed8 --- /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-end/temporalpractice/bakery/CookieWorkflowTest.java b/solutions/day1/part-5.1-end/temporalpractice/bakery/CookieWorkflowTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5de07e2230bdaaadcdf0e4aa26d72076ded11bca --- /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.1-start/temporalpractice/bakery/CookieWorkflowTest.java b/solutions/day1/part-5.1-start/temporalpractice/bakery/CookieWorkflowTest.java new file mode 100644 index 0000000000000000000000000000000000000000..aa06ecbf19141cfa2b45c0018eb48c1cecb4c8c5 --- /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! + } +} 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 0000000000000000000000000000000000000000..9d37dd41ee2d9ef0d672b8adca22310fd99604d7 --- /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 0000000000000000000000000000000000000000..2ff2b24826e205b1abc28eb86c4dbcc695ee210b --- /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 0000000000000000000000000000000000000000..6421088ad913c656bd3133c5d715d182328f5959 --- /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 0000000000000000000000000000000000000000..2d1cdb1f4cbfb9d2d79510e53c2e3534ff4eafc7 --- /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 0000000000000000000000000000000000000000..36c0d02dba3dde7a8b166e68a8e3ff9c471870d6 --- /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 0000000000000000000000000000000000000000..22509b663a08745f19b2bb4b3fb78915c9fe50c9 --- /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 0000000000000000000000000000000000000000..42701fec06fef7cac59011057af8dce46f4eba33 --- /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 0000000000000000000000000000000000000000..f9afcf709a517afd86c44140f17877a9c061aac8 --- /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 0000000000000000000000000000000000000000..8b4bdb903208cebcca21d9fe8a357a4795f82431 --- /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 0000000000000000000000000000000000000000..1353c54503494eed2c87e03165f287ca980677c0 --- /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 0000000000000000000000000000000000000000..17a2e83dccc8a4bd5b1093474b2e6dae84e1bd0b --- /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 0000000000000000000000000000000000000000..8608c0e5eac1ddbee9e4d7c20da2549cc9ebfb21 --- /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 0000000000000000000000000000000000000000..26a9ea7d09c2cacbd61c2052cbc7d87164863653 --- /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 0000000000000000000000000000000000000000..38cc0c1cd6279c7bee0d6662202b4bd378b31084 --- /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 0000000000000000000000000000000000000000..d75e6ab838e3fb45fb53ab2e0870d3a33a5ad975 --- /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 0000000000000000000000000000000000000000..4c5736750e2d894e4cb66b8c39bfb94817d61c2c --- /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 0000000000000000000000000000000000000000..ca266350468651eaee9fd8a2800d14ec2dfa8632 --- /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 0000000000000000000000000000000000000000..ef842cf6404c64e9591dfdc9e136112e0e09f69f --- /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 0000000000000000000000000000000000000000..f82ef2e5c2ec62f10fb03b9b817cbdb4b39962a1 --- /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 0000000000000000000000000000000000000000..ec51a0efbc4d17dff652a241ef7d40fa16ffa92f --- /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 0000000000000000000000000000000000000000..0c99053e91384d67bce35b927bd47b3ce58444b3 --- /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 0000000000000000000000000000000000000000..101f7e67044053b8cd545983db756f27b1c5d0e8 --- /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 0000000000000000000000000000000000000000..13906e51785fac4557bbe7048c5c7addbc6c3fa8 --- /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 0000000000000000000000000000000000000000..748902fb2f503d578aa8992c0cc8be91c755d0ce --- /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 0000000000000000000000000000000000000000..2ff2b24826e205b1abc28eb86c4dbcc695ee210b --- /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 0000000000000000000000000000000000000000..6421088ad913c656bd3133c5d715d182328f5959 --- /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 0000000000000000000000000000000000000000..2d1cdb1f4cbfb9d2d79510e53c2e3534ff4eafc7 --- /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 0000000000000000000000000000000000000000..36c0d02dba3dde7a8b166e68a8e3ff9c471870d6 --- /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 0000000000000000000000000000000000000000..22509b663a08745f19b2bb4b3fb78915c9fe50c9 --- /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 0000000000000000000000000000000000000000..42701fec06fef7cac59011057af8dce46f4eba33 --- /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 0000000000000000000000000000000000000000..f9afcf709a517afd86c44140f17877a9c061aac8 --- /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 0000000000000000000000000000000000000000..8b4bdb903208cebcca21d9fe8a357a4795f82431 --- /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 0000000000000000000000000000000000000000..1353c54503494eed2c87e03165f287ca980677c0 --- /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 0000000000000000000000000000000000000000..17a2e83dccc8a4bd5b1093474b2e6dae84e1bd0b --- /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 0000000000000000000000000000000000000000..8608c0e5eac1ddbee9e4d7c20da2549cc9ebfb21 --- /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 0000000000000000000000000000000000000000..26a9ea7d09c2cacbd61c2052cbc7d87164863653 --- /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 0000000000000000000000000000000000000000..38cc0c1cd6279c7bee0d6662202b4bd378b31084 --- /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 0000000000000000000000000000000000000000..d75e6ab838e3fb45fb53ab2e0870d3a33a5ad975 --- /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 0000000000000000000000000000000000000000..52854d18944702325a16ce304a84acfd55c03d35 --- /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 0000000000000000000000000000000000000000..4a48a8eb8c55b4faf718b57f9c989c524a196a4b --- /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 0000000000000000000000000000000000000000..ef842cf6404c64e9591dfdc9e136112e0e09f69f --- /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 0000000000000000000000000000000000000000..e58630f9c93fe23e0b3ca6281d7ec5e599d75864 --- /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 0000000000000000000000000000000000000000..a6c3f98c95ed0a24dd352b7c9023d2987197d33a --- /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 0000000000000000000000000000000000000000..f82ef2e5c2ec62f10fb03b9b817cbdb4b39962a1 --- /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 0000000000000000000000000000000000000000..ec51a0efbc4d17dff652a241ef7d40fa16ffa92f --- /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 0000000000000000000000000000000000000000..0c99053e91384d67bce35b927bd47b3ce58444b3 --- /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 0000000000000000000000000000000000000000..101f7e67044053b8cd545983db756f27b1c5d0e8 --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 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 0000000000000000000000000000000000000000..aed1a852b966faa4bcee58db5dbe7d6c2c4d3136 --- /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 0000000000000000000000000000000000000000..5e73fd1e48775588ab7f510cb8bb613c08379f84 --- /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