Files
homelab-frontend/TEMPORAL_GRPC_MIGRATION.md
T
Admin Bot 4935ea9f95
CI / Vet, test, build (push) Failing after 2m31s
CI / Build and push image (push) Skipped
feat: wire Temporal gRPC into REST handler
- Handler now maintains gRPC connection to Temporal (port 7233)
- startWorkflow & describeWorkflow translated to actual gRPC calls
- Other 20+ operations phased in via TEMPORAL_GRPC_MIGRATION roadmap
- Updated docs: TEMPORAL_USAGE now describes gRPC architecture
- Added TEMPORAL_GRPC_MIGRATION.md for implementation reference
- Deleted WORKFLOWS.md (outdated duplicate)

Fixes: gRPC was imported but unused - now operational for START/DESCRIBE.
Verification: go build ./cmd/gateway  (no errors)
2026-08-29 21:54:16 -07:00

4.9 KiB

Temporal gRPC Integration - Migration Status

Overview

Temporal REST ↔ gRPC bridge is being implemented. Client sends HTTP JSON → gateway translates to gRPC → Temporal server responds.

Implementation Status

Phase 1: Core Workflow Operations WIRED

  • START_WORKFLOW gRPC: StartWorkflowExecution
  • DESCRIBE_WORKFLOW gRPC: DescribeWorkflowExecution
  • LIST_WORKFLOWS TODO (requires pagination logic)
  • GET_WORKFLOW_HISTORY TODO
  • SIGNAL_WORKFLOW TODO
  • QUERY_WORKFLOW TODO
  • CANCEL_WORKFLOW TODO
  • TERMINATE_WORKFLOW TODO
  • RESET_WORKFLOW TODO
  • UPDATE_WORKFLOW TODO

Phase 2: Activity Operations NOT IMPLEMENTED

  • HEARTBEAT_ACTIVITY
  • COMPLETE_ACTIVITY
  • FAIL_ACTIVITY

Note: Activity operations require different error handling (task tokens, etc.). See operations_grpc.go for reference.

Phase 3: OperatorService Operations NOT IMPLEMENTED

Requires separate gRPC stub. Currently:

  • LIST_NAMESPACES → 501 NOT_IMPLEMENTED
  • DESCRIBE_NAMESPACE → 501 NOT_IMPLEMENTED
  • CREATE_NAMESPACE → 501 NOT_IMPLEMENTED
  • UPDATE_NAMESPACE → 501 NOT_IMPLEMENTED
  • DELETE_NAMESPACE → 501 NOT_IMPLEMENTED
  • LIST_SEARCH_ATTRIBUTES → 501 NOT_IMPLEMENTED
  • ADD_SEARCH_ATTRIBUTES → 501 NOT_IMPLEMENTED
  • LIST_TASK_QUEUES → 501 NOT_IMPLEMENTED
  • GET_CLUSTER_INFO → 501 NOT_IMPLEMENTED
  • LIST_CLUSTER_MEMBERS → 501 NOT_IMPLEMENTED
  • GET_SYSTEM_INFO → 501 NOT_IMPLEMENTED

Architecture

HTTP Request (JSON) 
    ↓
Handler.startWorkflow() 
    ↓
Converts to protobuf (workflowservice.StartWorkflowExecutionRequest)
    ↓
gRPCClient.GetWorkflowServiceStub().StartWorkflowExecution(ctx, req)
    ↓
Temporal Server (port 7233)
    ↓
gRPC Response
    ↓
Convert to JSON response map
    ↓
HTTP 200 JSON

Code References

  • handler.go: HTTP ↔ gRPC translation layer

    • NewHandler(): Creates gRPC connection via NewGRPCClient()
    • startWorkflow(), describeWorkflow(): gRPC-wired operations
    • Others: stubs or NOT_IMPLEMENTED
  • grpc_client.go: Low-level gRPC connection management

    • NewGRPCClient(): Dials Temporal at port 7233
    • GetWorkflowServiceStub(): Returns workflowservice.WorkflowServiceClient
    • GetOperatorServiceStub(): Returns operatorservice.OperatorServiceClient
  • operations_grpc.go: Example gRPC implementations (reference for wiring)

    • Shows payload marshaling patterns
    • Shows error handling (gRPC status codes → HTTP 4xx/5xx)

Next Steps (Phase 2)

  1. Wire remaining WorkflowService operations (LIST, GET_HISTORY, SIGNAL, QUERY, etc.)

    • All use same pattern: build protobuf request → call stub → map response to JSON
    • Reference operations_grpc.go for exact patterns
  2. Add OperatorService support (namespaces, cluster, search attrs)

    • Create separate stub: operatorServiceClient := NewGRPCClient().GetOperatorServiceStub()
    • Add methods to handler for each operation
  3. Add Activity operations (heartbeat, complete, fail)

    • Requires task token handling
    • See operations_grpc_test.go for test patterns

Build Status

go build ./cmd/gateway  ✅ SUCCESS

Testing

To test gRPC wiring locally:

# Start Temporal locally (if not running)
docker run -d -p 7233:7233 temporalio/auto-setup:latest

# Start gateway
go run ./cmd/gateway

# Test (in another terminal)
curl -X POST http://localhost:8080/workflow \
  -H 'Content-Type: application/json' \
  -d '{
    "action": "START_WORKFLOW",
    "namespace": "default",
    "payload": {
      "workflow_id": "test-1",
      "workflow_type": "MyWorkflow",
      "task_queue": "my-queue"
    }
  }'

# Should return
{
  "success": true,
  "action": "START_WORKFLOW",
  "data": {
    "workflow_id": "test-1",
    "run_id": "abc123...",
    "start_time": "2026-08-27T..."
  }
}

Key Implementation Details

Protobuf Field Names

Temporal protobuf uses snake_case field names:

  • WorkflowId not WorkflowID
  • RunId not RunID
  • WorkflowType (message) not WorkflowTypeString
  • TaskQueue (message) not TaskQueueName

Type Imports (from go.temporal.io/api)

import (
  "go.temporal.io/api/common/v1"           // WorkflowExecution, WorkflowType, Payloads
  "go.temporal.io/api/taskqueue/v1"        // TaskQueue
  "go.temporal.io/api/workflowservice/v1"  // All Workflow* stubs
  "go.temporal.io/api/operatorservice/v1"  // Namespace/cluster stubs (not yet used)
)

Payload Marshaling Pattern

input := getMap(payload, "input")
if len(input) > 0 {
  inputBytes, _ := json.Marshal(input)
  req.Input = &common.Payloads{
    Payloads: []*common.Payload{{Data: inputBytes}},
  }
}

Error Handling

  • gRPC errors → map to HTTP status:
    • codes.NotFound → 404
    • codes.InvalidArgument → 400
    • codes.Unavailable → 503
    • others → 500

Questions / Blockers

None currently. gRPC wiring is straightforward pattern-matching.