hello
The hello.example microservice is the main showcase example for the framework. Where helloworld shows the bare skeleton, hello collects many of the patterns a real microservice uses into a single package:
- typed configs with defaults and validation
- a ticker that fires on a fixed interval
- a service-to-service call to calculator via its generated client
- multicast discovery (
Ping) over the management port - embedded static resources (image + YAML) served from the binary
- locale-aware string lookup driven by the
Accept-Languageheader - a root-mapped endpoint via the magic
roothostname
It’s intentionally a grab-bag — the goal is breadth of demonstration, not modeling a production service.
Depends On
calculator.example— theCalculatorUI endpoint delegates the actual computation to it via the generatedcalculatorapiclient.
Try It
With the examples app running:
/hello?name=Bella— greeting driven byGreetingandRepeatconfigs:http://localhost:8080/hello.example/hello?name=Bella
Ciao, Bella! Ciao, Bella! Ciao, Bella!/echo— dumps the incoming HTTP request, including the framework’sMicrobus-*control headers:http://localhost:8080/hello.example/echo
GET /echo HTTP/1.1 Host: hello.example Microbus-Call-Depth: 1 Microbus-From-Host: http.ingress.core Microbus-From-Id: tg190vjj3j Microbus-Msg-Id: UQnfaJf4 Microbus-Time-Budget: 19.749s .../ping— multicasts to every microservice on the bus, lists the responders:http://localhost:8080/hello.example/ping
bvtgii68r8.messaging.example mv2pcoockl.messaging.example r52l78kha4.messaging.example pa6r5ohm5h.hello.example 0iij3m5fhf.http.ingress.core 7k9f82n45f.configurator.core n89hmtb9iq.calculator.example/calculator— HTML form that posts back through the calculator service:http://localhost:8080/hello.example/calculator

