Dependency injections

The server-side application's primary entry point is the main function defined in the igweb.go source file. The client-side application's primary entry point is the main function defined in the client/client.go source file. In both of these primary entry points, we utilize a dependency injection technique to share a common functionality throughout the web application. By doing so, we avoid having to utilize package-level global variables.

On both the server side, and on the client side, we implement a custom Env type in the common package. You may consider that Env stands for the common functionality that is to be accessed from the application environment.

Here's the declaration of the Env struct on the server side, found in the common/common.go source file:

package common

import (
"github.com/EngineerKamesh/igb/igweb/common/datastore"
"github.com/gorilla/sessions"
"github.com/isomorphicgo/isokit"
)

type Env struct {
DB datastore.Datastore
TemplateSet *isokit.TemplateSet
}

The DB field will be used to store the custom datastore object.

The TemplateSet field is a pointer to a TemplateSet object. A template set allows us to render templates in a flexible manner across environments, and we'll be going over them in detail in Chapter 4, Isomorphic Templates.

The Store field is a pointer to a sessions.FilesystemStore object. We will be using the sessions package from the Gorilla toolkit for session management.

Inside the main function in the igweb.go source file, we will declare a env variable, an object of the common.Env type:

  env := common.Env{}

We assign the DB and TemplateSet fields of the env object with a newly created RedisDatastore instance and a newly created TemplateSet instance, respectively (the assignments are shown in bold). For illustration purposes, we have omitted some code and we have shown a partial code listing here:

  db, err := datastore.NewDatastore(datastore.REDIS, "localhost:6379")
ts := isokit.NewTemplateSet()

env.TemplateSet = ts
env.DB = db

We will use the Gorilla Mux router for our server-side routing needs. Notice that we pass in a reference to the env object as an input argument (shown in bold) to the registerRoutes function:

func registerRoutes(env *common.Env, r *mux.Router) {

We propagate the env object to our request handler functions by including a reference to the env object as an input argument to the route handler function that we register for a particular route, as shown here:

r.Handle("/index", handlers.IndexHandler(env)).Methods("GET")

By calling the Gorilla Mux router's Handle method, we have registered the /index route, and we have associated the IndexHandler function from the handlers package as the function that will service this route. We have supplied the reference to the env object as the sole input argument to this function (shown in bold). At this point, we have successfully propagated the RedisDatastore and TemplateSet instances, and we have made them available to the IndexHandler function.

Let's examine the source code of the IndexHandler function defined in the handlers/index.go source file: 

package handlers

import (
"net/http"

"github.com/EngineerKamesh/igb/igweb/common"
"github.com/EngineerKamesh/igb/igweb/shared/templatedata"
"github.com/isomorphicgo/isokit"
)

func IndexHandler(env *common.Env) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
templateData := templatedata.Index{PageTitle: "IGWEB"}
env.TemplateSet.Render("index_page", &isokit.RenderParams{Writer: w, Data: templateData})
})
}

Notice that the handler logic for the handler function is placed into a closure, and we have closed over the env variable. This allows us to satisfy the requirement that the handler function should return a http.Handler, and at the same time, we can provide access to the env object to the handler function.

The benefit of this approach, instead of using package-level global variables, is that we can explicitly see that this handler function requires the env object to properly function by examining the input arguments to the function (shown in bold).

We follow a similar dependency injection strategy on the client side as well. Here's the declaration of the Env type on the client side, found in the client/common/common.go source file:

package common

import (
"github.com/isomorphicgo/isokit"
"honnef.co/go/js/dom"
)

type Env struct {
TemplateSet *isokit.TemplateSet
Router *isokit.Router
Window dom.Window
Document dom.Document
PrimaryContent dom.Element
Location *dom.Location
}

The Env type that we have declared on the client side is different from the one that we declared on the server side. This is understandable, since there's a different set of common functionality that we want to have access to on the client side. For example, there is no RedisDatastore that lives on the client side.

We have declared the TemplateSet field in the same manner that we did on the server side. Because the *isokit.TemplateSet type is isomorphic, it can exist on both the server side and the client side.

The Router field is a pointer to the client-side isokit.Router instance.

The Window field is the Window object, and the Document field is the Document object.

The PrimaryContent field represents the div container that we will render page content to, on the client side. We will be covering the roles of these fields in more detail in Chapter 4, Isomorphic Templates.

The Location field is for the Window object's Location object.

Inside the registerRoutes function defined in the client.go source file, we use the isokit.Router to handle our client-side routing needs. We propagate the env object to the client-side handler function as follows:

  r := isokit.NewRouter()
r.Handle("/index", handlers.IndexHandler(env))

Let's examine the source code of the IndexHandler function on the client side, defined in the client/handlers/index.go source file:</span>

func IndexHandler(env *common.Env) isokit.Handler {
return isokit.HandlerFunc(func(ctx context.Context) {
templateData := templatedata.Index{PageTitle: "IGWEB"}
env.TemplateSet.Render("index_content", &isokit.RenderParams{Data: templateData, Disposition: isokit.PlacementReplaceInnerContents, Element: env.PrimaryContent, PageTitle: templateData.PageTitle})
})
}

The means by which we have provided access to the env object (shown in bold) to this handler function is identical to the way that we had done so on the server side. The handler logic for the handler function is placed into a closure, and we have closed over the env variable. This allows us to satisfy the requirement that the client-side handler function should return an isokit.Handler, and at the same time, we can provide the handler function access to the env object.

The dependency injection technique that we have utilized here, was inspired by the technique illustrated in Alex Edwards' blog post on organizing database access: http://www.alexedwards.net/blog/organising-database-access.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.108.119