Gateway Answer Hub
Expose one public API over independent answer providers, forwarding matching operations automatically and routing one explicitly through broker request-response.
Examplegatewayapi-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 Answer API for the current time, date, day, day tip, and Amsterdam zone time. A gateway service owns that boundary: it forwards most operations to the provider APIs that already offer them, and routes the one operation without a provider API through a broker request-response topic.
Architecture
The UI calls only the gateway. Four operations are forwarded automatically to included APIs; zone time is routed explicitly to a broker responder.
What It Demonstrates
@api—includecomposes two provider APIs into one public gateway API that adds its own operation.@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 operation 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. Gateway
policies such as authentication, CORS, and rate limiting are out of
scope; they are a planned extension of the gateway model.
2. Prerequisites
- Ocean UI with the gateway capability of
@serviceand API composition in@api. - A container runtime for the generated deployment.
- Recommended:
answer-syncandanswer-async, which use the same answer providers with aggregation.
3. Example Structure
0015-gateway-answer-hub/
├── 00-gah-info.ocn
├── 10-gah-api.ocn
├── 20-gah-broker.ocn
├── 30-gah-config.ocn
├── 40-gah-expression.ocn
├── 50-gah-service-calendar.ocn
├── 50-gah-service-gateway.ocn
├── 50-gah-service-time-date.ocn
├── 50-gah-service-zone-time.ocn
├── 55-gah-dashboard.ocn
├── 56-gah-ui.ocn
├── 60-gah-deploy.ocn
├── architecture.mmd
├── broker-communication.mmd
└── example-info/example-info.html
4. Compose the Public API
10-gah-api.ocn defines the two provider APIs and the public
AnswerGatewayApi. include reuses the provider
operations without repeating them; the gateway API owns the transport
settings, so every operation is exposed under /answers.
AnswerGatewayApi style:rest
engine = gin
configType = ApiConfig
version = 1.0.0
basePath = /answers
generateSwagger = true
include TimeDateApi, CalendarApi
get /zone-time getZoneTime() : String
The effective public API is GET /answers/time,
/answers/date, /answers/day,
/answers/day-tip, and /answers/zone-time.
5. Implement the Gateway Service
50-gah-service-gateway.ocn implements the public 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. 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 api on gwCfg.apiConfig.port
including timeDate, calendar
connect api.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 APIs with ordinary connect statements to
expressions. ZoneTimeService implements no API: it answers
the request-response topic declared in 20-gah-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 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 gin REST engine, the broker uses
nats, and the UI is generated with HTMX, Bootstrap, and Go
templates. 60-gah-deploy.ocn imports a NATS container and
starts the gateway after its providers and the broker.
GatewayDeploy
service AnswerGatewayService
replica 1
export 9100:AnswerGatewayService.api
dependsOn TimeDateDeploy, CalendarDeploy, ZoneTimeDeploy, Broker
AnswerHubUiDeploy
service AnswerHubUi
replica 1
export 8085:AnswerHubUi.config
dependsOn GatewayDeploy
The provider APIs are also exported on ports 9101 and
9102 for direct inspection. The UI does not use them.
9. Validate, Generate, and Run
- Open the
Gateway Answer Hubexample in Ocean UI. - Validate and generate the complete model.
- 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. - Call
GET localhost:9100/answers/timedirectly and compare it withGET localhost:9101/time-date/time. - Stop
ZoneTimeServiceand call/answers/zone-time; the request fails after its two-second timeout while the forwarded operations still answer.
11. Experiments
- Add
connect api.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.
Executable model
<\> Implementation
Explore the runnable model by responsibility, then select a file to inspect its complete source.
# @ocean-meta-start
# tags:
# - gateway
# - api-facade
# - broker-request-response
# perspective:
# feature: gateway-answer-hub
# boundedContext: answer-hub
# @ocean-meta-end
@info
name: Gateway Answer Hub
version: 1.0.0
title: Gateway Answer Hub
subtitle: One public API for independent answer providers
shortDescription: A focused API gateway example with automatic API forwarding and an explicit broker-backed operation.
description:
Overview
Gateway Answer Hub exposes one public API for time, date, day, day tip, and zone time. It demonstrates the Ocean gateway feature without hiding how each operation is routed.
Automatic forwarding
The gateway includes the TimeDateApi and CalendarApi. 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 endpoint, so users see the gateway as the sole public boundary.
# @ocean-meta-start
# tags:
# - rest-api
# - gateway-contract
# perspective:
# feature: gateway-answer-hub
# boundedContext: answer-hub
# @ocean-meta-end
@api
TimeDateApi style:rest
engine = gin
configType = ApiConfig
version = 1.0.0
basePath = /time-date
generateSwagger = true
get /time getTime() : String
get /date getDate() : String
CalendarApi style:rest
engine = gin
configType = ApiConfig
version = 1.0.0
basePath = /calendar
generateSwagger = true
get /day getDay() : String
get /day-tip getDayTip() : String
AnswerGatewayApi style:rest
engine = gin
configType = ApiConfig
version = 1.0.0
basePath = /answers
generateSwagger = true
include TimeDateApi, CalendarApi
get /zone-time getZoneTime() : String
# @ocean-meta-start
# tags:
# - broker
# - nats
# - request-response
# perspective:
# feature: 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: 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: 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: 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
# perspective:
# feature: 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.
# Zone time is intentionally broker-backed and therefore routed explicitly.
connect gw.getZoneTime -> broker.answers.zoneTime
# @ocean-meta-start
# tags:
# - service
# - provider
# perspective:
# feature: 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: 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: gateway-answer-hub
# boundedContext: answer-hub
# service: answer-hub-ui
# @ocean-meta-end
@dashboard
AnswerHubDashboard
title: Gateway Answer Hub
layout: SingleColumnLayout
Widget GatewayGuide of type Text
title = One public API, three provider services
subtitle = API forwarding for time, date, day, and day tip; broker request-response for zone time
content = <p>Each action calls <b>AnswerGatewayApi</b>. The gateway automatically forwards matching API operations 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 = Gateway routing patterns
subtitle = Automatic forwarding and explicit routing in one service
content = <p>This example demonstrates an Ocean service that implements a gateway API. Matching endpoints are forwarded to included APIs automatically. A distinct endpoint is explicitly connected to a broker request-response topic.</p>
# @ocean-meta-start
# tags:
# - ui
# - gateway
# perspective:
# feature: 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 = Gateway Answer Hub
header subtitle = One public API for independent answer providers
header align = center
footer title = Built with Ocean-lab
footer subtitle = 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: gateway-answer-hub
# boundedContext: answer-hub
# service: shared
# @ocean-meta-end
@deploy
Name: GatewayAnswerHubDeploy
@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] -->|AnswerGatewayApi| GW[AnswerGatewayService]
GW -->|automatic: getTime, getDate| TD[TimeDateService]
GW -->|automatic: 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: GET /answers/zone-time
GW->>B: request answers.zoneTime
B->>Z: deliver request
Z-->>B: timezone-aware time
B-->>GW: reply
GW-->>UI: zone time