The aim of this article is to tell about a programming language Go (Golang) to those developers, who are interested in it, but haven’t risked studying it. The story will be told on the basis of a real sentence that represents RESTful API web-service.
I had a task to develop a backend to the mobile phone. The service is quite simple. It’s a mobile application, which shows posts of the users that are near the current location. Users can leave their comments to the posts and they can be commented as well. It’s like an original geo-forum.
I wanted to use the Golang in some serious projects. The choice was obvious. This language is extremely suitable for such tasks.
Main Golang advantages:
- Simple and clear syntax. It makes code writing a pleasant task.
- Static typing. Allows avoiding mistakes, made inadvertently. Simplifies the code reading and understanding. Makes the code unambiguous.
- Speed and compilation. Go performs much better than other scripting languages, while consuming less memory. The entire project is compiled into one binary file, without dependencies. And don’t worry about memory management since there’s a garbage collector.
- Step away from the OOP. There are no classes in the language, but there are data structures with methods. Inheritance is changed by an embedding mechanism. There are interfaces that don’t need to be completely implemented. You just need to implement the interface methods.
- Concurrency. Concurrent computing made easy, smart and without a headache. Go routines (something like threads) are light and don’t consume much memory.
- A well-stocked standard library. The language has everything necessary for the web-development and even more. The amount of exterior libraries grows all the time. Besides, you can use C & C++ libraries.
- Possibility to write in functional style. There are closures and anonymous functions in the language. Functions are the first-order objects. They can be transferred as arguments and used ad data types.
- Trustworthy Founding Fathers and a strong community. Rob Pike, Ken Thompson and Robert Griesemer stood at the origins of the language. Now it has a strong community and constantly develops.
- Open Source
- Charming mascot
These and other features allow marking the language out of the others. It’s a worthy candidate to be studied, plus it’s quite easy to cope with.
So, let’s get back to our task. Though the language doesn’t apply restrictions to the project, I have decided to manage this application according to MVC pattern. View is implemented on the client’s side. In my case it was AngularJS, native mobile application in future. Here I will tell only about the API on the server’s side.
The project structure is the following:
/project/
/conf/
errors.go
settings.go
/controllers/
posts.go
users.go
/models/
posts.go
users.go
/utils/
helpers.go
loctalk.go
The program is divided into packages in Go. This is indicated at the beginning of each file. The package name should correspond to directory which contains the files from the package. There also should be a main package with the main() function. It’s located in the boot file of the application loctalk.go. Thus, I have 5 packages: conf, controllers, models, utils, main.
I will not give the full file content, but the min necessary for understanding.
Conf package contains constants and site settings.
package conf
import (
"os"
)
const (
SITE_NAME string = "LocTalk"
DEFAULT_LIMIT int = 10
MAX_LIMIT int = 1000
MAX_POST_CHARS int = 1000
)
func init() {
mode := os.Getenv("MARTINI_ENV")
switch mode {
case "production":
SiteUrl = "http://loctalk.net"
AbsolutePath = "/path/to/project/"
default:
SiteUrl = "http://127.0.0.1"
AbsolutePath = "/path/to/project/"
}
}
I guess there’s nothing to comment on. Init() function is called in each package before the
main() call. There can be few of them in different files.
The main package.
package main
import (
"github.com/go-martini/martini"
"net/http"
"loctalk/conf"
"loctalk/controllers"
"loctalk/models"
"loctalk/utils"
)
func main() {
m := martini.Classic()
m.Use(func(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
})
m.Map(new(utils.MarshUnmarsh))
Auth := func(mu *utils.MarshUnmarsh, req *http.Request, rw http.ResponseWriter) {
reqUserId := req.Header.Get("X-Auth-User")
reqToken := req.Header.Get("X-Auth-Token")
if !models.CheckToken(reqUserId, reqToken) {
rw.WriteHeader(http.StatusUnauthorized)
rw.Write(mu.Marshal(conf.ErrUserAccessDenied))
}
}
// ROUTES
m.Get("/", controllers.Home)
// users
m.Get("/api/v1/users", controllers.GetUsers)
m.Get("/api/v1/users/:id", controllers.GetUserById)
m.Post("/api/v1/users", controllers.CreateUser)
// …
// posts
m.Get("/api/v1/posts", controllers.GetRootPosts)
m.Get("/api/v1/posts/:id", controllers.GetPostById)
m.Post("/api/v1/posts", Auth, controllers.CreatePost)
// ...
m.Run()
}
At the very top the package name is defined. Then goes the list of imported packages. We will use Martini package. It adds a light layer for a quick and convenient creation of web-applications. Please pay attention to how this package is imported. You should show to the repository where it was taken from. In order to get it, it’s enough to type the command go get github.com/go-martini/martini in the console.
Then we will create an exemplar Martini, adapt it and run it. Pay you attention to « := » sign. It’s a reduced syntax, which means: to create a variable of the corresponding type and initialize it. For example, having written a := «hello», we will create a variable of string type and assign «hello» line to it.
The m variable in our case has *ClassicMartini type. That’s what returns martini. * means the pointer, i.e. not the value itself is transferred, but the pointer to it. We pass the handler function to the m.Use() method. This Middleware allows Martini to do certain actions over each query. m.Map() method allows to attach our structure and use it later in controllers when needed (dependency injection mechanism). In the given case I created a cover for data structures coding to json format.
Here we also create an internal function Auth, which checks the user’s authorization. It can be inserted into our routes and called before the controller’s call. These things are possible thanks to Martini. Having used a standard library the code would have been a bit different.
Lets’ look at errors.go file of conf package.
package conf
import (
"fmt"
"net/http"
)
type ApiError struct {
Code int `json:"errorCode"`
HttpCode int `json:"-"`
Message string `json:"errorMsg"`
Info string `json:"errorInfo"`
}
func (e *ApiError) Error() string {
return e.Message
}
func NewApiError(err error) *ApiError {
return &ApiError{0, http.StatusInternalServerError, err.Error(), ""}
}
var ErrUserPassEmpty = &ApiError{110, http.StatusBadRequest, "Password is empty", ""}
var ErrUserNotFound = &ApiError{123, http.StatusNotFound, "User not found", ""}
var ErrUserIdEmpty = &ApiError{130, http.StatusBadRequest, "Empty User Id", ""}
var ErrUserIdWrong = &ApiError{131, http.StatusBadRequest, "Wrong User Id", ""}
// and so on.
The Language supports return of several values. Instead of try-catch mechanism another approach is often used, where the mistake is returned by the second argument. When there’s a mistake, it’s being handled. There is a built-in error type, which represents the interface:
type error interface {
Error() string
}
Thus, in order to implement this interface, it’s enough to have an Error() string method. I created my own type for ApiError mistakes. It’s more specific for my tasks, but is more compatible with the built-in error type.
Pay attention to type ApiError struct. It’s a definition of the structure, data pattern, which you will constantly use in your work. It consists of fields of definite types (hope you have noticed that data type is written after the variable name). By the way, other structures can function as fields, inheriting all methods and fields. Tags are indicated by single quotes ``. They are not necessary to be indicated. In this case they are used by encoding/json package for name indication in json output (minus sign «-» excludes the field from the output)
Please note that structure fields are written capitalized. It means that they have the scope beyond the package. Written with capital letter, they will not be exported and will be available within the package only. The same refers to functions and methods. That’s the simple mechanism of encapsulation.
Moving forward. The definition of func (e *ApiError) Error() string means the method of the given structure. The e variable is the pointer to the structure, a sort of self/this. So calling .Error() method we will get its Message field.
Then we define predetermined mistakes and complete their fields. Fields of http.StatusBadRequest type are values of int type in http package for standard response codes, a sort of alias. We use the reduced syntax of structure &ApiError{} declaration with the bootstrap. It could be written in a different way:
MyError := new(ApiError)
MyError.Code = 110
// …
& digit means to get a pointer to the given structure. New() operator also returns the pointer, not the value. At first you may be confused with pointers, but you will get used to them with time.
Let’s move on to our patterns. I will provide a reduced version of the posts pattern:
package models
import (
"labix.org/v2/mgo/bson"
"loctalk/conf"
"loctalk/utils"
"time"
"unicode/utf8"
"log"
)
// GeoJSON format
type Geo struct {
Type string `json:"-"`
Coordinates [2]float64 `json:"coordinates"`
}
type Post struct {
Id bson.ObjectId `json:"id" bson:"_id,omitempty"`
UserId bson.ObjectId `json:"userId"`
UserName string `json:"userName"`
ThumbUrl string `json:"thumbUrl"`
ParentId bson.ObjectId `json:"parentId,omitempty" bson:",omitempty"`
Enabled bool `json:"-"`
Body string `json:"body"`
Geo Geo `json:"geo"`
Date time.Time `json:"date" bson:",omitempty"`
}
func NewPost() *Post {
return new(Post)
}
func (p *Post) LoadById(id string) *conf.ApiError {
if !bson.IsObjectIdHex(id) {
return conf.ErrPostIdWrong
}
session := utils.NewDbSession()
defer session.Close()
c := session.Col("posts")
err := c.Find(bson.M{"_id": bson.ObjectIdHex(id), "enabled": true}).One(p)
if p.Id == "" {
return conf.ErrPostNotFound
}
if err != nil {
return conf.NewApiError(err)
}
return nil
}
func (p *Post) Create() (id string, err *conf.ApiError) {
// validation
switch {
case p.UserId == "":
err = conf.ErrUserIdEmpty
case p.Body == "":
err = conf.ErrPostBodyEmpty
case utf8.RuneCountInString(p.Body) > conf.MAX_POST_CHARS:
err = conf.ErrPostMaxSize
case p.Geo.Coordinates[0] == 0.0 || p.Geo.Coordinates[1] == 0.0:
err = conf.ErrPostLocationEmpty
}
if err != nil {
return
}
p.Id = bson.NewObjectId()
p.Geo.Type = "Point"
p.Enabled = true
p.Date = time.Now()
session := utils.NewDbSession()
defer session.Close()
c := session.Col("posts")
errDb := c.Insert(p)
if errDb != nil {
return "", conf.NewApiError(errDb)
}
return p.Id.Hex(), nil
}
func (p *Post) Update() *conf.ApiError {
session := utils.NewDbSession()
defer session.Close()
c := session.Col("posts")
err := c.UpdateId(p.Id, p)
if err != nil {
return conf.NewApiError(err)
}
return nil
}
func (p *Post) Disable() *conf.ApiError {
session := utils.NewDbSession()
defer session.Close()
p.Enabled = false
c := session.Col("posts")
err := c.UpdateId(p.Id, p)
if err != nil {
return conf.NewApiError(err)
}
return nil
}
// …
We use a great driver for MongoDb here — mgo, in order to save the data. For convenience, I created a small cover over api mgo — utils.NewDbSession. Logical operation with data: at first we create an object in the internal language structure, and then, with the hrlp of this structure method, save it to the database.
Please note, that we use conf.ApiError type of mistake in these methods. Standard mistakes we convert into ours with the help of conf.NewApiError(err). Defer operator is also important. He is performed in the very end the method execution. In this case, closes connection with the database.
And now we can look at the controller that processes queries and derives json to the respond.
package controllers
import (
"encoding/json"
"fmt"
"github.com/go-martini/martini"
"labix.org/v2/mgo/bson"
"loctalk/conf"
"loctalk/models"
"loctalk/utils"
"net/http"
)
func GetPostById(mu *utils.MarshUnmarsh, params martini.Params) (int, []byte) {
id := params["id"]
post := models.NewPost()
err := post.LoadById(id)
if err != nil {
return err.HttpCode, mu.Marshal(err)
}
return http.StatusOK, mu.Marshal(post)
}
// ...
Here we get the requested post id from the URL, create a new structure exemplar and call
LoadById(id) method at it for data download from the database and filling the structure. It’s derived to HTTP respond, previously having converted it to json with the help of mu.Marshal(post) method.
Pay attention to the function signature:
func GetPostById(mu *utils.MarshUnmarsh, params martini.Params) (int, []byte)
Input parameters are provided by Martini via dependency injection mechanism. We return two parameters — number (respond status) and byte massive (int, []byte).
Thus, we have discussed the basic components and methods, using which you can make an efficient RESTful API interface within a short time. I hope that this article is useful and will inspire some of you to study this wonderful language. I am sure that the future belongs to it.
I can recommend a good book for studying: “Programming in Go” by Mark Summerfield.
And, of course, practice a lot.
[original source]
2 comments
Upload image