Nghiên cứu REST API trong Revel framework

Golang là một ngôn ngữ tuyệt vời đối với việc xây dựng các ứng dựng web.Chúng ta có thể xây dựng một ứng dụng web Golang hoàn toàn từ đầu, nhưng cũng giống như các ngôn ngữ khác, ta có thể bắt đầu nhanh hơn với một framework được xây dựng sẵn. Nôỉ bật trong số những framework của Go là beego và revel.Trong bài viết này, tôi xin giới thiệu về framework revel, mà cụ thể hơn là rest API trong revel-đây là framework theo tìm hiểu của tôi thì khá nhiều sự yêu thích từ phía các developer vì một số đặc điểm bên dưới.

Những ưu điểm của Revel

  • Hot-compile/reload code: Revel hỗ trợ hot-compile/reload(không cần chạy restart server để chạy code mới), về phần này, revel còn có một ưu điểm nổi bật hơn so với một Go framework khác mình có đề cập ở trên là Beego.Đó là, ở Beego, file được tự động compile mỗi khi có thay đổi, còn Revel thì đơị đến khi nào nhận được request mới thực hiện compile code, điều này giúp tăng hiệu năng và tiết kiệm tài nguyên hệ thống hơn.
  • Độ hoàn thiện: revel cung cấp khá nhiều dịch vụ hỗ trợ cho người sử dụng và theo mình tìm hiểu thì những người đã tiếp xúc nhiều với các framework Go thì đánh giá khá cao điều này ở Revel, bên cạnh đó là việc tutorial được cung cấp đầy đủ và rõ ràng phục vụ cho người dùng.
  • Hiệu năng cao và dễ maintain: Revel được xây dựng dựa trên Go HTTP server, nó có thể xử lí được gấp từ 3 đến 10 lần nhiều requests so với Rails trong cùng một thời gian tải.Có thể thấy rõ hơn qua thống kế sau. http://www.techempower.com/benchmarks/#section=data-r11
  • Linh hoạt trong việc sử dụng thư việc hỗ trợ: khi làm Rails chúng ta có được sự hỗ trợ rất lớn từ các Gem được xây dựng sẵn dành cho rails.Điều này cũng gần như tương tự đối với Revel khi chúng ta hoàn toàn có thể include và sử dụng các thư việc được xây dựng để hỗ trợ cho Golang cho Revel.Đây là một việc hết sức tiện lợi và linh hoạt cho người sử dụng.

Cài Đặt Go và sample app

-Phần cài đặt có thể rất dễ dàng làm được qua hướng dẫn từ trang tutorial của revel ở đường link sau: http://revel.github.io/tutorial/gettingstarted.html -Bên cạnh đó, bạn hãy cài đặt sample app của revel theo hướng dẫn của link sau: http://revel.github.io/samples/index.html, đây là một sample rất chi tiết và hữu ích để ta có thể làm quen với revel qua 6 app buil sẵn:

  • Booking: một ứng dụng quản lí data đặt phòng khách sạn và quản lí user, nó rất hữu ích cho việc tìm hiểu về cách kết nối với database của revel qua thư viện cài sẵn.
  • Chat: một ứng dụng về phòng chat sử dụng cơ chế long-polling(comet), kèm theo đó là websocket, đây cũng là một ứng dụng rất hay để ta tìm hiểu về websocket qua revel.Để có thể hình dung dễ hơn, bạn có thể đọc qua bài viết này: tìm hiểu websocket trong revel qua demo app
  • Validation: một ví dụ về validate trong revel.
  • Upload: upload một hoặc nhiều file cùng lúc.
  • Twitter OAuth: một ví dụ về authenticate, posting lên tài khoản Twitter thông qua OAuth (khá quen thuộc với những người dùng Rails).
  • FaceBook OAuth2: một ứng dụng về hiển thị thông tin người dùng Facebook qua OAuth2.

Xây dựng API

Ở phần dưới đây, mình sẽ đưa ra hướng dẫn để build một API về quản lý issues-một demo mà mình đã từng làm qua khi học code php. Chúng ta sẽ tiến hành các bước sau để xây dựng một API hoàn chỉnh:

  • Khởi tạo app và định nghĩa Rest routes
  • Định nghĩa model
  • Add validation logic
  • Setup gorp và database
  • Xử lí Json request với ApiController

