Sample Web Application Using Beego and Mgo
Introduction
I am very excited about the Beego web framework. I wanted to share with you how I use the framework to build real world web sites and web services. Here is a picture of the sample website the post is going to showcase:
The sample web application:
• Implements a traditional grid view of data calling into MongoDB
• Provides a modal dialog box to view details using a partial view to generate the HTML
• Implements a web service that returns a JSON document
• Takes configuration parameters from the environment using envconfig (https://github.com/kelseyhightower/envconfig)
• Implements tests via goconvey (http://smartystreets.github.io/goconvey/)
• Leverages my logging (https://github.com/goinggo/tracelog) package
The code for the sample can be found in the GoingGo repository up on Github:
https://github.com/goinggo/beego-mgo (https://github.com/goinggo/beego-mgo)
You can bring the code down and run it. It uses a public MongoDB database I created at MongoLab. You will need git (https://help.github.com/articles/set-up-git) and bazaar (http://bazaar.canonical.com/en/) installed on your system before running go get.
go get github.com/goinggo/beego-mgo
To quickly run or test the web application, use the scripts located in the zscripts folder.
Web Application Code Structure
Let's take a look at the project structure and the different folders that exist:
controllers
Entry point for each Web call. Controllers process the requests.
localize
Provides localization support for different languages and cultures
models
Models are data structures used by the business and service layers
routes
Mappings between URL's and the controller code that handles those calls.
services
Services provide primitive functions for the different services that exist. These could be database or web calls that perform a specific function.
static
Resource files such as scripts, stylesheets and images
test
Tests that can be run through the go test tool.
utilities
Code that supports the web application. Boilerplate and abstraction layers for accessing the database and handling panics.
views
Code related to rendering views
zscripts
Support scripts to help make it easier to build, run and test the web application
Controllers, Models and Services
These layers make up the bulk of the code that implement the web application. The idea behind the framework is to hide and abstract as much boilerplate code as possible. This is accomplished by implementing a base controller package and a base services package.
Base Controller Package
The base controller package uses composition to abstract default controller behavior required by all controllers:
type (
BaseController struct {
beego.Controller
services.Service
}
)
func (this *BaseController) Prepare() {
this.UserId = this.GetString("userId")
if this.UserId == "" {
this.UserId = this.GetString(":userId")
}
err := this.Service.Prepare()
if err != nil {
this.ServeError(err)
return
}
}
func (this *BaseController) Finish() {
defer func() {
if this.MongoSession != nil {
mongo.CloseSession(this.UserId, this.MongoSession)
this.MongoSession = nil
}
}()
}
A new type called BaseController is declared with the Beego Controller type and the base Service type embedded directly. This composes the fields and methods of these types directly into the BaseController type and makes them directly accessible through an object of the BaseController type.
Beego Controller framework will execute the Prepare and Finish functions on any Controller object that implements these interfaces. The Prepare function is executed prior to the Controller function being called. These functions will belong to every Controller type by default, allowing this boilerplate code to be implemented once.
Services Package
The Service package maintains state and implements boilerplate code required by all services:
type (
// Services contains common properties
Service struct {
MongoSession *mgo.Session
UserId string
}
)
func (this *Service) Prepare() (err error) {
this.MongoSession, err = mongo.CopyMonotonicSession(this.UserId)
if err != nil {
return err
}
return err
}
func (this *Service) Finish() (err error) {
defer helper.CatchPanic(&err, this.UserId, "Service.Finish")
if this.MongoSession != nil {
mongo.CloseSession(this.UserId, this.MongoSession)
this.MongoSession = nil
}
return err
}
func (this *Service) DBAction(databaseName string, collectionName string, mongoCall mongo.MongoCall) (err error) {
return mongo.Execute(this.UserId, this.MongoSession, databaseName, collectionName, mongoCall)
}
In the Service type, the Mongo session and the id of the user is maintained. This version of Prepare handles creating a MongoDB session for use. Finish closes the session which releases the underlying connection back into the pool. The function DBAction provides an abstraction layer for running MongoDB commands and queries.
Buoy Service
This Buoy Service package implements the calls to MongoDB. Let's look at the FindStation function that is called by the controller methods:
func FindStation(service *services.Service, stationId string) (buoyStation *buoyModels.BuoyStation, err error) {
defer helper.CatchPanic(&err, service.UserId, "FindStation")
queryMap := bson.M{"station_id": stationId}
buoyStation = &buoyModels.BuoyStation{}
err = service.DBAction(Config.Database, "buoy_stations",
func(collection *mgo.Collection) error {
return collection.Find(queryMap).One(buoyStation)
})
if err != nil {
if strings.Contains(err.Error(), "not found") == false {
return buoyStation, err
}
err = nil
}
return buoyStation, err
}
The FindStation function prepares the query and then using the DBAction function to execute the query against MongoDB.
Implementing Web Calls
With the base types, boilerplate code and service functionality in place, we can now implement the web calls.
Buoy Controller
The BuoyController type is composed solely from the BaseController. By composing the BuoyController in this way, it immediately satisfies the Prepare and Finish interfaces and contains all the fields of a Beego Controller.
The controller functions are bound to routes. The routes specify the urls to the different web calls that the application supports. In our sample application we have three routes:
beego.Router("/", &controllers.BuoyController{}, "get:Index")
beego.Router("/buoy/retrievestation", &controllers.BuoyController{}, "post:RetrieveStation")
beego.Router("/buoy/station/:stationId", &controllers.BuoyController{}, "get,post:RetrieveStationJson")
The route specifies a url path, an instance of the controller used to handle the call and the name of the method from the controller to use. A prefix of which verb is accepted can be specified as well.
The Index controller method is used to deliver the initial html to the browser. This will include the javascript, style sheets and anything else needed to get the web application going:
func (this *BuoyController) Index() {
region := "Gulf Of Mexico"
buoyStations, err := buoyService.FindRegion(&this.Service, region)
if err != nil {
this.ServeError(err)
return
}
this.Data["Stations"] = buoyStations
this.Layout = "shared/basic-layout.html"
this.TplNames = "buoy/content.html"
this.LayoutSections = map[string]string{}
this.LayoutSections["PageHead"] = "buoy/page-head.html"
this.LayoutSections["Header"] = "shared/header.html"
this.LayoutSections["Modal"] = "shared/modal.html"
}
A call is made into the service layer to retrieve the list of regions. Then the slice of stations are passed into the view system. Since this is setting up the initial view of the application, layouts and the template are specified. When the controller method returns, the beego framework will generate the html for the response and deliver it to the browser.