Answer Aggregation (async)
Learn broker request-response, events, timeouts, context, and multi-service aggregation through one simple answer application.
ExampleGobrokerrequest-responseeventscontextaggregationexternal-expressionsgomicroservicesasyncuiapirest
🌅 Horizon
Asynchronous Answers at a Glance
Learning Scenario
This example starts with a deliberately simple question: “What is the current time?” The answer is made richer by combining the current day, date, time, daily tip, season, and time zone.
The business problem is intentionally small. The real purpose is to demonstrate how Ocean models asynchronous collaboration: one service asks questions through a broker, independent services answer them, an event updates context, and an expression gathers everything into one response.
Architecture
Day and time/date use request-response channels. Day tips arrive as events and are kept in context. Season and time zone come from external functions before the answer is returned.
Core Concepts
- Request-response: a service sends a request through a broker channel and waits for a typed response.
- Event: a producer publishes information without waiting for a response.
- Context: service-owned state keeps the latest tip available for future API calls.
- Aggregation: one expression combines values from broker calls, context, and external functions.
- External expression sources: typed Ocean expressions can delegate selected logic to functions implemented in an external source file.
Expected Result
One TimeAnswer containing day, date, time, tip, season, and
time zone, even though those values come from different parts of the
system.
🧭 Voyage
1. Follow the Message Flow
You will build the example from the messages outward. First you will define what travels through the broker, then create the APIs and broker channels, add context and expressions, and finally connect four services into one asynchronous flow.
request-response] B_date[date
request-response] B_time[time
request-response] B_tip[tip
event] end subgraph Services DS[Day Service] TDS[Time/Date Service] TipS[Day Tip Service] end U -->|API request| AS AS -->|requests| B_day DS -->|responds| B_day AS -->|requests| B_date TDS -->|responds| B_date AS -->|requests| B_time TDS -->|responds| B_time TipS -->|publishes| B_tip AS -->|subscribes| B_tip
This flowchart shows the topology: who requests, responds, publishes, and subscribes.
Runtime Sequence
The same design can be viewed over time. A tip event may update context before a user request arrives. The answer request then gathers day, date, and time before reading the stored tip and returning the result.
“Asynchronous” refers to broker-mediated collaboration. Inside the aggregation expression, day, date, and time are requested in sequence; each call waits for its response or timeout.
2. Model Broker Messages and the Final Answer
Choose between date and time
One broker channel will handle both date and time requests. You will use an enum so the request can state which value it needs.
@datatype
enum RequestType
date
time
Correlate requests and responses
The request carries a unique ID and the requested type. The response echoes the type and supplies its text result. Together they form the typed contract of the time/date broker channel.
TimeDateRequest
reqId: String
reqType: RequestType
TimeDateResponse
resType: RequestType
resContent: String
Define the aggregated answer
The public API returns more than any single service produces, so you will define a datatype that collects all contributions.
TimeAnswer
day: String
date: String
time: String
tip: String
zone: String
season: String
Afterwards, both broker messages and the final API result will have clear typed contracts.
3. Give Each Service an API
Expose the combined answer
AnswerApi is the entry point used by the UI and external
callers. Its single method returns the aggregated
TimeAnswer.
@api
AnswerApi style:rest
engine = gin
configType = ApiConfig
basePath = /answer
generateSwagger = true
get /time getTimeAnswer() : TimeAnswer
Expose the contributing capabilities
The other APIs let each domain service remain independently usable and testable. The broker connections you add later will reuse the same expressions behind these endpoints.
DayApi style:rest
basePath = /day
get / getDay() : String
TimeDateApi style:rest
basePath = /
get /time getTime() : String
get /date getDate() : String
DayTipApi style:rest
basePath = /day-tip
get / getDayTip() : String
4. Define the Broker Contract
Select the broker realization
Voyage is where technology choices appear. This example realizes
AnswerBroker with NATS and links it to a broker
configuration type.
@broker
AnswerBroker
engine = nats
configType = AnswerBrokerConfig
tags = primary, shared
Publish tips as events
A tip does not need a reply. The producer can publish a
String event, and interested consumers can react whenever a
new value arrives.
dayTip : String as event
Request the current day
Request-response channels declare input and output as
Request-Response. An underscore means the day request has no
payload; the response is a string. The channel also declares a timeout.
question.day : _-String as request-response with timeout:1s
Request date or time with typed messages
The second channel uses the datatypes defined earlier. The same
responder can inspect reqType and return either value.
question.timedate : TimeDateRequest-TimeDateResponse as request-response with timeout:1s
The broker contract now demonstrates both one-way events and two-way request-response messaging in one small example.
5. Keep the Latest Event in Context
Define service-owned state
Broker events arrive independently of API requests. To make the latest tip available later, you will define a context with a useful default.
@context
AnswerContext
tip: String (default=enjoy your day!)
Update context when an event arrives
A pointer input (*AnswerContext) allows the expression to
change the context. Every new dayTip event replaces the
stored value.
HandleDayTip
input: ctx:*AnswerContext & dayTip:String
output: _
logic
ctx.tip = dayTip
This is the key context lesson: an event can update state now, and a separate request can read that state later.
6. Build the Answer Logic
Handle a typed time/date request
The responder inspects reqType, calls the matching helper,
and wraps the result in TimeDateResponse.
HandleDateTimeRequest
input: req:TimeDateRequest
output: result:TimeDateResponse
logic
res.resType = req.reqType
if req.reqType == RequestType.time then
res.resContent = GetTimeString()
end
if req.reqType == RequestType.date then
res.resContent = GetDateString()
end
result = res
Use functions as expression inputs
HandleTimeQuestion does not know how day, date, and time are
transported. It receives callable functions, which the service will
later connect to broker channels.
HandleTimeQuestion
input: ctx:*AnswerContext
& getDay:Supplier<String>
& getTimeDate:Func<TimeDateRequest,TimeDateResponse>
output: result:TimeAnswer
Request and gather the contributions
You will create separate correlated requests for date and time, call the day supplier, read the current tip, and assemble one result.
req.reqId = getUUID()
req.reqType = RequestType.date
dateRes = getTimeDate(req)
req.reqId = getUUID()
req.reqType = RequestType.time
timeRes = getTimeDate(req)
temp.tip = ctx.tip
temp.day = getDay()
temp.date = dateRes.resContent
temp.time = timeRes.resContent
7. Extend Expressions with External Functions
Why use an external source?
Most logic in this example is expressed directly in Ocean. Season and time-zone calculation, however, use date, location, and formatting libraries from Go. External expressions let you keep the Ocean contract while implementing specialized logic in a source file.
Declare a typed Ocean boundary
An external expression still has ordinary typed inputs and outputs.
Instead of a logic block, its external block
identifies the source file and method that implement it.
GetSeason
input: _
output: season:String
external
source: my-funcs.go
method: GetCurrentSeason
GetTimezone
input: _
output: season:String
external
source: my-funcs.go
method: GetCurrentTimezone
Implement the matching Go functions
The methods live in expression/my-funcs.go. Their return
signature includes the declared string value and an error, allowing the
generated integration to propagate failures.
func GetCurrentTimezone() (string, error) {
_, offset := time.Now().Zone()
hours := offset / 3600
minutes := (offset % 3600) / 60
sign := "+"
if hours < 0 || minutes < 0 {
sign = "-"
hours = -hours
minutes = -minutes
}
return time.Now().Location().String() +
fmt.Sprintf(" (UTC%s%02d:%02d)", sign, hours, minutes), nil
}
This is the actual time-zone implementation. The same file implements
GetCurrentSeason by mapping the current month to a season.
Call external expressions like normal expressions
The caller does not need to know that the implementation is external.
HandleTimeQuestion invokes both functions exactly like
Ocean-native expressions and assigns their typed results.
temp.season = GetSeason()
temp.zone = GetTimezone()
Keep the boundary intentional
Use Ocean logic for portable domain behavior and external sources when you genuinely need a host-language library or existing implementation. Keep the external function small, deterministic where possible, and fully described by its Ocean input/output contract.
8. Connect Four Services Through the Broker
Prepare the answer service
The coordinating service uses the API, broker, context, expressions, and two function aliases. Those aliases will become broker clients.
AnswerService
use broker AnswerBroker as svcBroker
use context AnswerContext as svcCtx
use expression HandleTimeQuestion
use expression HandleDayTip
use function Supplier<String> as GetDay
use function Func<TimeDateRequest,TimeDateResponse> as GetTimeDate
Connect broker clients and the API
Function calls become requests on broker channels. The time/date call overrides the channel timeout with three seconds. The API method then receives those functions and the context.
connect GetDay -> svcBroker.question.day
connect GetTimeDate -> svcBroker.question.timedate & timeout(3:s)
connect api.getTimeAnswer -> HandleTimeQuestion(
ctx:svcCtx,
getday:GetDay,
getTimeDate:GetTimeDate
)
Consume events into context
The answer service subscribes to dayTip and passes each
event to the context-updating expression.
connect svcBroker.dayTip -> HandleDayTip(ctx:svcCtx)
Provide the request-response handlers
The day and time/date services listen on the matching broker channels. They can also expose their calculations through their own APIs.
connect svcBroker.question.day -> GetDayString
connect svcBroker.question.timedate -> HandleDateTimeRequest
Publish a recurring event
each(10:s) turns GetDayTip into a recurring
producer. Every ten seconds its output is published as a new event.
connect GetDayTip -> svcBroker.dayTip & each(10:s)
9. Configure the Runtime Components
Services reuse common API, logging, and NATS configuration. The broker and UI receive their own grouped contracts.
@config
@import config O.log.config@1.0.0 as LogConfig
@import config O.broker.nats.config@1.0.0 as BrokerConfig
CommonServiceConfig
apiConfig: ApiConfig
logConfig: LogConfig
brokerConfig: BrokerConfig
AnswerBrokerConfig
brokerConfig: BrokerConfig
logConfig: LogConfig
Afterwards, runtime settings can vary without changing broker contracts or aggregation logic.
10. Add a Small User Interface
The UI intentionally stays simple: one dashboard button calls the aggregate API method, making it easy to focus on the broker flow behind the response.
@dashboard
AnswerDashboard
title: Ask your question!
Widget TimeButton of type Button
label = Ask 'Time' Question
connect answer.TimeButton.click -> AnswerApi.getTimeAnswer
The concrete UI uses HTMX, Bootstrap, Go HTML templates, and Gin, with an additional About dashboard explaining the pattern.
11. Deploy Services and Broker
Provide the broker runtime
Deployment realizes the logical broker with Ocean's packaged NATS service.
@deploy
@import service P.broker.nats.docker@1.0.0 as NATS
Broker
service NATS
Deploy the collaborating services
Each service is deployed independently and depends on the broker. The answer API is exposed on 9093; the contributing APIs use 9094–9096.
AnswerDeploy
service AnswerService
export 9093:AnswerService.api
dependsOn Broker
DayTipDeploy
service DayTipService
export 9094:DayTipService.api
dependsOn Broker
TimeDateDeploy
service TimeDateService
export 9095:TimeDateService.api
dependsOn Broker
DayDeploy
service DayService
export 9096:DayService.api
dependsOn Broker
Deploy the UI
The UI is exposed on port 8083 and starts after the answer service.
AnswerUiDeploy
service AnswerUi
export 8083:AnswerUi.config
dependsOn AnswerDeploy
12. Validate and Observe the Flow
- Open the
Asynchronous Answer Aggregationexample. - Validate and generate the complete model.
- Follow the generated
Readme.mdto start the system. - Wait at least ten seconds for a day-tip event to update context.
- Open the UI at
localhost:8083and ask the time question. - Inspect the answer API at
localhost:9093/swagger/index.html. - Observe broker requests, responses, the event, and timeout behavior in the logs.
13. Conclusion and Experiments
By the end of this example, you will have used both broker interaction styles and combined them with context. Afterwards, you should understand how to:
- design typed request-response channels;
- publish and consume one-way events;
- turn broker calls into function inputs for expressions;
- use timeouts at channel and connection level;
- update context from an event and read it during a later request;
- aggregate broker, context, and external-function results.
Try adding another contributing service, reducing a timeout to observe failure behavior, or adding a correlation map to context for several concurrent questions. Those experiments make the advanced broker concepts more visible without changing the simple learning scenario.
Executable model
<\> Implementation
Explore the runnable model by responsibility, then select a file to inspect its complete source.
# @ocean-meta-start
# tags:
# - datatype
# perspective:
# feature: answer-async
# boundedContext: answer-aggregation
# @ocean-meta-end
@datatype
@include O.datatype.error.standard@1.0.0
TimeDateRequest
reqId : String
reqType : RequestType
TimeDateResponse
resType : RequestType
resContent : String
enum RequestType
date
time
TimeAnswer
day : String
date : String
time : String
tip : String
zone : String
season : String
# @ocean-meta-start
# tags:
# - documentation
# perspective:
# feature: answer-async
# boundedContext: answer-aggregation
# @ocean-meta-end
@info
name: Answering App
version: 1.0.0
title: AnsweringApp
subtitle: To answer your questions
shortDescription: Answers all your questions
description:
Overview
The Answer Service implements an asynchronous fan-out/gather pattern that distributes a user request to multiple domain services via a broker and aggregates their responses into one unified answer.
Request Flow
The user calls the API, which forwards the request to AnswerService, which publishes a distributed query to the broker for all subscribed services to process in parallel.
Parallel Processing
DayService, DateService, TimeService, and TipService each compute their part independently, optionally using shared expressions, and publish their results back to the broker.
Aggregation
AnswerService listens for all expected responses, aggregates them into a single result, and optionally uses shared context for correlation or caching.
Response
After collecting all contributions (or timing out), AnswerService returns a unified, coherent response to the user via the API.
Key Benefits
Asynchronous execution reduces latency, domain services remain decoupled, the system becomes easily extensible, and aggregation enables rich composite answers without cross-service dependencies.
Summary
One request is fanned out to multiple domain services via a broker, each returns its part asynchronously, and AnswerService gathers everything into a single answer for the user.
# @ocean-meta-start
# tags:
# - answer-aggregation
# - context
# perspective:
# feature: answer-async
# boundedContext: answer-aggregation
# service: answer-service
# @ocean-meta-end
@context
AnswerContext
tip : String (default=enjoy your day!)
# @ocean-meta-start
# tags:
# - answer-api
# - rest-api
# perspective:
# feature: answer-async
# boundedContext: answer-aggregation
# service: answer-service
# @ocean-meta-end
@api
AnswerApi style:rest
engine = gin
configType = ApiConfig
version = 1.0.0
basePath = /answer
generateSwagger = true
get /time getTimeAnswer() : TimeAnswer
# @ocean-meta-start
# tags:
# - day-tip-api
# - rest-api
# perspective:
# feature: answer-async
# boundedContext: answer-aggregation
# service: day-tip-service
# @ocean-meta-end
@api
DayTipApi style:rest
engine = gin
configType = ApiConfig
version = 1.0.0
basePath = /day-tip
generateSwagger = true
get / getDayTip() : String
# @ocean-meta-start
# tags:
# - day-api
# - rest-api
# perspective:
# feature: answer-async
# boundedContext: answer-aggregation
# service: day-service
# @ocean-meta-end
@api
DayApi style:rest
engine = gin
configType = ApiConfig
version = 1.0.0
basePath = /day
generateSwagger = true
get / getDay() : String
# @ocean-meta-start
# tags:
# - time-date-api
# - rest-api
# perspective:
# feature: answer-async
# boundedContext: answer-aggregation
# service: time-date-service
# @ocean-meta-end
@api
TimeDateApi style:rest
engine = gin
configType = ApiConfig
version = 1.0.0
basePath = /
generateSwagger = true
get /time getTime() : String
get /date getDate() : String
# @ocean-meta-start
# tags:
# - answer-aggregation
# - async-request-response
# - broker
# - nats
# perspective:
# feature: answer-async
# boundedContext: answer-aggregation
# @ocean-meta-end
@broker
AnswerBroker
@perspectives: version:0.1.0, lifestyle:stable
engine = nats
configType = AnswerBrokerConfig
tags = primary, shared
dayTip : String as event
question.day : _-String as request-response with timeout:1s
question.timedate : TimeDateRequest-TimeDateResponse as request-response with timeout:1s
# @ocean-meta-start
# tags:
# - configuration
# perspective:
# feature: answer-async
# boundedContext: answer-aggregation
# service: all
# @ocean-meta-end
@config
@import config O.log.config@1.0.0 as LogConfig
@import config O.broker.nats.config@1.0.0 as BrokerConfig
CommonServiceConfig
apiConfig : ApiConfig
logConfig : LogConfig
brokerConfig : BrokerConfig
ApiConfig
port : Int (default=8080)
AnswerBrokerConfig
brokerConfig : BrokerConfig
logConfig : LogConfig
UiConfig
port: Int (default=8080)
logConfig : LogConfig
# @ocean-meta-start
# tags:
# - answer-aggregation
# - expression
# perspective:
# feature: answer-async
# boundedContext: answer-aggregation
# service: backend
# @ocean-meta-end
@expression
GetSeason
input: _
output: season:String
external
source: my-funcs.go
method: GetCurrentSeason
GetTimezone
input: _
output: season:String
external
source: my-funcs.go
method: GetCurrentTimezone
HandleDayTip
input : ctx:*AnswerContext & dayTip:String
output : _
logic
ctx.tip = dayTip
HandleTimeQuestion
input : ctx:*AnswerContext & getDay:Supplier<String> & getTimeDate:Func<TimeDateRequest,TimeDateResponse>
output : result:TimeAnswer
logic
var req TimeDateRequest
# get date
var id String
id = getUUID()
req.reqId = id
req.reqType = RequestType.date
dateRes = getTimeDate(req)
# get time
req.reqId = getUUID()
req.reqType = RequestType.time
timeRes = getTimeDate(req)
# generate full response
var temp TimeAnswer
temp.tip = ctx.tip
temp.day = getDay()
temp.date = dateRes.resContent
temp.time = timeRes.resContent
temp.season = GetSeason()
temp.zone = GetTimezone()
result = temp
HandleDateTimeRequest
input: req:TimeDateRequest
output: result:TimeDateResponse
logic
var res TimeDateResponse
res.resType = req.reqType
if req.reqType == RequestType.time then
res.resContent = GetTimeString()
end
if req.reqType == RequestType.date then
res.resContent = GetDateString()
end
result = res
GetTimeString
input: _
output: result:String
logic
nowTime = nowTimeString()
result = nowTime
GetDateString
input: _
output: result:String
logic
nowDate = nowDateString()
result = nowDate
GetDayString
input: _
output: result:String
logic
today = today()
result = today
GetDayTip
input: _
output: result:String
logic
dayTip = tipOfTheDay()
result = dayTip
# @ocean-meta-start
# tags:
# - coordinator
# - aggregator
# - gateway
# perspective:
# feature: answer-async
# service: answer-service
# broker: subscriber-requester
# @ocean-meta-end
@service
AnswerService
@perspectives: version:1.0.0, lifestyle:dev
use config CommonServiceConfig as svcCfg
impl api AnswerApi as api on svcCfg.apiConfig.port
use broker AnswerBroker as svcBroker
use context AnswerContext as svcCtx
use expression HandleTimeQuestion
use expression HandleDayTip
use function Supplier<String> as GetDay
use function Func<TimeDateRequest,TimeDateResponse> as GetTimeDate
connect svcBroker.dayTip -> HandleDayTip(ctx:svcCtx)
connect GetDay -> svcBroker.question.day
connect GetTimeDate -> svcBroker.question.timedate & timeout(3:s)
connect api.getTimeAnswer -> HandleTimeQuestion(ctx:svcCtx, getday:GetDay, getTimeDate:GetTimeDate)
# @ocean-meta-start
# tags:
# - provider
# - day-data
# perspective:
# feature: answer-async
# service: day-service
# broker: responder
# @ocean-meta-end
@service
DayService
@perspectives: version:1.0.0, lifestyle:dev
use config CommonServiceConfig as svcCfg
impl api DayApi as api on svcCfg.apiConfig.port
use broker AnswerBroker as svcBroker
use expression GetDayString
connect api.getDay -> GetDayString
connect svcBroker.question.day -> GetDayString
# @ocean-meta-start
# tags:
# - provider
# - day-tip-data
# perspective:
# feature: answer-async
# service: day-tip-service
# broker: publisher
# @ocean-meta-end
@service
DayTipService
@perspectives: version:1.0.0, lifestyle:dev
use config CommonServiceConfig as svcCfg
impl api DayTipApi as api on svcCfg.apiConfig.port
use broker AnswerBroker as svcBroker
use expression GetDayTip
connect api.getDayTip -> GetDayTip
connect GetDayTip -> svcBroker.dayTip & each(10:s)
# @ocean-meta-start
# tags:
# - provider
# - time-date-data
# perspective:
# feature: answer-async
# service: time-date-service
# broker: responder
# @ocean-meta-end
@service
TimeDateService
@perspectives: version:1.0.0, lifestyle:dev
use config CommonServiceConfig as svcCfg
impl api TimeDateApi as api on svcCfg.apiConfig.port
use broker AnswerBroker as svcBroker
use expression GetTimeString
use expression GetDateString
use expression HandleDateTimeRequest
connect api.getTime -> GetTimeString
connect api.getDate -> GetDateString
connect svcBroker.question.timedate -> HandleDateTimeRequest
# @ocean-meta-start
# tags:
# - ui
# - dashboard
# perspective:
# feature: answer-async
# boundedContext: answer-aggregation
# service: ui-service
# @ocean-meta-end
@dashboard
AnswerDashboard
title: Ask your question!
layout: SingleColumnLayout
Widget TimeButton of type Button
label = Ask 'Time' Question
AboutDashboard
title: About
layout: SingleColumnLayout
Widget AboutText of type Text
title = About Answer! App
subtitle = To answer your questions! 💬
content = <p>This is a simple yet comprehensive example for <i>Aggregation of Microservices via Broker</i></p><br><br><b>Overview</b><br><p>The Answer Service implements an asynchronous fan-out/gather pattern that distributes a user request to multiple domain services via a broker and aggregates their responses into one unified answer.</p><br><b>Request Flow</b><br><p>The user calls the API, which forwards the request to AnswerService, which publishes a distributed query to the broker for all subscribed services to process in parallel.</p><br><b>Parallel Processing</b><br><p>DayService, DateService, TimeService, and TipService each compute their part independently, optionally using shared expressions, and publish their results back to the broker.</p><br><b>Aggregation</b><br>AnswerService listens for all expected responses, aggregates them into a single result, and optionally uses shared context for correlation or caching.</p><br><b>Response</b><br><p>After collecting all contributions (or timing out), AnswerService returns a unified, coherent response to the user via the API.</p><br><b>Key Benefits</b><br><p>Asynchronous execution reduces latency, domain services remain decoupled, the system becomes easily extensible, and aggregation enables rich composite answers without cross-service dependencies.</p><br><b>Summary</b><br><p>One request is fanned out to multiple domain services via a broker, each returns its part asynchronously, and AnswerService gathers everything into a single answer for the user.</p>
# @ocean-meta-start
# tags:
# - ui
# - dashboard
# perspective:
# feature: answer-async
# boundedContext: answer-aggregation
# service: ui-service
# @ocean-meta-end
@ui
AnswerUi
@perspectives: version:0.1.0, lifestyle:stable
framework: htmx
styling: bootstrap
template: go-html-template
backend: go-gin
static: static
configType: UiConfig
nav Ask me! 🙋 dashboard = AnswerDashboard
nav About dashboard = AboutDashboard
header title = 💬 Answer! App
header subtitle = To answer all your questions!
header align = center
footer title = ⚡ Built with Ocean-lab
footer subtitle = Version 1.0.0
footer align = center
use dashboard AnswerDashboard as answer
use dashboard AboutDashboard as about
use api AnswerApi
connect answer.TimeButton.click -> AnswerApi.getTimeAnswer
# @ocean-meta-start
# tags:
# - deployment
# perspective:
# feature: answer-async
# boundedContext: answer-aggregation
# service: all
# @ocean-meta-end
@deploy
Name: AnswerService-PRD-deploy
@import service P.broker.nats.docker@1.0.0 as NATS
AnswerDeploy
service AnswerService
replica 1
export 9093:AnswerService.api
dependsOn Broker
DayTipDeploy
service DayTipService
replica 1
export 9094:DayTipService.api
dependsOn Broker
TimeDateDeploy
service TimeDateService
replica 1
export 9095:TimeDateService.api
dependsOn Broker
DayDeploy
service DayService
replica 1
export 9096:DayService.api
dependsOn Broker
AnswerBrokerDeploy
service AnswerBroker
replica 1
dependsOn Broker
Broker
service NATS
AnswerUiDeploy
service AnswerUi
replica 1
export 8083:AnswerUi.config
dependsOn AnswerDeploy
flowchart
u[User]
subgraph s[System]
a[API]
b([Broker])
e[Expressions]
as[[AnswerService]]
ds[[DayService]]
das[[DateService]]
ts[[TimeService]]
tis[[TipService]]
ctx([Context])
end
u -.asks TIME.-> a
a <--request-response--> as
as <--distribute-aggregate--> b
b <--> ds
b <--> das
b <--> ts
b <--> tis
ds -..-> e
das -..-> e
ts -..-> e
tis -..-> e
ctx -.read.-> as
b -.write.-> ctx
flowchart LR
subgraph Client
U[User]
end
subgraph REST
AS[AnsweringService]
end
subgraph Broker
B_day[day<br/>request-response]
B_date[date<br/>request-response]
B_time[time<br/>request-response]
B_tip[tip<br/>event]
end
subgraph Services
DS[DayService]
DaS[DateService]
TS[TimeService]
TipS[TipService]
end
U --> |HTTP request| AS
AS --requests--> B_day
DS --responses--> B_day
AS --requests--> B_date
DaS --responses-->B_date
AS --requests--> B_time
TS --responses--> B_time
TipS -->|publishes| B_tip
AS -->|subscribes| B_tippackage main
import (
"fmt"
"time"
)
func GetCurrentSeason() (string, error) {
now := time.Now()
month := now.Month()
day := now.Day()
switch month {
case time.March:
if day >= 1 {
return "Spring", nil
}
case time.April, time.May:
return "Spring", nil
case time.June:
if day >= 1 {
return "Summer", nil
}
case time.July, time.August:
return "Summer", nil
case time.September:
if day >= 1 {
return "Autumn", nil
}
case time.October, time.November:
return "Autumn", nil
case time.December:
if day >= 1 {
return "Winter", nil
}
case time.January, time.February:
return "Winter", nil
}
return "Unknown", nil
}
func GetCurrentTimezone() (string, error) {
_, offset := time.Now().Zone()
// Convert offset in seconds to hours and minutes
hours := offset / 3600
minutes := (offset % 3600) / 60
sign := "+"
if hours < 0 || minutes < 0 {
sign = "-"
hours = -hours
minutes = -minutes
}
return time.Now().Location().String() +
fmt.Sprintf(" (UTC%s%02d:%02d)", sign, hours, minutes), nil
}@info
## Metadata
- **Name:** Answering App
- **Version:** 1.0.0
- **Title:** AnsweringApp
- **Subtitle:** To answer your questions
- **Short Description:** Answers all your questions
---
## Description
### Overview
The Answer Service implements an asynchronous **fan-out / gather** pattern that distributes a user request to multiple domain services via a broker and aggregates their responses into one unified answer.
### Request Flow
The user calls the API, which forwards the request to **AnswerService**, which publishes a distributed query to the broker for all subscribed services to process in parallel.
### Parallel Processing
**DayService**, **DateService**, **TimeService**, and **TipService** each compute their part independently, optionally using shared expressions, and publish their results back to the broker.
### Aggregation
**AnswerService** listens for all expected responses, aggregates them into a single result, and optionally uses shared context for correlation or caching.
### Response
After collecting all contributions (or timing out), **AnswerService** returns a unified, coherent response to the user via the API.
---
## Key Benefits
- Asynchronous execution reduces latency
- Domain services remain decoupled
- The system is easily extensible
- Aggregation enables rich composite answers without cross-service dependencies
---
## Summary
One request is fanned out to multiple domain services via a broker.
Each service returns its contribution asynchronously, and **AnswerService** gathers everything into a single answer for the user.