1.Khởi tạo app và định nghĩa Rest routes

-Để tạo mới ứng dụng, ta dùng lệnh:

revel new revel-sample

-Trước tiên, chúng ta cần định nghĩa route của ứng dụng trong file <app_root>/conf/routes . Phần route trong file này chúng ta sẽ định nghĩa theo một syntax như sau:

REQUSEST_NAME           PATH        CONTROLLER.ACTION
HTTP_REQUEST             /          App.#{action_name}

-Routes hoàn chỉnh của mình sẽ như sau:

GET     /                                       App.Index

GET  /issues/regist Issues.Regist
#POST /issues Issues.Create
#POST /issues/:id Issues.Update

GET  /api/user/:id      ApiUser.Show
GET  /api/user          ApiUser.List
POST /api/user          ApiUser.Create

2.Định nghĩa model

-Về Model: chúng ta có thể thấy rằng Revel không hỗ trợ ORM vì vậy chúng ta không thấy thư mục model trong Revel, chúng ta ta sẽ phải sử dụng một thư viện để hỗ trợ kết nối tới database.Ở đây mình sử dụng gorp, đây là một package khá phổ biến đối với Revel. -Trước tiên ta tạo một thư mục có tên là models trong thư mục <app-root>/app .Tạo một file có tên là user_data.go với nội dung như sau:

package models

import (
	"errors"
	"regexp"

	"github.com/revel/revel"
)

type UserData struct {
	ID       int    `json:"id"`
	MailAddr string `json:"mailaddr"`
	Password string `json:"password"`
	Created  int64  `json:"-"`
	Updated  int64  `json:"-"`
}

trong đó UserData sẽ là bảng ứng với tên user_data trong cơ sở dữ liệu, mỗi hàng ở trong sẽ là tên column trong cơ sở dữ liệu kèm theo kiểu dữ liệu của nó.

3.Add validation logic

Revel đã cung cấp cho ta một hệ thống validation khá ổn, vì vậy chúng ta có thể sử dụng nó.Đối với model user_data,việc validate địa chỉ email và password là cần thiết nên mình sẽ tiến hành validate như sau:

func (userdata *UserData) Validate() error {
	var v revel.Validation

	v.Match(userdata.MailAddr, regexp.MustCompile(`([a-zA-Z0-9])[email protected]`))
	if v.HasErrors() {
		return errors.New("mail address is validate error")
	}

	v.Check(
		userdata.Password,
		revel.Required{},
		revel.MinSize{4},
	)
	if v.HasErrors() {
		return errors.New("password is validate error")
	}

	return nil
}

Như đã khai báo ở phần tạo model, mình có import thêm 2 thư viện mặc định của Revel đó là errorsregexp dùng để hỗ trợ việc tạo errors mesages và tạo regexp để validate email.

4.Setup gorp và kết nối database

-Ta tạo một file gorp.go trong models với nội dung sau:

package models

import (
	"database/sql"

	"github.com/go-gorp/gorp"
	_ "github.com/go-sql-driver/mysql"
	"github.com/revel/modules/db/app"
	r "github.com/revel/revel"
)

var (
	Dbm *gorp.DbMap
	Txn *gorp.Transaction
)

func InitDB() {
	db.Init()
	Dbm = &gorp.DbMap{Db: db.Db, Dialect: gorp.MySQLDialect{"InnoDB", "UTF8"}}
	Dbm.TraceOn("[gorp]", r.INFO)
	Dbm.AddTableWithName(UserData{}, "user").SetKeys(true, "ID")
}

type GorpController struct {
	*r.Controller
}

func (c *GorpController) Begin() r.Result {
	txn, err := Dbm.Begin()
	if err != nil {
		panic(err)
	}
	Txn = txn
	return nil
}

func (c *GorpController) Commit() r.Result {
	if Txn == nil {
		return nil
	}
	if err := Txn.Commit(); err != nil && err != sql.ErrTxDone {
		panic(err)
	}
	Txn = nil
	return nil
}

