4. 第一个Temporal工作流:编写HelloWorld工作流、注册活动、启动工作流执行

本章将带领你从零搭建第一个Temporal工作流——HelloWorld。通过这个最小化示例,你将掌握Temporal工作流开发的核心三要素:工作流定义、活动实现以及客户端启动。这是后续所有复杂调度与OpenAPI扩展的基础。

4.1 工作流与活动的角色划分

在Temporal中,工作流(Workflow)负责编排业务逻辑的执行顺序,而活动(Activity)负责执行具体的业务操作。两者通过任务队列(Task Queue)进行通信。下表列出了它们的关键区别:

维度 工作流(Workflow) 活动(Activity)
职责 编排与协调,决定“做什么” 执行具体任务,决定“怎么做”
执行时长 可长期运行(天/月/年) 通常较短(秒/分钟)
状态持久化 自动持久化,支持重放 不自动持久化,需自行处理
重试策略 默认不重试 默认支持重试
代码限制 必须确定性(无随机/IO) 无限制(可调用外部API)
设计原则:将所有可能失败的操作(如网络请求、文件读写)放入活动,工作流只负责调用活动并处理其结果。这能最大化利用Temporal的自动重试与故障恢复能力。

4.2 HelloWorld工作流架构

下图展示了HelloWorld工作流的完整执行流程,包含Worker、Temporal Server以及客户端三方的交互关系:

HelloWorld 工作流执行架构 客户端 StartWorkflow() Temporal Server 任务队列调度 Worker 执行工作流与活动 提交任务 分发任务 HelloWorld 工作流 SayHello 活动 调用 异步结果通知 说明:Worker 从任务队列拉取任务,执行工作流代码,工作流再通过 Stub 调用活动,所有状态由 Temporal Server 持久化。

4.3 编写HelloWorld工作流与活动

我们使用Java SDK作为示例,其他语言(Go、Python、TypeScript)的概念完全一致。整个开发过程分为三个步骤:

4.3.1 定义活动接口

活动接口使用 @ActivityInterface 注解标记,Temporal会自动扫描并注册:

import io.temporal.activity.ActivityInterface;
import io.temporal.activity.ActivityMethod;

@ActivityInterface
public interface HelloActivities {

    @ActivityMethod
    String sayHello(String name);
}

4.3.2 实现活动逻辑

活动实现类可以包含任意业务代码,例如调用外部API、读写数据库等:

public class HelloActivitiesImpl implements HelloActivities {

    @Override
    public String sayHello(String name) {
        // 模拟业务处理,实际场景可替换为REST调用或数据库查询
        String greeting = "Hello, " + name + "! 当前时间: " + System.currentTimeMillis();
        System.out.println("活动执行: " + greeting);
        return greeting;
    }
}

4.3.3 定义工作流接口与实现

工作流接口使用 @WorkflowInterface 注解,实现类通过 Workflow.newActivityStub 创建活动代理:

import io.temporal.workflow.WorkflowInterface;
import io.temporal.workflow.WorkflowMethod;
import io.temporal.workflow.Workflow;
import io.temporal.activity.ActivityOptions;
import java.time.Duration;

@WorkflowInterface
public interface HelloWorkflow {

    @WorkflowMethod
    String greet(String name);
}

public class HelloWorkflowImpl implements HelloWorkflow {

    private final HelloActivities activities;

    public HelloWorkflowImpl() {
        // 配置活动选项:设置任务队列与超时
        ActivityOptions options = ActivityOptions.newBuilder()
                .setTaskQueue("HELLO_TASK_QUEUE")
                .setStartToCloseTimeout(Duration.ofSeconds(10))
                .build();
        this.activities = Workflow.newActivityStub(HelloActivities.class, options);
    }

