« Back to home

Ideas for Go-Json-Rest v3.0.0 (RFC)

Posted on

V3 is the opportunity for API changes and improvements.

In the past two years Go-Json-Rest has changed a lot. It got the notion of middleware, bringing basic features like Auth, CORS, JSONP that were missing at the beginning. Over time the list of options and settings has doubled. This is reflected in the main object ResourceHandler that has become a long list of options. It works, but it could be more convenient to use. The goal of the v3 is to provide a replacement for ResourceHandler that is simpler to use and more flexible.

The ResourceHandler does not do much, given the settings, it instantiates the middlewares and the router, and wraps all that in order to produce a net/http Handler. The middlewares and the router are in fact the low-level api of go-json-rest. The idea of v3 is to open it, make it public.

Proposed new API

1) Make public the existing private Middlewares

So in addition the following, already public, middlewares:

  • AuthBasicMiddleware
  • CorsMiddleware
  • JsonpMiddleware

We will get the following ones:

  • AccessLogApacheMiddleware
  • AccessLogJsonMiddleware
  • TimerMiddleware
  • RecorderMiddleware
  • GzipMiddleware
  • RecoverMiddleware
  • ContentTypeCheckerMiddleware
  • JsonIndentMiddleware
  • PoweredByMiddleware
  • StatusMiddleware

2) Propose some predefined stacks of Middlewares

Precise lists and options to be determined, but for instance:

    var DefaultDevStack = []Middleware{
            &AccessLogApacheMiddleware{},
            &TimerMiddleware{},
            &RecorderMiddleware{},
            &JsonIndentMiddleware{},
            &PoweredByMiddleware{},
            &RecoverMiddleware{
                    EnableResponseStackTrace: true,
            },
    }

    var DefaultProdStack = []Middleware{
            &AccessLogApacheMiddleware{},
            &TimerMiddleware{},
            &RecorderMiddleware{},
            &GzipMiddleware{},
            &PoweredByMiddleware{},
            &RecoverMiddleware{},
            &CheckContentTypeMiddleware{},
    }

Most of the options and settings of the current ResourceHandler end up being options of the middlewares, or just including of not including a middleware. Example:

    MyStack := []Middleware{
            &AccessLogApacheMiddleware{
                   Logger: &myLogger,
                   Format: rest.CombinedLogFormat,
            },
            &TimerMiddleware{},
            &RecorderMiddleware{},
            &GzipMiddleware{},
            &RecoverMiddleware{
                   Logger: &myLogger,
            },
            &CheckContentTypeMiddleware{},
    }

3) A new App interface

Go-Json-Rest already defines this interface:

type HandlerFunc func(ResponseWriter, *Request)

type Middleware interface {
    MiddlewareFunc(handler HandlerFunc) HandlerFunc
}

In v3, it will be completed by this new:

type App interface {
    AppFunc() HandlerFunc
}

v3 will also offer the two following adapter types. Convenient to write simple Apps or Middlewares without defining types. (Unit tests are written this way, for instance)

    type MiddlewareSimple func(handler HandlerFunc) HandlerFunc
    func (ms MiddlewareSimple) MiddlewareFunc(handler HandlerFunc) HandlerFunc {
    }

    type AppSimple HandlerFunc
    func (as AppSimple) AppFunc() HandlerFunc {
    }

It allows to write

    api.SetApp(AppSimple(func(w ResponseWriter, r *Request) {
            ...
    }))

    api.Use(MiddlewareSimple(func(handler HandlerFunc) HandlerFunc {
            return func(w ResponseWriter, r *Request) {
                    ...
            }
    }))

4) Explicitly build the router as an App

The router already implements the App interface by providing the AppFunc method. The following function will be added to explicitly build it:

    func MakeRouter(routes ...Routes) (App, error) {
    }

Building a Go-Json-Rest api now consists in assembling a stack of Middlewares and putting an App on top of it. It also open the door to interchangeable routers by allowing third party routers to be wrapped inside an App.

5) Finally, this new Api object that provides the syntactic sugar

    type Api struct {
    }

    func NewApi() *Api {
    }

    func SetApp(app App) {
    }

    func (api *Api) Use(middlewares ...Middleware) {
    }

    func (api *Api) MakeHandler() http.Handler {
    }

The new “Hello World!” example

“Hello World!” with JSONP support:

    func main() {

            api := rest.NewApi()

            // the Middleware stack
            api.Use(rest.DefaultDevStack...)
            api.Use(&rest.JsonpMiddleware{
                    CallbackNameKey: "cb",
            })

            // build the App, here the rest Router
            router, err := rest.MakeRouter(
                    &Route{"GET", "/message", func(w rest.ResponseWriter, req *rest.Request) {
                            w.WriteJson(map[string]string{"Body": "Hello World!"})
                    }},
            )
            if err != nil {
                    log.Fatal(err)
            }
            api.SetApp(router)

            // build and run the handler
            log.Fatal(http.ListenAndServe(
                    ":8080",
                    api.MakeHandler(),
            ))
    }

Compared to the previous version:

    func main() {
            handler := rest.ResourceHandler{
                    PreRoutingMiddlewares: []rest.Middleware{
                            &rest.JsonpMiddleware{
                                    CallbackNameKey: "cb",
                            },
                    },
            }
            err := handler.SetRoutes(
                    &rest.Route{"GET", "/message", func(w rest.ResponseWriter, req *rest.Request) {
                            w.WriteJson(map[string]string{"Body": "Hello World!"})
                    }},
            )
            if err != nil {
                    log.Fatal(err)
            }
            log.Fatal(http.ListenAndServe(":8080", &handler))
    }

Semver and backward compatibility

Go-Json-Rest follows Semver, breaking API changes are introduced only with major version changes. In this particular case, even if the API changes are important, it is possible to maintain the backward compatibility and just mark the old API as deprecated.

The objects will be marked as deprecated, and after a few months, can be removed from the package.