gRPC Gateway Answer Hub
Expose one public gRPC API over independent answer providers, forwarding matching methods to provider gRPC APIs automatically and routing one explicitly through broker request-response.
Examplegrpcprotobufgatewayapi-facadeapi-compositionapibrokerrequest-responseuidashboard
🌅 Horizon
Example at a Glance
Overview
Clients should not need to know which service answers which question.
This example gives them one public gRPC Answer API, with a single
.proto contract, for the current time, date, day, day tip,
and Amsterdam zone time. A gateway service owns that boundary: it
forwards most methods to the provider gRPC APIs that already offer them,
and routes the one method without a provider API through a broker
request-response topic.
It is the gRPC counterpart of gateway-answer-hub (REST) and
soap-gateway-answer-hub (SOAP). Only the API style changes:
every API is declared with style:grpc, and the services,
broker, UI, and deployment stay the same.
Draft: the gRPC style is specified, but its code generation is not implemented yet, so this example cannot be generated or run until gRPC support lands.
Architecture
The UI calls only the gateway gRPC service. Four methods are forwarded automatically to included APIs; zone time is routed explicitly to a broker responder.
What It Demonstrates
@api—style:grpcAPIs with their own protobuf package and server reflection, each generating a proto3 contract, typed server, and typed client.@api—includecomposes two provider gRPC APIs into one public gateway gRPC service that adds its own method.@service—impl gateway api ... includingforwards matching operations automatically, and an explicitconnectroutes the rest.@broker— a request-response topic answered by a service that exposes no API.@expression— small provider expressions that produce each answer.@dashboardand@ui— a generated interface whose buttons call only the gateway API.@configand@deploy— shared service configuration and deployment of the gateway, providers, broker, and UI.
Expected Result
Each dashboard button calls one gRPC method of the public gateway API and displays its answer. Time, date, day, and day tip come from their provider services through automatic forwarding; zone time comes from the broker responder through the explicit route.
🧭 Voyage
1. Problem and Constraints
A public API should be declared once and routed without repeating a
connect for every operation that a provider already
implements. Operations that no provider API offers must still be
routable, and an explicit route must be visible in the model. All APIs
use gRPC, so the public contract is one .proto service even
though the methods come from several services. Streaming,
@from-context metadata, and deadlines are out of scope, as
are gateway policies such as authentication and rate limiting.
2. Prerequisites
- Ocean UI with the gateway capability of
@service, API composition in@api, and gRPC code generation (specified, not implemented yet). - A container runtime for the generated deployment.
- Recommended:
gateway-answer-hubandsoap-gateway-answer-hub, the same example with REST and SOAP APIs. - Optional:
grpcurl, orcurlover the Connect protocol, for calling the services directly.
3. Example Structure
0017-grpc-gateway-answer-hub/
├── 00-ggah-info.ocn
├── 10-ggah-api.ocn
├── 20-ggah-broker.ocn
├── 30-ggah-config.ocn
├── 40-ggah-expression.ocn
├── 50-ggah-service-calendar.ocn
├── 50-ggah-service-gateway.ocn
├── 50-ggah-service-time-date.ocn
├── 50-ggah-service-zone-time.ocn
├── 55-ggah-dashboard.ocn
├── 56-ggah-ui.ocn
├── 60-ggah-deploy.ocn
├── architecture.mmd
├── broker-communication.mmd
└── example-info/example-info.html
4. Declare and Compose the gRPC APIs
10-ggah-api.ocn defines the two provider APIs and the public
AnswerGatewayApi, all with style:grpc. Each API
uses the connect engine, declares its own
protoPackage, and serves reflection; Ocean generates a
proto3 contract, typed server, and typed client for each one.
TimeDateApi style:grpc
engine = connect
configType = ApiConfig
version = 1.0.0
protoPackage = oceanlab.examples.answerhub.timedate.v1
reflection = true
rpc getTime() : String
rpc getDate() : String
include reuses the provider operations without repeating
them. The gateway API owns the transport settings, so the included
operations become methods of its own protobuf service, in its package
and its .proto contract.
AnswerGatewayApi style:grpc
engine = connect
configType = ApiConfig
version = 1.0.0
protoPackage = oceanlab.examples.answerhub.answers.v1
reflection = true
include TimeDateApi, CalendarApi
rpc getZoneTime() : String
The effective public service is oceanlab.examples.answerhub.answers.v1.AnswerGatewayApi
with the methods GetTime, GetDate,
GetDay, GetDayTip, and
GetZoneTime; each is served at
/<protoPackage>.<ApiName>/<RpcName>. Every method
takes no input and returns a String, so on the wire the
request is google.protobuf.Empty and the response is a
generated message with one value field, such as
GetTimeResponse.
Each API has its own package: the gateway's contract repeats the providers' methods and their generated messages, which would collide in a shared package.
5. Implement the Gateway Service
50-ggah-service-gateway.ocn implements the public gRPC API as a
gateway. Every name after including is a local
use api alias. getTime, getDate,
getDay, and getDayTip match included operations
and are forwarded automatically from the gateway's gRPC service to the
provider's gRPC service. getZoneTime has no
included counterpart, so it is connected explicitly to the broker topic.
use api TimeDateApi as timeDate
use api CalendarApi as calendar
use broker AnswerHubBroker as broker
impl gateway api AnswerGatewayApi as gw on gwCfg.apiConfig.port
including timeDate, calendar
connect gw.getZoneTime -> broker.answers.zoneTime
An explicit connect on a gateway operation also takes
precedence over automatic forwarding, so the same form can override a
forwarded route.
6. Provide the Answers
TimeDateService and CalendarService implement
their provider gRPC APIs with ordinary impl api and
connect statements to expressions; nothing in a service
changes because an API uses gRPC. Services see each operation in its
Ocean form, not its wire messages.
ZoneTimeService implements no API: it answers the
request-response topic declared in 20-ggah-broker.ocn.
AnswerHubBroker
engine = nats
configType = AnswerHubBrokerConfig
answers.zoneTime : _-String as request-response with timeout:2s
ZoneTimeService
use broker AnswerHubBroker as broker
use expression GetAmsterdamTime as zoneTime
connect broker.answers.zoneTime -> zoneTime
GetAmsterdamTime calls
GetZoneTimeString("Europe/Amsterdam").
7. Call Only the Gateway from the UI
AnswerHubUi uses AnswerGatewayApi, through its
generated gRPC client, and nothing else. Each button on AnswerHubDashboard calls one public
operation, so the UI does not depend on where each answer is produced.
use api AnswerGatewayApi as gateway
connect answers.TimeButton.click -> gateway.getTime
connect answers.ZoneTimeButton.click -> gateway.getZoneTime
8. Select Technologies and Deploy
The APIs use the connect gRPC engine, the broker uses
nats, and the UI is generated with HTMX, Bootstrap, and Go
templates. 60-ggah-deploy.ocn imports a NATS container and
starts the gateway after its providers and the broker.
GatewayDeploy
service AnswerGatewayService
replica 1
export 9100:AnswerGatewayService.gw
dependsOn TimeDateDeploy, CalendarDeploy, ZoneTimeDeploy, Broker
AnswerHubUiDeploy
service AnswerHubUi
replica 1
export 8085:AnswerHubUi.config
dependsOn GatewayDeploy
The provider gRPC APIs are also exported on ports 9101 and
9102 for direct inspection. The UI does not use them.
9. Validate, Generate, and Run
- Once gRPC generation is available, open the
gRPC Gateway Answer Hubexample in Ocean UI. - Validate and generate the complete model, including the
.protocontract of each gRPC API. - Follow the generated
Readme.mdto start the broker, services, and UI. - Open the UI at
localhost:8085.
10. Verify the Result
- Use the time, date, day, and day tip buttons; each answer is forwarded by the gateway to its provider service.
- Use the Amsterdam zone time button; the answer is returned by
ZoneTimeServicethrough the broker. - List the gateway's services through reflection and confirm that
AnswerGatewayApihas all five methods, although onlyGetZoneTimeis declared locally:grpcurl -plaintext localhost:9100 list grpcurl -plaintext localhost:9100 describe oceanlab.examples.answerhub.answers.v1.AnswerGatewayApi - Call
GetTimeon the gateway and onTimeDateServiceand compare the answers:
Thegrpcurl -plaintext localhost:9100 oceanlab.examples.answerhub.answers.v1.AnswerGatewayApi/GetTime grpcurl -plaintext localhost:9101 oceanlab.examples.answerhub.timedate.v1.TimeDateApi/GetTimeconnectengine also accepts the Connect protocol, so plain HTTP works too:curl -X POST localhost:9100/oceanlab.examples.answerhub.answers.v1.AnswerGatewayApi/GetTime \ -H 'Content-Type: application/json' -d '{}' - Stop
ZoneTimeServiceand callGetZoneTime; the call fails with a gRPC error status after its two-second timeout while the forwarded methods still answer.
11. Experiments
- Add
connect gw.getTime -> broker.answers.zoneTimeand observe that the explicit route overrides automatic forwarding. - Remove
calendarfromincludingand validate;getDayandgetDayTipbecome unresolved public operations. - Remove the
9101and9102exports so that the gateway is the only reachable API. - Give
CalendarApithe gateway'sprotoPackageand compare the generated contracts; the gateway'sGetDayResponseandGetDayTipResponsemessages would now collide with the provider's. - Change
CalendarApitostyle:restand validate; the include is rejected because every included API must use the same style as the composing API.
Executable model
<\> Implementation
Explore the runnable model by responsibility, then select a file to inspect its complete source.
# @ocean-meta-start
# tags:
# - grpc
# - gateway
# - api-facade
# - broker-request-response
# perspective:
# feature: grpc-gateway-answer-hub
# boundedContext: answer-hub
# @ocean-meta-end
@info
name: gRPC Gateway Answer Hub
version: 1.0.0
title: gRPC Gateway Answer Hub
subtitle: One public gRPC API for independent answer providers
shortDescription: A focused gRPC API gateway example with protobuf-backed API composition, automatic forwarding, and an explicit broker-backed operation.
description:
Overview
gRPC Gateway Answer Hub exposes one public gRPC API for time, date, day, day tip, and zone time. It is the gRPC counterpart of Gateway Answer Hub (REST) and SOAP Gateway Answer Hub: the same gateway and routing model, with every API declared as style:grpc.
gRPC contracts
Each API declares its own protobuf package and serves gRPC reflection. Ocean generates a proto3 contract, a typed server, and a typed client for every API from the same definition. The connect engine serves the gRPC, gRPC-Web, and Connect protocols.
Automatic forwarding
The gateway includes the TimeDateApi and CalendarApi. Their operations become methods of the gateway's own protobuf service, package, and .proto contract. Operations with the same name and compatible contracts are forwarded automatically to their owning services.
Explicit routing
Zone time is intentionally not exposed by an internal API. The gateway routes getZoneTime explicitly through a NATS request-response topic to ZoneTimeService.
UI
AnswerHubUi calls only AnswerGatewayApi. Each dashboard action independently invokes one public gRPC method, so users see the gateway as the sole public boundary.
# @ocean-meta-start
# tags:
# - grpc-api
# - gateway-contract
# perspective:
# feature: grpc-gateway-answer-hub
# boundedContext: answer-hub
# @ocean-meta-end
@api
TimeDateApi style:grpc
engine = connect
configType = ApiConfig
version = 1.0.0
protoPackage = oceanlab.examples.answerhub.timedate.v1
reflection = true
rpc getTime() : String
rpc getDate() : String
CalendarApi style:grpc
engine = connect
configType = ApiConfig
version = 1.0.0
protoPackage = oceanlab.examples.answerhub.calendar.v1
reflection = true
rpc getDay() : String
rpc getDayTip() : String
AnswerGatewayApi style:grpc
engine = connect
configType = ApiConfig
version = 1.0.0
protoPackage = oceanlab.examples.answerhub.answers.v1
reflection = true
include TimeDateApi, CalendarApi
rpc getZoneTime() : String
# @ocean-meta-start
# tags:
# - broker
# - nats
# - request-response
# perspective:
# feature: grpc-gateway-answer-hub
# boundedContext: answer-hub
# @ocean-meta-end
@broker
AnswerHubBroker
@perspectives: version:1.0.0, lifestyle:stable
engine = nats
configType = AnswerHubBrokerConfig
tags = primary, shared
answers.zoneTime : _-String as request-response with timeout:2s
# @ocean-meta-start
# tags:
# - configuration
# perspective:
# feature: grpc-gateway-answer-hub
# boundedContext: answer-hub
# service: shared
# @ocean-meta-end
@config
@import config O.broker.nats.config@1.0.0 as BrokerConfig
@import config O.log.config@1.0.0 as LogConfig
ApiConfig
port : Int (default=8080)
CommonServiceConfig
apiConfig : ApiConfig
brokerConfig : BrokerConfig
logConfig : LogConfig
AnswerHubBrokerConfig
brokerConfig : BrokerConfig
logConfig : LogConfig
UiConfig
port : Int (default=8080)
logConfig : LogConfig
# @ocean-meta-start
# tags:
# - expression
# - answer-provider
# perspective:
# feature: grpc-gateway-answer-hub
# boundedContext: answer-hub
# service: shared
# @ocean-meta-end
@expression
GetTimeString
input: _
output: result:String
logic
result = nowTimeString()
GetDateString
input: _
output: result:String
logic
result = nowDateString()
GetDayString
input: _
output: result:String
logic
result = today()
GetDayTip
input: _
output: result:String
logic
result = tipOfTheDay()
GetAmsterdamTime
input: _
output: result:String
logic
result = GetZoneTimeString("Europe/Amsterdam")
# @ocean-meta-start
# tags:
# - service
# - provider
# perspective:
# feature: grpc-gateway-answer-hub
# boundedContext: answer-hub
# service: calendar-service
# @ocean-meta-end
@service
CalendarService
@perspectives: version:1.0.0, lifestyle:stable
use config CommonServiceConfig as svcCfg
impl api CalendarApi as api on svcCfg.apiConfig.port
use expression GetDayString as day
use expression GetDayTip as tip
connect api.getDay -> day
connect api.getDayTip -> tip
# @ocean-meta-start
# tags:
# - service
# - gateway
# - api-facade
# - grpc
# perspective:
# feature: grpc-gateway-answer-hub
# boundedContext: answer-hub
# service: answer-gateway-service
# @ocean-meta-end
@service
AnswerGatewayService
@perspectives: version:1.0.0, lifestyle:stable
use config CommonServiceConfig as gwCfg
use api TimeDateApi as timeDate
use api CalendarApi as calendar
use broker AnswerHubBroker as broker
impl gateway api AnswerGatewayApi as gw on gwCfg.apiConfig.port
including timeDate, calendar
# getTime, getDate, getDay, and getDayTip are automatically forwarded
# from the gateway's gRPC service to the providers' gRPC services.
# Zone time is intentionally broker-backed and therefore routed explicitly.
connect gw.getZoneTime -> broker.answers.zoneTime
# @ocean-meta-start
# tags:
# - service
# - provider
# perspective:
# feature: grpc-gateway-answer-hub
# boundedContext: answer-hub
# service: time-date-service
# @ocean-meta-end
@service
TimeDateService
@perspectives: version:1.0.0, lifestyle:stable
use config CommonServiceConfig as svcCfg
impl api TimeDateApi as api on svcCfg.apiConfig.port
use expression GetTimeString as time
use expression GetDateString as date
connect api.getTime -> time
connect api.getDate -> date
# @ocean-meta-start
# tags:
# - service
# - broker-responder
# perspective:
# feature: grpc-gateway-answer-hub
# boundedContext: answer-hub
# service: zone-time-service
# @ocean-meta-end
@service
ZoneTimeService
@perspectives: version:1.0.0, lifestyle:stable
use config CommonServiceConfig as svcCfg
use broker AnswerHubBroker as broker
use expression GetAmsterdamTime as zoneTime
connect broker.answers.zoneTime -> zoneTime
# @ocean-meta-start
# tags:
# - dashboard
# - gateway
# perspective:
# feature: grpc-gateway-answer-hub
# boundedContext: answer-hub
# service: answer-hub-ui
# @ocean-meta-end
@dashboard
AnswerHubDashboard
title: gRPC Gateway Answer Hub
layout: SingleColumnLayout
Widget GatewayGuide of type Text
title = One public gRPC API, three provider services
subtitle = gRPC forwarding for time, date, day, and day tip; broker request-response for zone time
content = <p>Each action calls a gRPC method of <b>AnswerGatewayApi</b>. The gateway automatically forwards matching methods to the provider gRPC APIs and explicitly delegates zone time to a broker responder.</p>
Widget TimeButton of type Button
label = Current time
Widget DateButton of type Button
label = Current date
Widget DayButton of type Button
label = Current day
Widget DayTipButton of type Button
label = Day tip
Widget ZoneTimeButton of type Button
label = Amsterdam zone time
AboutDashboard
title: About this example
layout: SingleColumnLayout
Widget AboutText of type Text
title = gRPC gateway routing patterns
subtitle = Automatic forwarding and explicit routing in one service
content = <p>This example demonstrates an Ocean service that implements a gRPC gateway API. Matching methods are forwarded to included gRPC APIs automatically. A distinct method is explicitly connected to a broker request-response topic.</p>
# @ocean-meta-start
# tags:
# - ui
# - gateway
# perspective:
# feature: grpc-gateway-answer-hub
# boundedContext: answer-hub
# service: answer-hub-ui
# @ocean-meta-end
@ui
AnswerHubUi
@perspectives: version:1.0.0, lifestyle:stable
framework: htmx
styling: bootstrap
template: go-html-template
backend: go-gin
static: static
configType: UiConfig
nav Answers dashboard = AnswerHubDashboard
nav About dashboard = AboutDashboard
header title = gRPC Gateway Answer Hub
header subtitle = One public gRPC API for independent answer providers
header align = center
footer title = Built with Ocean-lab
footer subtitle = gRPC gateway feature example
footer align = center
use dashboard AnswerHubDashboard as answers
use dashboard AboutDashboard as about
use api AnswerGatewayApi as gateway
connect answers.TimeButton.click -> gateway.getTime
connect answers.DateButton.click -> gateway.getDate
connect answers.DayButton.click -> gateway.getDay
connect answers.DayTipButton.click -> gateway.getDayTip
connect answers.ZoneTimeButton.click -> gateway.getZoneTime
# @ocean-meta-start
# tags:
# - deployment
# - gateway
# perspective:
# feature: grpc-gateway-answer-hub
# boundedContext: answer-hub
# service: shared
# @ocean-meta-end
@deploy
Name: GrpcGatewayAnswerHubDeploy
@import service P.broker.nats.docker@1.0.0 as NATS
TimeDateDeploy
service TimeDateService
replica 1
export 9101:TimeDateService.api
CalendarDeploy
service CalendarService
replica 1
export 9102:CalendarService.api
ZoneTimeDeploy
service ZoneTimeService
replica 1
dependsOn Broker
GatewayDeploy
service AnswerGatewayService
replica 1
export 9100:AnswerGatewayService.gw
dependsOn TimeDateDeploy, CalendarDeploy, ZoneTimeDeploy, Broker
Broker
service NATS
AnswerHubUiDeploy
service AnswerHubUi
replica 1
export 8085:AnswerHubUi.config
dependsOn GatewayDeploy
flowchart LR
UI[AnswerHubUi] -->|gRPC: AnswerGatewayApi| GW[AnswerGatewayService]
GW -->|gRPC forward: getTime, getDate| TD[TimeDateService]
GW -->|gRPC forward: getDay, getDayTip| CAL[CalendarService]
GW -->|request: answers.zoneTime| BROKER[(AnswerHubBroker)]
BROKER -->|reply| ZONE[ZoneTimeService]
sequenceDiagram
participant UI as AnswerHubUi
participant GW as AnswerGatewayService
participant B as AnswerHubBroker
participant Z as ZoneTimeService
UI->>GW: gRPC AnswerGatewayApi/GetZoneTime
GW->>B: request answers.zoneTime
B->>Z: deliver request
Z-->>B: timezone-aware time
B-->>GW: reply
GW-->>UI: GetZoneTimeResponse: zone time