Pasar contexto a métodos de interfaz

Algo inspirado porEste artículo la semana pasada, estoy jugando con la refactorización de una aplicación que tengo para pasar más explícitamente el contexto (grupos de bases de datos, almacenes de sesiones, etc.) a mis controladores.

Sin embargo, un problema que tengo es que sin un mapa de plantillas globales, elServeHTTP método en mi tipo de controlador personalizado (como para satisfacerhttp.Handler) ya no puede acceder al mapa para representar una plantilla.

Necesito retener lo globaltemplates variable, o redefinir mi tipo de controlador personalizado como una estructura.

¿Hay una mejor manera de lograr esto?

func.go

package main

import (
    "fmt"
    "log"
    "net/http"

    "html/template"

    "github.com/gorilla/sessions"
    "github.com/jmoiron/sqlx"
    "github.com/zenazn/goji/graceful"
    "github.com/zenazn/goji/web"
)

var templates map[string]*template.Template

type appContext struct {
    db    *sqlx.DB
    store *sessions.CookieStore
}

type appHandler func(w http.ResponseWriter, r *http.Request) (int, error)

func (ah appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // templates must be global for us to use it here
    status, err := ah(w, r)
    if err != nil {
        log.Printf("HTTP %d: %q", status, err)
        switch status {
        case http.StatusNotFound:
            // Would actually render a "http_404.tmpl" here...
            http.NotFound(w, r)
        case http.StatusInternalServerError:
            // Would actually render a "http_500.tmpl" here
            // (as above)
            http.Error(w, http.StatusText(status), status)
        default:
            // Would actually render a "http_error.tmpl" here
            // (as above)
            http.Error(w, http.StatusText(status), status)
        }
    }
}

func main() {
    // Both are 'nil' just for this example
    context := &appContext{db: nil, store: nil}

    r := web.New()
    r.Get("/", appHandler(context.IndexHandler))
    graceful.ListenAndServe(":8000", r)
}

func (app *appContext) IndexHandler(w http.ResponseWriter, r *http.Request) (int, error) {
    fmt.Fprintf(w, "db is %q and store is %q", app.db, app.store)
    return 200, nil
}

struct.go

package main

import (
    "fmt"
    "log"
    "net/http"

    "html/template"

    "github.com/gorilla/sessions"
    "github.com/jmoiron/sqlx"
    "github.com/zenazn/goji/graceful"
    "github.com/zenazn/goji/web"
)

type appContext struct {
    db        *sqlx.DB
    store     *sessions.CookieStore
    templates map[string]*template.Template
}

// We need to define our custom handler type as a struct
type appHandler struct {
    handler func(w http.ResponseWriter, r *http.Request) (int, error)
    c       *appContext
}

func (ah appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    status, err := ah.handler(w, r)
    if err != nil {
        log.Printf("HTTP %d: %q", status, err)
        switch status {
        case http.StatusNotFound:
            // Would actually render a "http_404.tmpl" here...
            http.NotFound(w, r)
        case http.StatusInternalServerError:
            // Would actually render a "http_500.tmpl" here
            // (as above)
            http.Error(w, http.StatusText(status), status)
        default:
            // Would actually render a "http_error.tmpl" here
            // (as above)
            http.Error(w, http.StatusText(status), status)
        }
    }
}

func main() {
    // Both are 'nil' just for this example
    context := &appContext{db: nil, store: nil}

    r := web.New()
    // A little ugly, but it works.
    r.Get("/", appHandler{context.IndexHandler, context})
    graceful.ListenAndServe(":8000", r)
}

func (app *appContext) IndexHandler(w http.ResponseWriter, r *http.Request) (int, error) {
    fmt.Fprintf(w, "db is %q and store is %q", app.db, app.store)
    return 200, nil
}

¿Hay alguna forma más limpia de pasar elcontext instancia aServeHTTP?

Tenga en cuenta quego build -gcflags=-m muestra que ninguna de las opciones parece ser peor en equipos de asignación de almacenamiento dinámico: el&appContext literal escapa al montón (como se esperaba) en ambos casos, aunque mi interpretación es que la opción basada en estructura pasa un segundo puntero (acontext) en cada solicitud:corrígeme si me equivoco aquí ya que me encantaría entenderlo mejor.

No estoy totalmente convencido de que los globales sean malos en el paquete principal (es decir, no una lib), siempre que sean seguros de usar de esa manera (solo lectura / mutexes / un grupo), pero me gusta la claridad que tiene que pasar explícitamente el contexto.

Respuestas a la pregunta(2)

Su respuesta a la pregunta