Skip to main content
  1. Temporal Internals/

Chapter 1: Public API and Service Routing #

Welcome! Before we dive into Temporal, imagine walking into a busy restaurant. There is a host at the door, waiters moving around, and a kitchen in the back. You don’t walk into the kitchen and start cooking. Instead, you tell the host what you want, and the host directs you to the right place. In Temporal, that host is the Public API.

All Temporal SDK and CLI clients talk to the Temporal server through this API. It is the “front door” of the server. When you start a workflow, check its status, or send it a signal, your request first arrives at this door. Then the API routes your request to the correct internal service: frontend, matching, history, or worker.

This chapter will walk you through what that front door looks like, why it exists, and how it routes requests. We’ll keep things beginner-friendly and use a simple workflow as our example.


Why This Abstraction Exists #

If you’ve ever used a Temporal SDK, you probably called something like:

c.ExecuteWorkflow(ctx, options, MyWorkflow)

You did not need to know which server component handled your request. That is the magic of the Public API. It creates a clean boundary:

  • Clients (SDKs and CLI) only need to know how to speak the API.
  • Server internals (history, matching, etc.) can change without breaking clients.
  • Workers (the code that runs your workflows) don’t need to know where the server stores data or how it routes tasks.

Without this front door, every client would need to know the messy details of every internal service. The API is the polite host that hides all that complexity.


A Concrete Use Case: Starting a Workflow #

Let’s use a very common action: starting a workflow. You have a simple HelloWorld workflow. You want to start it from the CLI and then from a Go SDK.

From the CLI #

With the Temporal CLI, you can start a workflow like this:

temporal workflow start \
  --task-queue hello \
  --workflow-id hello-1

You’ll see something like:

Workflow start succeeded:
  RunID: 12345678-... 

That’s it. The CLI turned your command into a gRPC request and sent it to Temporal’s Public API. But what happened behind the scenes? The request was routed to the right services, a workflow was created, and a task was queued for a worker. We’ll see step by step soon.

From the Go SDK #

In Go, the same action looks like this:

// client.go (simplified)
c, _ := client.Dial(client.Options{})

execute, _ := c.ExecuteWorkflow(context.Background(), client.StartWorkflowOptions{
    ID:        "hello-1",
    TaskQueue: "hello",
}, HelloWorld)

The ExecuteWorkflow call sends a StartWorkflowExecution request. The response gives you a RunID, which identifies this specific workflow execution.

Both examples hide a whole chain of internal routing. That chain is exactly what we are going to uncover.


Key Concepts #

Let’s break down the pieces. Think of a restaurant again.

1. gRPC and Protobuf: The Language the API Speaks #

The Public API isn’t a human language. It’s a protocol defined using Protobuf (Protocol Buffers) and transported over gRPC.

  • Protobuf defines the exact structure of a request or response (like an order form).
  • gRPC is the transport mechanism (like the waiter carrying the order).

The protocol is defined in the module go.temporal.io/api. For example, the request for starting a workflow is called StartWorkflowExecutionRequest. You don’t need to understand every field right now. Just know that all clients and the server share this common “language”.

2. The Frontend Service: The Host #

The frontend service is the actual server component that receives the gRPC request. It checks if the request is valid, decides which internal service should handle it, and passes it along. It’s the host who reads the order and says, “Kitchen, we need a pasta! Waiter, table 5 is ready!”

3. Matching Service: The Order Queue #

The matching service is responsible for task queues. When you start a workflow, the workflow itself isn’t magically executed. A workflow task is created and placed into a task queue. The matching service holds that queue and delivers tasks to workers when they are ready. Think of it as the “order ticket” board in the kitchen: when a chef is available, they take the ticket and start cooking.

4. History Service: The Memory #

The history service is the sensitive memory of Temporal. It stores the state of each workflow execution. When you start a workflow, the history service persists the workflow’s information and generates a RunID. It also records events that happen during the workflow’s life. If the server restarts, the history service remembers everything.

