Skip to content

Bueller87/cadence-handle-eats-order

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Uber-Eats Cadence Java Sample

A fully functional Java project demonstrating a simplified Uber-Eats-style workflow using Cadence. In this repo you’ll find:

  • Two Workflows:

    • HandleEatsOrder – receives an order, waits for a restaurant’s approval, prepares food, then invokes a child workflow.
    • DeliverOrder – simulates delivery of the order.
  • One Activity Interface (OrderActivities) and its implementation (OrderActivitiesImpl), responsible for printing/logging order-related messages.

  • A Worker Starter (WorkerStarter.java) that registers workflows and activities on the “EatsTaskList.”

  • A Workflow Starter (WorkflowStarter.java) that kicks off (and signals) the HandleEatsOrder instance.

  • A logback.xml under src/main/resources for SLF4J/Logback configuration, allowing logs to be captured in both console and the Cadence Web UI.

The rest of this document explains:

  1. How Does the Code Work
  2. How to Run the Code from Command Line
  3. Cadence Learning Process and Implementation
  4. Feedback on This Process

1. How Does the Code Work

At a high level, this project implements a two-step Uber-Eats order flow:

  1. HandleEatsOrder Workflow

    • Start Parameters
      • userId - a randomly generated UUID representing the customer.
      • orderId - a randomly generated UUID for the specific cart/order.
      • restaurantId - a randomly generated UUID for the restaurant.
      • items - a List<String> with order contents (e.g., ["cheeseburger", "diet coke"]).
    • Flow Steps
      1. Print/Log “Order Received”
        • The workflow calls OrderActivities.notifyOrderReceived(...), which logs
          [Activity] Your order received! user=<userId>, order=<orderId>, restaurant=<restaurantId>, items=[...]
          
          to both console and Cadence UI.
      2. Await Restaurant Signal
        • The workflow registers a signal method acceptOrder(boolean accepted). Internally, it executes
          Workflow.await(() -> orderAccepted);
          blocking until some external thread (in our case, WorkflowStarter) invokes wf.acceptOrder(true).
      3. Preparation Sleep
        • Once orderAccepted == true, the workflow calls
          Workflow.sleep(Duration.ofSeconds(3));
          to simulate “food preparation time.”
      4. Invoke Child Workflow DeliverOrder
        • We create a typed child workflow stub:
          DeliverOrder child = Workflow.newChildWorkflowStub(
            DeliverOrder.class,
            ChildWorkflowOptions.newBuilder()
                .setExecutionStartToCloseTimeout(Duration.ofSeconds(30))
                .build()
          );
        • Then call child.deliver(orderId), which transitions into the child workflow logic.
      5. (Child Workflow) Delivery
        • Inside DeliverOrderImpl.deliver(String orderId), we log:
          wfLogger.info("ChildWorkflow: Starting delivery for order {}", orderId);
          Workflow.sleep(Duration.ofSeconds(4));
          activities.notifyInFrontDoor(orderId);
        • The activity then logs:
          [Activity] Order <orderId> is in front of your door!
          
  2. OrderActivities Interface & Implementation

    • Interface (OrderActivities.java) uses the @ActivityInterface annotation and declares three methods:
      void notifyOrderReceived(String userId, String orderId, String restaurantId, List<String> items);
      void notifyInFrontDoor(String orderId);
      void notifyOrderDelivered(String orderId);
      These notifications appear in your console.
  3. Worker Starter (WorkerStarter.java)

    public class WorkerStarter {
        public static void main(String[] args) {
             WorkflowClient client =
             WorkflowClient.newInstance(
                 new Thrift2ProtoAdapter(
                     IGrpcServiceStubs.newInstance(
                         ClientOptions.newBuilder()
                             .setFeatureFlags(
                                 new FeatureFlags()
                                     .setWorkflowExecutionAlreadyCompletedErrorEnabled(true))
                             .setPort(Constants.Port)
                             .build())),
                 WorkflowClientOptions.newBuilder().setDomain(Constants.CadenceDomain).build());
    
         WorkerFactory factory = WorkerFactory.newInstance(client);
         Worker worker = factory.newWorker(Constants.TaskList);
    
         worker.registerWorkflowImplementationTypes(
             HandleEatsOrderImpl.class, DeliverOrderImpl.class);
         worker.registerActivitiesImplementations(new OrderActivitiesImpl());
    
         factory.start();
        }
    }
    • What it does: Connects to a Cadence server on localhost:7933 in domain cadence-domain.
    • Registers both HandleEatsOrderImpl and DeliverOrderImpl under the same task list EatsTaskList.
    • Registers the activity implementation OrderActivitiesImpl.
    • Starts polling for tasks—now the worker can pick up both workflow and activity tasks.
  4. Workflow Starter (WorkflowStarter.java)

    public class WorkflowStarter {
        public static void main(String[] args) {
         WorkflowClient client =
             WorkflowClient.newInstance(
                 new Thrift2ProtoAdapter(
                     IGrpcServiceStubs.newInstance(
                         ClientOptions.newBuilder()
                             .setFeatureFlags(
                                 new FeatureFlags()
                                     .setWorkflowExecutionAlreadyCompletedErrorEnabled(true))
                             .setPort(Constants.Port)
                             .build())),
                 WorkflowClientOptions.newBuilder()
                     .setDomain(Constants.CadenceDomain)
                     .build());
    
         WorkflowOptions workflowOptions = new WorkflowOptions.Builder()
                 .setTaskList(Constants.TaskList)
                 .setExecutionStartToCloseTimeout(Duration.ofSeconds(Constants.StartToCloseTimeout))
                 .build();
    
         HandleEatsOrder wf = client.newWorkflowStub(HandleEatsOrder.class, workflowOptions);
    
         WorkflowClient.start(wf::handleOrder,
             UUID.randomUUID().toString(),
             UUID.randomUUID().toString(),
             UUID.randomUUID().toString(),
             Arrays.asList("cheeseburger", "diet coke"));
    
         try { Thread.sleep(2000); } catch (InterruptedException e) {}
         
         // 3. Send an accept signal to your workflow
         wf.acceptOrder(true);
        }
    }
    • Sequence:
      1. Create a typed stub for HandleEatsOrder.
      2. Call WorkflowClient.start(...), which sends a “start workflow” request to Cadence.
      3. Sleep 2 seconds to ensure the workflow execution is up & running (avoiding race conditions).
      4. Call wf.acceptOrder(true), sending the signal that “the restaurant accepted the order.”
    • After signaling, HandleEatsOrderImpl::handleOrder can proceed past the Workflow.await(...) line.
  5. Cadence Configuration

    • Domain: cadence-domain (must be registered in Cadence beforehand).
    • Task List: EatsTaskList (all workflows/activities poll on this).
    • Timeouts: Activities, the child workflow and the main workflow use 30 second timeouts defined in Constants.java.

