HTTP Layer
Fairway provides two registries for wiring commands and views to HTTP routes: HttpChangeRegistry for commands and HttpViewRegistry for views.
HttpChangeRegistry
Collects command routes and registers them on an http.ServeMux.
type HttpChangeRegistry struct { /* ... */ }
func (r *HttpChangeRegistry) RegisterCommand(
pattern string,
handler func(CommandRunner) http.HandlerFunc,
)
func (r HttpChangeRegistry) RegisterRoutes(mux *http.ServeMux, runner CommandRunner)
func (r HttpChangeRegistry) RegisteredRoutes() []string
Pattern
RegisterCommand takes a Go 1.22+ HTTP pattern ("METHOD /path/{param}") and a factory function. The factory receives a CommandRunner and returns an http.HandlerFunc. This allows handlers to close over the runner.
Example
var ChangeRegistry fairway.HttpChangeRegistry
func Register(registry *fairway.HttpChangeRegistry) {
registry.RegisterCommand("POST /api/lists/{listId}", func(runner fairway.CommandRunner) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var body struct {
Name string `json:"name" validate:"required"`
}
if err := utils.JsonParse(r, &body); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := runner.RunPure(r.Context(), createListCommand{
listId: r.PathValue("listId"),
name: body.Name,
}); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
}
})
}
func init() { Register(&ChangeRegistry) }
Wiring in main.go
HttpViewRegistry
Collects view routes and registers them on an http.ServeMux.
type HttpViewRegistry struct { /* ... */ }
func (r *HttpViewRegistry) RegisterView(
pattern string,
handler func(EventsReader) http.HandlerFunc,
)
func (r HttpViewRegistry) RegisterRoutes(mux *http.ServeMux, client EventsReader)
func (r HttpViewRegistry) RegisteredRoutes() []string
Example
var ViewRegistry fairway.HttpViewRegistry
func Register(registry *fairway.HttpViewRegistry) {
registry.RegisterView("GET /api/lists/{listId}", func(reader fairway.EventsReader) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// build projection and write response
}
})
}
func init() { Register(&ViewRegistry) }
Wiring in main.go
Self-Registering Modules
The pattern throughout Fairway is to use init() for zero-coordination self-registration:
// in change/createlist/createlist.go
var _ = func() struct{} {
// or simply:
return struct{}{}
}
func init() {
change.ChangeRegistry.RegisterCommand("POST /api/lists/{listId}", handler)
}
main.go only needs to:
- Create the store
- Create the mux
- Call
RegisterRouteson each registry - Start the server
All modules wire themselves automatically at import time. Adding a new module is a single import line in main.go.
Complete main.go Example
package main
import (
"log"
"net/http"
"github.com/apple/foundationdb/bindings/go/src/fdb"
"github.com/err0r500/fairway"
"github.com/err0r500/fairway/dcb"
_ "myapp/change/createlist" // registers itself via init()
_ "myapp/change/additem"
_ "myapp/view/showlist"
"myapp/change"
"myapp/view"
)
func main() {
fdb.MustAPIVersion(740)
db := fdb.MustOpenDefault()
store := dcb.NewDcbStore(db, "myapp")
mux := http.NewServeMux()
change.ChangeRegistry.RegisterRoutes(mux, fairway.NewCommandRunner(store))
view.ViewRegistry.RegisterRoutes(mux, fairway.NewReader(store))
log.Fatal(http.ListenAndServe(":8080", mux))
}