/bus.png— static image served from the embeddedresources/directory./localized— picks a translation fromresources/text.yamlbased on theAccept-Languageheader.http://localhost:8080/hello.example/localized
en: Hello fr: Bonjour es: Hola it: Salve de: Guten Tag pt: Olá da: Goddag nl: Goedendag pl: Dzień dobry no: God dag tr: Merhaba sv: God dag/— root page, served from the magicroothostname.
Code Walkthrough
Source: exampleservices/hello/service.go.
The Service struct holds no state of its own; everything it needs comes from the embedded *Intermediate:
type Service struct {
*Intermediate // IMPORTANT: Do not remove
}OnStartup and OnShutdown are empty.
Wiring (intermediate.go)
The constructor plus the salient subscriptions, ticker, and configs from the generated intermediate.go:
func NewService() *Service {
svc := &Service{}
svc.Intermediate = NewIntermediate(svc)
return svc
}Each web endpoint is a sub.Web() subscription — the handler signature is (w http.ResponseWriter, r *http.Request):
svc.Subscribe("Hello", svc.Hello, sub.At(...), sub.Web())
svc.Subscribe("Echo", svc.Echo, sub.At(...), sub.Web())
svc.Subscribe("Ping", svc.Ping, sub.At(...), sub.Web())
svc.Subscribe("Calculator", svc.Calculator, sub.At(...), sub.Web())
svc.Subscribe("BusPNG", svc.BusPNG, sub.At(...), sub.Web())
svc.Subscribe("Localization", svc.Localization, sub.At(...), sub.Web())
svc.Subscribe("Root", svc.Root, sub.At(...), sub.Web())The ticker fires TickTock every 10 seconds:
svc.StartTicker("TickTock", 10*time.Second, svc.TickTock)Configs are declared with defaults and (optionally) validation rules — and produce typed accessor methods on *Service:
svc.DefineConfig(
"Greeting",
cfg.Description(`Greeting to use.`),
cfg.DefaultValue("Hello"),
)
svc.DefineConfig(
"Repeat",
cfg.Description(`Repeat indicates how many times to display the greeting.`),
cfg.DefaultValue("1"),
cfg.Validation("int [0,100]"),
)After this, svc.Greeting() returns a string and svc.Repeat() returns an int — the conversion from the raw string config value happens in the generated code, so handlers never deal with svc.Config("Repeat") + manual parsing.
Hello
func (svc *Service) Hello(w http.ResponseWriter, r *http.Request) error {
name := r.URL.Query().Get("name")
if name == "" {
name = "World"
}
greeting := svc.Greeting()
hello := greeting + ", " + name + "!\n"
hello = strings.Repeat(hello, svc.Repeat())
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(hello))
return nil
}The simplest config consumer in the example. svc.Greeting() and svc.Repeat() are the typed accessors generated from the DefineConfig calls above. Set them in main/config.yaml to change the greeting without rebuilding.
Echo
func (svc *Service) Echo(w http.ResponseWriter, r *http.Request) error {
// Prevent the http package from serializing Go-http-client/1.1 as the user-agent
if len(r.Header.Values("User-Agent")) == 0 {
r.Header.Set("User-Agent", "")
}
w.Header().Set("Content-Type", "text/plain")
err := r.Write(w)
return errors.Trace(err)
}The interesting output isn’t the body the user sent — it’s the headers the framework added on the way in. Microbus-Call-Depth, Microbus-From-Host, Microbus-From-Id, Microbus-Msg-Id, Microbus-Time-Budget are the framework’s transport metadata, propagated on every call. Echo is a handy diagnostic when wiring up new services or chasing a tracing/identity issue.
Ping
func (svc *Service) Ping(w http.ResponseWriter, r *http.Request) error {
var buf bytes.Buffer
ch := svc.Publish(
r.Context(),
pub.GET("https://all:888/ping"),
pub.Multicast(),
)
for i := range ch {
res, err := i.Get()
if err == nil {
fromHost := frame.Of(res).FromHost()
fromID := frame.Of(res).FromID()
buf.WriteString(fromID)
buf.WriteString(".")
buf.WriteString(fromHost)
buf.WriteString("\r\n")
}
}
w.Header().Set("Content-Type", "text/plain")
w.Write(buf.Bytes())
return nil
}Ping demonstrates multicast over the management port :888:
- The hostname
allis the magic “broadcast to every service on the bus” target. - Port
:888is the framework’s management port — it serves built-in control endpoints like/pingand/openapi.jsonon every microservice. It’s reachable only over the bus (not through the HTTP ingress proxy), so calls to it are inherently internal. pub.Multicast()says “expect many responses, not one.” The returned channel yields each response as it arrives;frame.Of(res).FromHost()and.FromID()extract the responder’s identity from the response headers.
Calculator
func (svc *Service) Calculator(w http.ResponseWriter, r *http.Request) error {
// ... HTML form scaffolding for X, op, Y inputs ...
if x != "" && y != "" && op != "" {
xx, err := strconv.Atoi(x)
if err != nil {
return errors.Trace(err)
}
yy, err := strconv.Atoi(y)
if err != nil {
return errors.Trace(err)
}
// Call the calculator service using its client
_, _, _, result, err := calculatorapi.NewClient(svc).Arithmetic(r.Context(), xx, op, yy)
if err != nil {
buf.WriteString(html.EscapeString(err.Error()))
} else {
buf.WriteString(strconv.Itoa(result))
}
}
// ... form submit, response ...
}The HTML scaffolding is omitted above for brevity; the salient point is the service-to-service call in the middle: calculatorapi.NewClient(svc).Arithmetic(r.Context(), xx, op, yy). The generated client makes the call over the bus and unmarshals the response into typed return values — there’s no URL construction, no JSON parsing, no pub.Request here. Errors from the calculator are written into the rendered HTML rather than bubbled up, because this is a UI handler.
BusPNG
func (svc *Service) BusPNG(w http.ResponseWriter, r *http.Request) (err error) {
return svc.ServeResFile("bus.png", w, r)
}ServeResFile serves a file from the package’s embedded resources/ filesystem (the FS is registered in intermediate.go via svc.SetResFS(resources.FS)). The content type is inferred from the extension.
Localization
func (svc *Service) Localization(w http.ResponseWriter, r *http.Request) (err error) {
ctx := r.Context()
hello, _ := svc.LoadResString(ctx, "Hello")
w.Write([]byte(hello))
return nil
}LoadResString looks up the key "Hello" in resources/text.yaml and returns the best translation for the request’s Accept-Language header. The matching logic — including weighting and fallback to a default language — is handled by the framework; the handler just asks for a string by key.
Root
func (svc *Service) Root(w http.ResponseWriter, r *http.Request) (err error) {
var buf bytes.Buffer
buf.WriteString(`<html><body><h1>Microbus</h1></body></html>`)
w.Write(buf.Bytes())
return nil
}Root is mapped to the route //root (absolute path beginning with //). The leading double slash routes the handler under the magic root hostname, which the HTTP ingress proxy exposes at the server root (/) rather than under /hello.example/.... This is the pattern for a landing page or any other endpoint that should live at the ingress root.
TickTock
func (svc *Service) TickTock(ctx context.Context) error {
svc.LogInfo(ctx, "Ticktock")
return nil
}The handler signature for a ticker is just (ctx context.Context) error — no HTTP because it’s not invoked as a request. The schedule (10*time.Second) is set in the StartTicker call shown in Wiring. Tickers are the standard place to put periodic housekeeping: cache eviction, batch flushing, health probes against external systems.
See Also
- Calculator example — the downstream service that
Calculatorcalls. - Helloworld example — the bare-skeleton baseline this one extends.
- Configurator core service — how
DefineConfigdeclarations get values fromconfig.yaml. - Embedded resources — the
resources/package pattern used byBusPNG. - Internationalization — how
LoadResStringpicks a locale viaAccept-Language.