Buffalo — Interfaces#

Interface catalog#

Context#

  • Package: github.com/gobuffalo/buffalo
  • File: context.go:18
  • Methods:
    // Embeds context.Context (Deadline, Done, Err, Value)
    Response() http.ResponseWriter
    Request() *http.Request
    Session() *Session
    Cookies() *Cookies
    Params() ParamValues
    Param(string) string
    Set(string, any)
    LogField(string, any)
    LogFields(map[string]any)
    Logger() Logger
    Bind(any) error
    Render(int, render.Renderer) error
    Error(int, error) error
    Redirect(int, string, ...any) error
    Data() map[string]any
    Flash() *Flash
    File(string) (binding.File, error)
  • Purpose: Per-request state bag passed to every Handler and middleware. Wraps the HTTP writer/request pair, provides session/cookie/flash access, logging, body binding, and response rendering in a single unified surface.
  • Implementations: DefaultContext (default_context.go) — verified with compile-time assertion var _ Context = &DefaultContext{}.
  • Design quality: Broad (17 methods + inherited context.Context). Deliberately wide by framework design — it is the single thing middleware and handlers receive. This violates strict ISP but is a pragmatic framework choice: handlers rarely use all methods, but having them co-located saves indirection. The stdlib context.Context embedding is clean; it makes buffalo.Context passable to any stdlib-aware function expecting context.Context.

ParamValues#

  • Package: github.com/gobuffalo/buffalo
  • File: context.go:41
  • Methods:
    Get(string) string
  • Purpose: Minimal interface for URL/query/form parameter lookup. Satisfied by url.Values from stdlib.
  • Implementations: url.Values (stdlib) is the primary implementation; the interface allows users to supply custom param sources.
  • Design quality: Excellent ISP example. One method, one concern. The comment “isn’t it great that you set your own?” signals deliberate extensibility at minimal cost.

render.Renderer#

  • Package: github.com/gobuffalo/buffalo/render
  • File: render/renderer.go:7
  • Methods:
    ContentType() string
    Render(io.Writer, Data) error
  • Purpose: Unified contract for all response serialization strategies. Context.Render(statusCode, Renderer) accepts any value satisfying this interface.
  • Implementations: templateRenderer (HTML via plush), downloadRenderer (file download), stringRenderer (plain text), htmlAutoRenderer (content-negotiation auto), funcRenderer (callback-based), sseRenderer (Server-Sent Events). All are unexported structs; the Engine methods (HTML(), JSON(), String(), Download(), Auto()) serve as factories.
  • Design quality: Near-perfect example of the Interface Segregation Principle. Two methods, zero dependencies on buffalo internals — a third-party package can implement Renderer with zero imports from buffalo. The Data type (map[string]any) is the only coupling and it is defined in the same package.

servers.Server#

  • Package: github.com/gobuffalo/buffalo/servers
  • File: servers/servers.go:10
  • Methods:
    Shutdown(context.Context) error
    Start(context.Context, http.Handler) error
    SetAddr(string)
  • Purpose: Abstracts the HTTP serving layer. App.Serve() calls server.Start(ctx, app) and server.Shutdown(ctx) on SIGTERM, without knowing whether the server is plain TCP, TLS, or a pre-created listener.
  • Implementations: Simple (wraps *http.Server), TLS (adds cert/key loading), Listener (wraps pre-created net.Listener). Factory functions Wrap, WrapTLS, WrapListener create them from stdlib types.
  • Design quality: Well-segregated. Three methods cover the entire lifecycle. SetAddr is slightly awkward (mutating after creation), but needed to inject the address from Options after the server object is constructed. No compile-time assertion present (unlike Worker and Context).