5. Worker: The Chef #

The worker is not a server component. It’s your application code that runs workflow tasks. The worker receives a task from the matching service, executes your HelloWorld function, and reports the result back. In our restaurant analogy, the worker is the chef cooking your meal.


How a Request Flows: Step-by-Step #

Let’s trace exactly what happens when you start a workflow from the SDK.

sequenceDiagram participant C as Client (SDK/CLI) participant F as Frontend Service participant M as Matching Service participant H as History Service participant W as Worker C->>F: StartWorkflowExecution(...) F->>H: Persist workflow state & run ID F->>M: Add workflow task to queue M->>W: Deliver workflow task W->>W: Run your workflow code W-->>F: Report result

Let’s walk through it:

  1. Client sends request – Your SDK sends a gRPC message called StartWorkflowExecution to the frontend service.
  2. Frontend validates & routes – The frontend checks the request, creates a new RunID, and decides: “This workflow needs to be persisted, and a task needs to be queued.”
  3. History service saves state – The frontend asks the history service to store the workflow execution information and the new RunID.
  4. Matching service queues task – The frontend asks the matching service to put a workflow task on the task queue.
  5. Worker picks up task – A worker that is polling that task queue receives the task.
  6. Worker executes code – The worker calls your HelloWorld workflow function.
  7. Result reports back – The worker reports the result, and the frontend can confirm that the workflow start was successful.

All of this happens in milliseconds. The client only sees: “Here is your RunID.”


Let’s Open the Hood: Code Walkthrough #

In the Temporal server repository (go.temporal.io/server), the frontend service is implemented in files under service/frontend. The handler for StartWorkflowExecution is in service/frontend/workflow_handler.go.

Here is a simplified version of what that handler might look like:

// service/frontend/workflow_handler.go (simplified)
func (h *WorkflowHandler) StartWorkflowExecution(ctx context.Context, req *workflowservice.StartWorkflowExecutionRequest) (*workflowservice.StartWorkflowExecutionResponse, error) {
    runID := uuid.New()
    h.history.StartWorkflowExecution(ctx, req, runID) // save state
    h.matching.AddWorkflowTask(ctx, req, runID)      // queue work
    return &workflowservice.StartWorkflowExecutionResponse{RunId: runID}, nil
}

Let’s break it down:

  • runID := uuid.New() – Generates a unique identifier for this workflow execution.
  • h.history.StartWorkflowExecution(...) – Tells the history service to persist the workflow state.
  • h.matching.AddWorkflowTask(...) – Tells the matching service to add a workflow task to the queue.
  • The response returns the RunID to the client.

Of course, the real code is much more complex. It has validations, retries, authentication, and more. But this is the core idea.

The actual routing decision is simple: the history service handles state, and the matching service handles task delivery. The frontend knows this and distributes the work accordingly.


What About Other Request Types? #

Starting a workflow is just one type of request. The same routing idea applies to other actions:

  • Querying workflow status – The frontend asks the history service for the current state.
  • Sending a signal to a workflow – The frontend tells the history service to record a signal event, then asks matching service to create a task so the worker can process it.
  • Recording workflow results – The worker sends results through the frontend, which routes them to the history service.

In every case, the frontend is the host. It looks at the request and decides which internal service should handle it. That is service routing.


Conclusion #

The Public API and Service Routing is the front door of Temporal Server. It gives every SDK and CLI client a consistent way to interact with the server. The frontend service is the concrete implementation of that API, and it routes requests to the matching service, history service, and workers.

You learned:

  • The Public API is defined in go.temporal.io/api and transported using gRPC.
  • The frontend service receives all client requests.
  • Matching service manages task queues.
  • History service stores workflow state.
  • Workers run your code.

Now that you know how requests enter the system and are routed, the next logical question is: How does Temporal remember all that workflow state? That’s where durable persistence comes in.

Continue to Chapter 2: Durable Persistence.


Generated by AI Codebase Knowledge Builder