    @Override
    public String greet(String name) {
        // 工作流编排:调用活动并返回结果
        String result = activities.sayHello(name);
        // 可在此处添加更多编排逻辑,例如条件判断、循环等
        return result;
    }
}
重要约束:工作流实现代码必须保持确定性(Deterministic)。禁止使用 System.currentTimeMillis()、Random、Thread.sleep() 等非确定性操作。如果需要获取当前时间,应使用 Workflow.currentTimeMillis()。

4.4 注册活动与启动Worker

Worker是连接Temporal Server与业务代码的桥梁。它负责注册工作流与活动实现,并持续轮询任务队列:

import io.temporal.client.WorkflowClient;
import io.temporal.serviceclient.WorkflowServiceStubs;
import io.temporal.worker.Worker;
import io.temporal.worker.WorkerFactory;

public class HelloWorker {

    public static void main(String[] args) {
        // 1. 连接Temporal Server(默认地址:127.0.0.1:7233)
        WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
        WorkflowClient client = WorkflowClient.newInstance(service);

        // 2. 创建Worker工厂,绑定任务队列
        WorkerFactory factory = WorkerFactory.newInstance(client);
        Worker worker = factory.newWorker("HELLO_TASK_QUEUE");

        // 3. 注册工作流与活动实现
        worker.registerWorkflowImplementationTypes(HelloWorkflowImpl.class);
        worker.registerActivitiesImplementations(new HelloActivitiesImpl());

        // 4. 启动Worker,开始轮询
        factory.start();
        System.out.println("Worker 已启动,等待任务...");
    }
}

4.5 启动工作流执行

客户端通过 WorkflowClient 创建工作流Stub,然后调用 @WorkflowMethod 启动执行:

import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowOptions;
import io.temporal.serviceclient.WorkflowServiceStubs;

public class HelloClient {

    public static void main(String[] args) {
        // 1. 连接Temporal Server
        WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
        WorkflowClient client = WorkflowClient.newInstance(service);

        // 2. 配置工作流选项(指定任务队列与工作流ID)
        WorkflowOptions options = WorkflowOptions.newBuilder()
                .setTaskQueue("HELLO_TASK_QUEUE")
                .setWorkflowId("hello-workflow-" + System.currentTimeMillis())
                .build();

        // 3. 创建工作流Stub
        HelloWorkflow workflow = client.newWorkflowStub(HelloWorkflow.class, options);

        // 4. 同步启动工作流(阻塞等待结果)
        String result = workflow.greet("Temporal");
        System.out.println("工作流执行结果: " + result);
    }
}

4.6 完整执行流程总结

下图将上述步骤整合为一条完整的执行链路,帮助你理解从客户端提交到Worker执行再到结果返回的全过程:

HelloWorld 工作流执行时序 ① 客户端调用 ② Server调度 ③ Worker执行 ④ 活动返回结果 ① 客户端通过 WorkflowStub 调用 greet() 方法,触发工作流启动 ② Temporal Server 将任务放入 HELLO_TASK_QUEUE,等待 Worker 拉取 ③ Worker 拉取任务,执行 HelloWorkflowImpl,内部调用 SayHello 活动 ④ 活动执行完毕,结果沿原路径返回给客户端,工作流完成

4.7 常见问题与避坑指南

  • 任务队列不匹配:Worker注册的任务队列名称必须与客户端 WorkflowOptions 中设置的完全一致,否则工作流无法被Worker拾取。
  • 活动未注册:Worker必须显式调用 registerActivitiesImplementations(),否则Temporal会抛出 ActivityNotRegisteredException。
  • 工作流超时:如果活动执行时间超过 setStartToCloseTimeout 设置的值,Temporal会标记活动失败并触发重试。
  • 非确定性错误:工作流代码中使用了 new Random() 或 System.currentTimeMillis(),导致重放时结果不一致,Temporal会抛出 NonDeterministicWorkflowError。
验证方法:启动Worker后,运行客户端程序。如果控制台输出 Hello, Temporal! 当前时间: ...,则说明第一个Temporal工作流已成功运行。你可以通过Temporal Web UI(默认端口8233)查看工作流执行历史。