worker.Worker#

  • Package: github.com/gobuffalo/buffalo/worker
  • File: worker/worker.go:14
  • Methods:
    Start(context.Context) error
    Stop() error
    Perform(Job) error
    PerformAt(Job, time.Time) error
    PerformIn(Job, time.Duration) error
    Register(string, Handler) error
  • Purpose: Background job queue abstraction. Decouples App from any specific job processing backend. The built-in Simple implementation runs jobs in goroutines; the interface allows third-party adapters (e.g., gocraft/work) to be swapped in via Options.Worker.
  • Implementations: Simple (worker/simple.go) — compile-time assertion var _ Worker = &Simple{} confirmed. Third-party adapters implement this interface externally.
  • Design quality: Well-designed, though the project itself acknowledges semantic ambiguity in a TODO comment: Perform() is named like an executor but acts as an enqueuer. The three-variant Perform/PerformAt/PerformIn scheduling API is a reasonable decomposition. Register decoupling job names from implementation allows serialization of job types across process restarts.

Resource#

  • Package: github.com/gobuffalo/buffalo
  • File: resource.go:28
  • Methods:
    List(Context) error
    Show(Context) error
    Create(Context) error
    Update(Context) error
    Destroy(Context) error
  • Purpose: Convention-based REST resource contract. App.Resource("/path", r) maps the five CRUD handlers to standard HTTP method+path combinations automatically.
  • Implementations: BaseResource (default implementation returning 404 for all methods, used for embedding). User types embed BaseResource and override specific methods — a template method pattern without using generics or reflection.
  • Design quality: Fixed five-method surface encodes the “resource = CRUD” convention explicitly. The Middler companion interface (Use() []MiddlewareFunc) allows per-resource middleware declaration without requiring App to be aware of it. The comment block explaining the middleware-skip interaction with type assertions is telling: the reflection-based middleware identity system requires the Resource variable to be typed as buffalo.Resource, not the concrete struct, for Skip() to work — a subtle coupling cost of the reflection approach.

Middler#

  • Package: github.com/gobuffalo/buffalo
  • File: resource.go:38
  • Methods:
    Use() []MiddlewareFunc
  • Purpose: Optional companion to Resource. If a resource struct also implements Middler, App.Resource() automatically applies the returned middleware to the resource’s route group.
  • Implementations: User-defined resource structs (opt-in).
  • Design quality: Good use of optional interface discovery (if mm, ok := r.(Middler); ok). Keeps Resource small while adding opt-in capability.

binding.Bindable#

  • Package: github.com/gobuffalo/buffalo/binding
  • File: binding/bindable.go:8
  • Methods:
    Bind(*http.Request) error
  • Purpose: Allows a struct to override the default body-binding logic. When ctx.Bind(v) is called and v implements Bindable, the struct’s own Bind method is called instead of the framework’s decoder.
  • Implementations: User-defined model types (opt-in).
  • Design quality: Classic “self-binder” pattern. Single method, zero framework coupling (just *http.Request). Clean escape hatch from the default decoder.

binding.ContenTypeBinder#

  • Package: github.com/gobuffalo/buffalo/binding
  • File: binding/types.go:8
  • Methods:
    BinderFunc() Binder
    ContentTypes() []string
  • Purpose: Allows registration of custom body decoders keyed by Content-Type header value. Used internally to register JSON, XML, and form decoders.
  • Implementations: Internal binder registrations.
  • Design quality: Two-method interface for an extension point that few users will touch. The name ContenTypeBinder has a typo (Conten missing the t) — a minor but persistent API wart.

mail.Sender / mail.BatchSender#

  • Package: github.com/gobuffalo/buffalo/mail
  • File: mail/sender.go:4
  • Methods (Sender):
    Send(Message) error
  • Methods (BatchSender extends Sender):
    Send(Message) error
    SendBatch(messages ...Message) ([]error, error)
  • Purpose: Email sending abstraction. Sender is the minimal contract; BatchSender extends it for bulk delivery with per-message error reporting.
  • Implementations: SMTP dialer implementation in mail/dialer.go.
  • Design quality: Good embedding pattern — BatchSender is a superset of Sender, so any BatchSender satisfies Sender. The variadic SendBatch returning ([]error, error) is somewhat unusual but practical for bulk mail scenarios.