2. How to Run the Code from Command Line

Below are step-by-step instructions for both PowerShell (Windows) and Bash Shell (macOS/Linux). By default, this project uses the Gradle Wrapper (gradlew / gradlew.bat) so you do not need to install Gradle globally.

2.1. Preliminaries

  1. Ensure Cadence Server is Running

    • If you have Docker installed, you can spin up a local Cadence server quickly:
      docker run --rm --name cadence-server -p 6379:6379 -p 7933:7933 ubercadence/server:latest
    • In another shell, register the domain:
      cadence --domain cadence-domain domain register

      If you don’t have the Cadence CLI, you can download it from releases or use a Docker container:

      docker run --rm -v "${PWD}:/cadence" ubercadence/cli:latest --domain cadence-domain domain register
  2. Clone this repo

    git clone https://github.com/Bueller87/cadence-handle-eats-order.git 
    
  3. Grant Execute Permission (macOS/Linux)

    chmod +x gradlew

2.2. Build the Project

PowerShell (Windows)

# From project root:
.\gradlew.bat clean build --refresh-dependencies

Bash (macOS/Linux)

# From project root:
./gradlew clean build --refresh-dependencies
  • What this does:
    • Downloads all dependencies (Cadence client, SLF4J, Logback).
    • Compiles all .java files.
    • Copies logback.xml into build/resources/main/.
    • Runs any tests (there are none by default).
    • Produces a JAR under build/libs/.

2.3. Run WorkerStarter (Left Side)

# From project root:
.\gradlew.bat runWorker

2.4. Run WorkerStarter (Right Side)

open a second new powershell instance

# From project root:
.\gradlew.bat runWorkflow

2.5. Side by Side Results in VS Code

Side by Side Results in VS Code

2.6. Domain View Results in Cadence Web

Domain View

2.7. Handle Order Workflow Results in Cadence Web

Handle Order Workflow

2.8. Deliver Order Workflow Results in Cadence Web

Deliver Order Child Workflow

3. Cadence Learning Process and Implementation