func (c *GorpController) Rollback() r.Result {
	if Txn == nil {
		return nil
	}
	if err := Txn.Rollback(); err != nil && err != sql.ErrTxDone {
		panic(err)
	}
	Txn = nil
	return nil
}

-Lưu ý: Đối với một số package chúng ta sử dụng lần đầu tiên mà chưa có sẵn ở local, ta cần dùng cú pháp sau để lấy source của package về local:

go get package_url

-Ở trên mình có import thêm 2 package là gorp và mysql.Đây là 2 gói quan trọng trong việc kết nối app của chúng ta tới database.Ngoài 2 package này ra, ta có thể dùng gorm hay xorm để thay thế cho gorp và thay cho mysql có thể là sqlite3 hoặc mongodb. -Nội dung của file gorp.go sẽ khai báo cấu hình ban đầu với gorp và một số action quen thuộc liên quan tới database như commit, rollback.

-sau đó tạo file init.go với nội dung sau:

package models

import "github.com/revel/revel"

func init() {
	revel.OnAppStart(InitDB)
	revel.InterceptMethod((*GorpController).Begin, revel.BEFORE)
	revel.InterceptMethod((*GorpController).Commit, revel.AFTER)
	revel.InterceptMethod((*GorpController).Rollback, revel.FINALLY)
}

-Để app của chúng ta có thể hoàn toàn kết nối được với database,ta cần config file conf/app.conf như sau:

[dev]
db.import = github.com/go-sql-driver/mysql
db.driver = mysql
db.spec = db_user:[email protected](localhost:3306)/table_name

config này nhìn có vẻ rất đơn giản nhưng khi mới bắt đầu làm quen với Revel, mình đã mất khá nhiều thời gian để tìm hiểu,bởi vì rất khó để tìm kiếm được giải pháp chính xác từ google khi cộng đồng của Revel khá nhỏ, và một số config từ các bài hướng dẫn mình đọc được thì lại là từ những version trước không sử dụng được.Tóm lại là khi sử dụng một package mới thì nên vào trang doccument của nó để đọc, điều này sẽ hiểu quả hơn nhiều so với việc tìm kiếm giải pháp qua mạng.

5.Tạo api controller

-Trong thư mục controller ta tạo một thư mục có tên api , đây sẽ là nơi quản lí api của app.

-tạo file api.go

package controllers

import "github.com/revel/revel"

type ApiController struct {
	*revel.Controller
	callBack string
}

type Response struct {
	Code    int         `json:"code"`
	Results interface{} `json:"results,omitempty"`
}

type ErrorResponse struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

/* api response code */
const (
	OK int = iota
	WARN_NOT_FOUND
	ERR_VALIDATE
	ERR_FATAL
)

func (c *ApiController) Response(s interface{}) revel.Result {

	if c.callBack != "" {
		return c.RenderJsonP(c.callBack, s)
	} else {
		return c.RenderJson(s)
	}
}

-file này là file định nghĩa việc quản lý response.

-và user.go

package controllers

import (
	"encoding/json"
	"errors"
	"io"
	"io/ioutil"
	"github.com/revel/revel"
)

type ApiUser struct {
	ApiController
}

func (c *ApiUser) Create() revel.Result {
	user := &models.User{}
	c.BindParams(user)

	user.Validate(c.App.Validation)
	if c.App.Validation.HasErrors() {
		return c.App.RenderJson(&ErrorResponse{ERR_VALIDATE, ErrorMessage(ERR_VALIDATE)})
	}

	err := c.Txn.Insert(user)
	if err != nil {
		panic(err)
	}

	return c.App.RenderJson(&Response{OK, user})
}

func BindJsonParams(i io.Reader, s interface{}) error {
	bytes, err := ioutil.ReadAll(i)
	if err != nil {
		return errors.New("can't read request data.")
	}

	if len(bytes) == 0 {
		return errors.New("data is nil")
	}

	return json.Unmarshal(bytes, s)
}

-Ở đây, mình ví dụ về functin create user, trong đó có sử dụng ApiController được định nghĩa ở trên.

-Như vậy là ta đã hoàn thành các bước cần thiết để tạo một API hoàn chỉnh.

Tài liệu tham khảo

-https://rclayton.silvrback.com/revel-gorp-and-mysql -https://revel.github.io/