Documentation
¶
Overview ¶
Package di helps implement Dependency Injection for web development. Dependency Injection aims to make dependencies accessible to its clients without them having to construct or ask for their dependencies explicitly. This raises the questions
- Where should objects be constructed ?
- How to make those objects available to all of the places it is needed ?
di uses factories to isolate dependency construction and make them available via struct fields. This makes them accessible to code that uses them residing in methods on those structs.
To that end di provides the types Dispatcher, Binding and interfaces ApplicationFactory, RequestFactory, Controller and Router.
A Controller represents a type whose methods can serve HTTP requests and exports Bindings. A Binding specifies binds an HTTP request with a specific <Verb,Path> to a method on that Controller. A Controller can be registered with a Dispatcher teaching it how to route requests.
ApplicationFactory and RequestFactory encapsulate all object construction including Controllers. Dispatcher uses the factories to get hold of a Controller object for the request and call the appropriate method.
The Dispatcher uses a Router to handle request multiplexing.
The flow of control while serving requests looks like
- Request arrives.
- Router routes it based on <Verb, Path> to a closure registered by the Dispatcher.
- The closure gets hold of RequestFactory by calling ApplicationFactory.With passing it the request object.
- The closure gets hold of Controller by passing the appropriate label to RequestFactory.NewController .
- The closure looks up and calls the Controller method registered.
The example demonstrates how to wire everything up.
Example ¶
// This example demonstrates injecting Transport into controller. The Transport // implementation injected depends on the value of the variable config.test. package main import ( "fmt" "net/http" "net/http/httptest" "net/url" "github.com/kkrs/di" "github.com/kkrs/di/router" ) // Transport represents the ability to send a message. type Transport interface { Send(from, to, msg string) error } // controller handles requests to send messages. Transport is its sole // dependency. type controller struct { transport Transport // dependency injected } // controller would like requests <GET, /send> to be dispatched to its method // Send. func (ct controller) Bindings() []di.Binding { return []di.Binding{ {"GET", "/send", "Send"}, } } // Send implements logic to process the HTTP request to send a message. It // delegtes the actual task of sending the message to Transport. func (ct controller) Send(rw http.ResponseWriter, req *http.Request) { err := ct.transport.Send(req.FormValue("from"), req.FormValue("to"), req.FormValue("msg")) if err != nil { rw.WriteHeader(http.StatusInternalServerError) return } rw.WriteHeader(http.StatusOK) } // exampleTransport is a Transport implementation that prints its arguments to stdout. // That allows for it to be used in an Example test. type exampleTransport struct{} func (m exampleTransport) Send(from, to, msg string) error { fmt.Printf("%s %s from %s\n", msg, to, from) return nil } // gaeTransport is a dummy impl that demonstrates what an appengine version would // look like. type gaeTransport struct { // ctx context.Context } // gaeTransport.Send does nothing so it doesn't import appengine. func (tr gaeTransport) Send(from, to, msg string) error { // mail here refers to the appengine mail package // // mail.Send(tr.ctx, mail.Message{ // Sender: from, // To: []string{to}, // Body: msg, // }) return nil } // application configuration. type config struct { test bool } // appFactory implements ApplicationFactory for this app. It gets created with // the singletons config and exampleTransport. type appFactory struct { config config exampleTransport exampleTransport } // With is called by Dispatcher to inject the request object. func (fa appFactory) With(req *http.Request) di.RequestFactory { return reqFactory{ af: fa, // pass reference to self so reqFactory has access all singletons } } type reqFactory struct { af appFactory // reference to singletons } // newTransport returns a Transport implementation depending on the value of // config.test. This allows Transport implementations that gets injected to be // configurable. func (fa reqFactory) newTransport() Transport { if fa.af.config.test { return fa.af.exampleTransport // return the test impl } // return the dummy prod impl. A real implementation would do // return gaeTransport{appengine.NewContext(fa.req)} return gaeTransport{} } func (fa reqFactory) NewController(name string) di.Controller { switch name { case "message": return controller{fa.newTransport()} default: panic(fmt.Errorf("do not know how to create '%s'", name)) } } func main() { // populate singletons factory := appFactory{ config{true}, // test environment exampleTransport{}, } router := router.New() dispatcher := di.New("example", router, factory) if err := dispatcher.Register(controller{}, "message"); err != nil { panic(err) } query := url.Values{"from": {"kkrs"}, "to": {"world"}, "msg": {"hello"}} req, err := http.NewRequest("GET", "/send?"+query.Encode(), nil) if err != nil { panic(err) } rw := httptest.NewRecorder() router.ServeHTTP(rw, req) // NOTE: need a blank line before the Output line or the test won't get // executed. // Verify that controller works as expected. }
Output: hello world from kkrs
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ApplicationFactory ¶
type ApplicationFactory interface { // With associates a RequestFactory with the request object. With(*http.Request) RequestFactory }
An ApplicationFactory is expected to have access to all singletons and know how to create a RequestFactory given a request object.
type Binding ¶
type Binding struct { Verb string // The HTTP Verb to use Path string // The URL path to attach the method to Name string // Name of the method the request should be dispatched to }
A Binding describes how a request is to be routed and is returned by Controller.Bindings. It specifies that the request <Verb, Path> be delivered to the method Name. The method Name refers to is required to be of type
func(Controller, http.ResponseWriter, *http.Request)
Reflection is used to lookup Name and validate it during registration.
type Controller ¶
type Controller interface {
Bindings() []Binding
}
A Controller has methods that handle requests. It exports Bindings describing how those methods are to be bound.
type Dispatcher ¶
type Dispatcher struct {
// contains filtered or unexported fields
}
Dispatcher orchestrates request handling with the help of the other types in this package. It uses Router to multiplex requests, ApplicationFactory and RequestFactory to get hold of fully constructed Controllers. It then dispatches the request to the appropriate Controller method.
func New ¶
func New(name string, router Router, factory ApplicationFactory) Dispatcher
New creates a new Dispatcher. It panics if any of its arguments have zero values.
func (Dispatcher) Register ¶
func (di Dispatcher) Register(ctrl Controller, as string) error
Register registers Bindings returned by Controller. It looks up and validates that each method of the Binding is of the appropriate type and arranges for requests to be delivered to the appropriate methods. Refer to the documentation for Binding.
func (Dispatcher) String ¶
func (di Dispatcher) String() string
type RequestFactory ¶
type RequestFactory interface { // NewController returns a controller instance given the label it was // registered with. It is expected to panic if it encounters errors during // construction. NewController(label string) Controller }
A RequestFactory is expected to know to create Controllers along with their dependencies. The Dispatcher uses RequestFactory to get hold of the Controller to dispatch the request to.
type Router ¶
type Router interface { Handle(verb string, path string, handler http.Handler) HandleFunc(verb string, path string, handler func(http.ResponseWriter, *http.Request)) // Must be a handler itself http.Handler }
A Router represents the ability to multiplex an http request with <Verb, Path> to handler. The Dispatcher delegates request multiplexing to Router. A simple implementation around http.ServeMux is provided in sub-package router.