- 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)
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 viaNewGRPCClient()startWorkflow(),describeWorkflow(): gRPC-wired operations- Others: stubs or NOT_IMPLEMENTED
-
grpc_client.go: Low-level gRPC connection management
NewGRPCClient(): Dials Temporal at port 7233GetWorkflowServiceStub(): Returnsworkflowservice.WorkflowServiceClientGetOperatorServiceStub(): Returnsoperatorservice.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)
-
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
-
Add OperatorService support (namespaces, cluster, search attrs)
- Create separate stub:
operatorServiceClient := NewGRPCClient().GetOperatorServiceStub() - Add methods to handler for each operation
- Create separate stub:
-
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:
WorkflowIdnotWorkflowIDRunIdnotRunIDWorkflowType(message) notWorkflowTypeStringTaskQueue(message) notTaskQueueName
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→ 404codes.InvalidArgument→ 400codes.Unavailable→ 503- others → 500
Questions / Blockers
None currently. gRPC wiring is straightforward pattern-matching.