Interface patterns#

  • Size distribution: Lean overall. Most interfaces have 1–3 methods (ParamValues, Renderer, Bindable, Server, Sender). Worker has 6 methods. Context is the outlier at ~17 (including embedded context.Context). Average excluding Context: ~2.5 methods per interface.

  • Embedding:

    • Context embeds context.Context (stdlib) — the single most consequential embedding decision in the codebase. It means buffalo contexts thread naturally through the stdlib ecosystem.
    • BatchSender embeds Sender — clean capability layering.
    • Resource does not embed anything; BaseResource is a concrete default, not an interface embed.
  • Implicit satisfaction: All interfaces are satisfied implicitly (no registration, no tagging). Consumers discover capability at runtime via type assertions (if mm, ok := r.(Middler); ok). Compile-time assertions (var _ Context = &DefaultContext{}, var _ Worker = &Simple{}) are used in two key locations to catch regressions during development.

  • Stdlib interfaces used:

    • context.Context — embedded in buffalo.Context
    • io.Writer — parameter in render.Renderer.Render()
    • http.HandlerApp implements it (ServeHTTP); servers.Server.Start() accepts it
    • http.ResponseWriter — returned by Context.Response()
    • http.Hijacker — implemented by Response for WebSocket support
    • fs.ReadDirFile — implemented by fs.go for embedded template FS

Key abstractions#

  1. Context — The load-bearing abstraction of the entire framework. Every handler and middleware is written against Context, not against concrete HTTP types. Broad by design; the cost is that testing handlers requires constructing a full DefaultContext or a test double for all 17 methods. This is the interface most likely to feel burdensome to mock.

  2. render.Renderer — The best-designed interface in the codebase. Two methods, no buffalo dependencies, trivially mockable. The richness of the render ecosystem (7+ implementations) from such a small interface is evidence of how well ISP works here. Third-party renderers are a natural extension point.

  3. worker.Worker — The primary DI seam in Options. Buffalo’s built-in job system is deliberately minimal; the interface exists to make it swappable. The semantic confusion between “enqueue” and “execute” (noted in code TODOs) is the main design debt.

  4. servers.Server — Makes TLS, Unix socket, and custom listener configurations first-class via Options.Servers []Server. Most users never see this interface, but it is what allows Buffalo to support non-TCP serving without any conditional logic in App.

  5. Resource — The framework’s opinionated REST convention made explicit in Go types. The BaseResource default implementation pattern (embed and override) trades generics/reflection for simplicity. The interaction with reflection-based middleware identity is the weakest coupling in the design.


Interface-driven extensibility#

Buffalo uses interfaces at three distinct extension layers:

  • Serving layer (servers.Server): Users plug in custom HTTP servers by passing []Server to Serve(). This is how production deployments add TLS termination, custom listeners, or UNIX domain sockets.

  • Rendering layer (render.Renderer): Any type implementing two methods can be returned from ctx.Render(). Third-party renderers (e.g., a PDF renderer, a MessagePack renderer) require zero changes to the framework.

  • Background jobs (worker.Worker): The Options.Worker field accepts any Worker implementation. Production deployments typically replace the in-process Simple worker with a Redis-backed adapter (e.g., buffalo-gocraft-work), and the framework is entirely unaware of the backend.

The Bindable and ContenTypeBinder interfaces provide escape hatches in the request-decoding pipeline — useful for specialized data types or custom protocols.

What Buffalo does not use interfaces for: routing (gorilla/mux is a concrete dependency), sessions (gorilla/sessions is concrete), or templating (gobuffalo/plush is concrete). These are design choices, not oversights — the framework picks sensible defaults and exposes extension points only where real variability is expected.