
The purpose of this article is to tell about the
Go programming language (Golang) to those developers who are looking in the direction of this language but have not yet decided to take up its study. The story will be conducted on the example of a real application, which is a RESTful API web service.
My task was to develop a backend for a mobile service. The essence of the service is quite simple. Mobile application that shows posts of users located near the current location. Users can post their comments on posts, which, in turn, can also be commented. It turns out a kind of geo-forum.
I have long wanted to try Go language for any serious projects. The choice was obvious, since this language is best suited for such tasks.
')
The main advantages of the Go language:
- Simple and clear syntax . This makes writing code a pleasant experience.
- Static typing . Allows you to avoid mistakes made by inattention, simplifies reading and understanding of the code, makes the code unambiguous.
- Speed ββand compilation . Go speeds are ten times faster than scripting languages, with less memory consumption. At the same time, the compilation is almost instantaneous. The whole project is compiled into one binary file, without dependencies. As the saying goes, "just add water." And you do not have to worry about memory, there is a garbage collector.
- Departure from OOP . There are no classes in the language, but there are data structures with methods. Inheritance is replaced by an embedding mechanism. There are interfaces that do not need to be explicitly implemented, but only enough to implement interface methods.
- Parallelism . Parallel computing in the language is done simply, elegantly and without a headache. Gorutin (something like streams) are lightweight, consume little memory.
- Rich standard library . The language has everything you need for web development and not only. The number of third-party libraries is constantly growing. In addition, it is possible to use the C and C ++ libraries.
- The ability to write in a functional style . The language has closures and anonymous functions. Functions are first-order objects, they can be passed as arguments and used as data types.
- Authoritative founding fathers and a strong community . Rob Pike, Ken Thompson, Robert Grizmer stood at the origins. Now more than 300 contributors have a language. The language has a strong community and is constantly evolving.
- Open source
- Charming talisman
All these, and many other features allow you to select the language among others. This is a worthy candidate for the study, besides, mastering the language is quite simple.
So back to our task. Although the language does not impose restrictions on the structure of the project, I decided to organize this application according to the MVC model. True View is implemented on the client side. In my case, it was AngularJS, in the future - a native mobile application. Here I will only talk about the API on the service side.
The structure of the project is as follows:
/project/ /conf/ errors.go settings.go /controllers/ posts.go users.go /models/ posts.go users.go /utils/ helpers.go loctalk.go
The program in Go is divided into packages (package), which is indicated at the beginning of each file. The package name must correspond to the directory in which the files included in the package are located. Also, there should be a main main package with a main () function. I have it in the root file of the loctalk.go application. Thus, I got 5 packages: conf, controllers, models, utils, main.
I will give incomplete content files, but only the minimum necessary for understanding.
The 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 think there is nothing to comment on. The init () function is called in each packet before the main () call. They may be several in different files.
Package main.
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)) } }
At the very top is the name of the package. Next comes the list of imported packages. We will use the
Martini package. It adds an easy layer for quick and easy creation of web applications. Notice how this package is imported. You need to specify the path to the repository from where it was taken. And to get it, just go to the console to get go github.com/go-martini/martini
Next, we create an instance of Martini, configure it and run it. Pay attention to the ": =" sign. This is an abbreviated syntax, it means: create a variable of the appropriate type and initialize it. For example, by writing a: = "hello", we will create a variable of type string and assign it the string "hello".
The variable m in our case is of the type * ClassicMartini, and that is what martini.Classic () returns. * means a pointer, i.e. it is not the value itself that is transmitted, but only a pointer to it. In the m.Use () method, we pass a handler function. This Middleware allows Martini to do certain actions on each request. In this case, we define the Content-Type for each request. The m.Map () method allows us to bind our structure and then use it in controllers if necessary (mechanism of dependency injection). In this case, I created a wrapper for encoding the data structure in json format.
Immediately we create an internal function Auth, which checks the user's authorization. It can be inserted into our routes and it will be called before the controller is called. These things are possible thanks to Martini. Using the standard library, the code would have been a little different.
Take a look at the errors file of the 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", ""}
The language supports returning multiple values. Instead of the try-catch mechanism, a technique is often used when the second argument returns an error. And, if available, it is processed. There is a built-in error type, which is the interface:
type error interface { Error() string }
Thus, to implement this interface, it is enough to have the Error () string method. I created my own type for ApiError errors, which is more specific for my tasks, but compatible with the built-in type error.
Note the - type ApiError struct. This is the definition of the structure, the data model that you will use constantly in your work. It consists of fields of certain types (I hope you have noticed that the data type is written after the variable name). By the way, other structures can be fields, inheriting all methods and fields. In single quotes `` tags are specified. They do not have to be specified. In this case, they are used by the encoding / json package to specify the name in the json output (the minus sign β-β generally excludes the field from the output).
Note that the structure fields are capitalized. This means that they have scope outside the package. If you write them in capital letters, they will not be exported, but will be available only within the package. The same applies to functions and methods. Here is such a simple encapsulation mechanism.
Moving on. Definition of func (e * ApiError) Error () string means nothing more than a method of this structure. The variable e is a pointer to a structure, a kind of self / this. Accordingly, by calling the .Error () method on the structure, we get its Message field.
Next, we define the preset errors and fill in their fields. Fields of the form http.StatusBadRequest are int values ββin the http package for standard response codes, a kind of alias. We use the abbreviated syntax for declaring the & ApiError {} structure with initialization. Otherwise one could write this:
MyError := new(ApiError) MyError.Code = 110
The symbol & means to get a pointer to this structure. The new () operator also returns a pointer, not a value. In the beginning there is a little confusion with pointers, but, over time, you get used to it.
Let us turn to our models. I will give a trimmed version of the post model:
package models import ( "labix.org/v2/mgo/bson" "loctalk/conf" "loctalk/utils" "time" "unicode/utf8" "log" )
Here we use a great driver for MongoDb - mgo to save data. For convenience, I created a small wrapper over api mgo - utils.NewDbSession. The logic of working with data: first we create an object in the internal structure of the language, and then, using the method of this structure, save it to the database.
Note that in these methods we use our type of error conf.ApiError everywhere. We convert standard errors to ours using conf.NewApiError (err). Also, the defer statement is important. It is executed at the very end of the method execution. In this case, closes the connection with the database.
Well, it remains to look at the controller, which processes requests and displays json in response.
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 URL of the requested post from the URL, create a new instance of our structure, and call the LoadById (id) method on it to load data from the database and fill this structure. Which we output the HTTP response, having previously converted to json by our mu.Marshal (post) method.
Pay attention to the function signature:
func GetPostById(mu *utils.MarshUnmarsh, params martini.Params) (int, []byte)
Input parameters are provided to us by Martini using the dependency injection mechanism. And we return two parameters (int, [] byte) - a number (response status) and an array of bytes.
So, we have disassembled the main components and approaches, using which, you can make an effective RESTful API interface in a short time. Hope the article was helpful and will inspire some of you to learn the wonderful language of Go. I'm sure the future will be his.
For study I can recommend a good book in Russian β
Programming in the Go language β Mark Summerfield. And, of course, practice more.
UPD:
Tour Go in Russian.