Below is a chronological breakdown of my Cadence learning and how it informed this implementation:

  1. Initial Research

    • Objective: Understand core Cadence concepts—workflows, activities, task lists, timeouts, signals and child workflows.
    • Resources Consulted:
    • Key Takeaways:
      • A Workflow is durable, single-threaded, and only contains deterministic (predictable ) code & Cadence API calls.
      • An Activity is non-deterministic, can call external systems, and must specify timeouts.
      • Signals can be sent to a running workflow to influence its path.
      • Child Workflows allow one workflow to spin up sub-workflows and await their completion.
  2. Setting Up the Environment

    • Spun up a local Cadence docker container.
    • Registered a test domain (cadence-domain).
  3. Hello-World Workflow

    • Started by replicating a minimal Cadence Java sample (a “HelloWorld” that prints “hello” → sleeps → prints “world”).
    • Verified:
      1. Code structure: Interface + Implementation for workflow and activity.
      2. How WorkerFactory and WorkflowClient connect to localhost:7933.
      3. How Workflow.sleep(...) appears as a timer in the History.
  4. Signal and Sleep Patterns

    • Extended to include a Signal:
      @SignalMethod void someSignal(String data);
      Verified the workflow could block on Workflow.await(...) and unblock upon receiving a signal.
    • Verified Workflow.await(...) and Workflow.sleep(...) both generate events in the UI.
  5. Child Workflows

    • Added a child workflow (ChildWorkflow) that returns a value.
    • Called the child via a stub:
      ChildWorkflow child = Workflow.newChildWorkflowStub(
        ChildWorkflow.class,
        ChildWorkflowOptions.newBuilder()
          .setExecutionStartToCloseTimeout(...)
          .build()
      );
      String result = child.execute(...);
    • Observed how parent workflow history included events for “StartChildWorkflow” and “ChildWorkflowCompleted.”
  6. Combining All Pieces

    • Defined a realistic example: “Uber-Eats Order” flow with:
      • Activities to log order reception & delivery.
      • Signal to accept/reject.
      • A 3s preparation sleep.
      • A 4s child workflow for delivery.
      • Final log when delivery completes.
  7. Testing and Iteration

    • Manually tested in both PowerShell and Bash.
    • Confirmed logs appear correctly in both console and UI. Though at this time I cannot find logs in the console.
    • Adjusted timeouts when receiving IllegalStateException (added ScheduleToCloseTimeout).

4. Feedback on This Process

Below is my feedback on how this Cadence learning & implementation process went, with suggestions for improvement:

Area What Went Well What Can Be Improved
Official Documentation - Clear examples for “HelloWorld,” “Signal,” “Child” - The Java SDK guide has gaps: workflow/activity timeout requirements aren’t front-and-center.
- No unified “getting started” for Java+VS Code specifically.
Sample Repositories - GitHub demos show code patterns (Interface/Impl). - Many samples are in multi-module layouts or require heavy boilerplate.
- Lack of a single, end-to-end “Uber-Eats”-style case.
Timeout/Error Guidance - When IllegalStateException occurs, error is clear. - Exception message (“Either ScheduleToClose or both ScheduleToStart & StartToClose required”) wasn’t obvious if you haven’t seen it before.
- Would be nice if the docs recommended a “minimal working” ActivityOptions snippet in every example.
Logging & UI Visibility - Workflow.getLogger(...) examples exist, but rarely in main docs. - Documentation could explicitly show “if you use SLF4J in workflow, it will not appear in UI; use Workflow.getLogger instead.”
- More examples showing “Activity Logs” vs “Workflow Logs.”
Community Help / Blogs - StackOverflow questions often drill into specific errors (e.g., missing timeouts). - Answers are scattered: need to search multiple threads to get a complete picture.
- An “official” troubleshooting guide for common pitfalls (timeouts, logging, signals) would be invaluable.

Proposed Improvements

  1. Enhanced Java SDK Quickstart

    • A one-page “Java + Gradle + VS Code” quickstart guide in official docs, showing:
      • How to generate and run the Gradle wrapper.
      • How to import into VS Code and resolve dependencies.
      • A minimal sample with a signal + sleep + activity + child-workflow.
  2. Default Timeouts in Samples

    • Every code snippet in the Java SDK docs should include:
      ActivityOptions.newBuilder()
          .setScheduleToCloseTimeout(Duration.ofMinutes(1))
          .build();
    • By default, so newcomers aren’t bitten by missing-timeout errors.
  3. Logging Guidance

    • A dedicated “Logging & Observability” section that:
      • Differentiates between Workflow.getLogger(...) vs plain SLF4J in workflow code.
      • Shows a complete logback.xml snippet.
      • Explains where those logs show up in the Web UI (History vs Activity Logs panels).
  4. Template Repo

    • Provide a standalone “uber-eats-cadence” template repository (similar to this one), so developers can clone & immediately run a realistic end-to-end scenario without building everything from scratch.
  5. Expanded “Troubleshooting”

    • In docs, add a dedicated sub-page for common errors:
      • IllegalStateException: Either ScheduleToClose or both ScheduleToStart and StartToClose timeouts are required
      • WorkflowServiceException from signaling too early
      • “Workflow not found” / “TaskList not found” errors
      • “Logging does not appear in UI” and how to fix with Workflow.getLogger(...) or Logback.
  6. More Recent End to End Demos on YouTube

    • Most content on YouTube is from before 2020.
    • Developers might get the impression that this is old technology.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages