GolangのWebアプリケーションフレームワークBeegoを試す

2014年 7月10日 Golang Beego

Node.jsとかPhalconは速いんですけど、結局ネイティブでどこまで処理するかってところで、アプリケーションが大きくなってスクリプトで処理する部分が増えると、それなりに遅くなってしまいます。
そこで、Golangです。すべてネイティブなので、アプリケーションが大きくなっても速いままです!
Java VMでもいいんですが、アプリケーションサーバーを立ち上げてListenするだけで数百メガのメモリを消費してしまうので、クソアプリを気軽にサーバーに置けないのが悩ましいです。
GolangはListenするだけなら数メガで済みます!
HTTPサーバーやテンプレートのライブラリがデフォルトで組み込まれていて、簡単にWebアプリを作れそうですが、Webアプリ用のフレームワークがあったので、試しつつ掲示板を作ってみます。

martiniRevelが有名どころらしいですが、ORマッパーまでサポートしているBeegoというフレームワークがあり、v1.3のStableらしいので、これを試してみます。



準備

Golangはすでにインストールしていることにします。
まずはBeegoをインストールします。

$ go get github.com/astaxie/beego

Beeツールもインストールします。
これでbeeコマンドが使えるようになります。

$ go get github.com/beego/bee

プロジェクトの作成

GOPATHのsrcに移動してプロジェクトを作成します。

$ cd $GOPATH/src
$ bee new beego-bbs

作成したディレクトリに移動して実行します。

$ cd beego-bbs
$ bee run

MySQLを使う

まずはMySQLドライバをインストールします。

$ go get github.com/go-sql-driver/mysql

/conf/app.confに接続情報を書きます。

mysqluser = "root"
mysqlpass = "password"
mysqlurls = "127.0.0.1”
mysqldb = "beego-bbs"
main.goのinit関数でMySQLに接続します。
go
// main.go
package main
 
import (
    "fmt"
    "time"
    "strings"
    "github.com/astaxie/beego"
    "github.com/astaxie/beego/orm"
    _ "github.com/go-sql-driver/mysql"
    _ "beego-bbs/routers"
)
 
func init() {
    user := beego.AppConfig.String("mysqluser")
    pass := beego.AppConfig.String("mysqlpass")
    host := beego.AppConfig.String("mysqlurls")
    db := beego.AppConfig.String("mysqldb")
    datasource := fmt.Sprintf("%s:%s@tcp(%s:3306)/%s?charset=utf8", user, pass, host, db)
    orm.RegisterDataBase("default", "mysql", datasource, 30)
 
    err := orm.RunSyncdb("default", false, true)
    if err != nil {
        panic(err)
    }
 
    beego.AddFuncMap("dateformatJst", func(in time.Time) string {
        in = in.Add(time.Duration(9) * time.Hour)
        return in.Format("2006-01-02 15:04:05")
    })
    
    beego.AddFuncMap("nl2br", func(in string) string {
        return strings.Replace(in, "\n", "<br>", -1)
    })
}
 
 
func main() {
    beego.Run()
}

これでMySQLに接続できるようになりました!

Modelの作成

beegoではModelを/modelsの下に置くようです。
明示的なフィールドを持ったEntityに振る舞いを書くと見通しが悪くなるので、modelsの下にpostパッケージを切り、EntityとRepositoryを作成します。
Playのドキュメントではこういうのはアンチパターンと言ってますが、モデルオブジェクトって何でしょう?Service層もモデルですよね?

Entity

go
// models/post/post.go
package post
 
import (
    "time"
)
 
type Post struct {
    Id int64
    Content string `orm:"type(text)"`
    Created time.Time
    Modified time.Time
}

次のRepositoryのinit関数で実装しますが、
beegoではorm.RegisterModel(new(Post))とするとテーブルが無かった場合に作成してくれるようで、stringはvarchar(255)になります。

また、それぞれのメンバにパラメータを設定することで、DBのデータ型を指定できるようです。
Contentはtextにしたいので、orm:”type(text)”のパラメータを設定しました。

Repository

RepositoryではとりあえずDBに対するCRUD操作を実装します。

go
// models/post/post_repository.go
package post
 
import (
    "time"
    "github.com/astaxie/beego/orm"
)
 
type PostRepository struct {
}
 
func init() {
    orm.RegisterModel(new(Post))
}
 
func (this *PostRepository) FindAll() ([]*Post, error) {
    o := orm.NewOrm()
    var posts []*Post
    _, err := o.QueryTable("post").OrderBy("-Created").All(&posts)
    return posts, err
}
 
func (this *PostRepository) FindById(id int64) (*Post, error) {
    o := orm.NewOrm()
    post := Post{Id: id}
    err := o.Read(&post)
    return &post, err
}
 
func (this *PostRepository) Save(p *Post) error {
    var err error
    o := orm.NewOrm()
    now := time.Now()
    
    if p.Id != 0 {
        err = o.Read(p)
        if err != nil {
            p.Modified = now
            _, err = o.Update(p)
        }
    } else {
        p.Created = now
        p.Modified = now
        _, err = o.Insert(p)
    }
    
    return err
}
 
func (this *PostRepository) Delete(p *Post) error {
    o := orm.NewOrm()
    _, err := o.Delete(p)
    return err
}

Controllerの作成

Controllerはcontrollersディレクトリに置きます。
PostControllerの構造体ではbeego.Controllerを匿名フィールドで宣言して継承します。

go
// controllers/post_controller.go
package controllers
 
import (
    "beego-bbs/models/post"
    "github.com/astaxie/beego"
)
 
type PostController struct {
    beego.Controller
    repository post.PostRepository
}
 
func (this *PostController) Index() {
    posts, _ := this.repository.FindAll()
    this.Data["posts"] = posts
    this.Layout = "layouts/application.html"
    this.TplNames = "post/index.html"
}
 
func (this *PostController) Create() {
    post := post.Post{
        Content: this.GetString("content"),
    }
 
    err := this.repository.Save(&post)
    flash := beego.NewFlash()
    if err != nil {
        flash.Error("The post could not be saved. Please, try again.")
    } else {
        flash.Notice("The post has been saved.")
    }
    flash.Store(&this.Controller)
    
    this.Redirect("/", 302)
}

Routing

beegoのrooterはいろいろ機能があるようで、Controllerとの棲み分けがいまいちよくわからないです。
とりあえずは一般的なフレームワークのrooterとして使います。
トップページは投稿一覧にします。
AutoRouterメソッドを使うとコントローラのアクションを自動的にルーティングしてくれるようです。
今回はIndexとCreateアクションを実装したので、それぞれ/post/index、/post/createにルーティングされます。

go
// routers/router.go
package routers
 
import (
    "beego-bbs/controllers"
    "github.com/astaxie/beego"
)
 
func init() {
    beego.Router("/", &controllers.PostController{}, "*:Index")
    beego.AutoRouter(&controllers.PostController{})
}

Viewのレイアウト

beegoではレイアウトファイルを定義でき、その中で{{.LayoutContent}}とすると各Viewが読み込まれます。
/views/layouts/application.htmlを作成します。
レイアウトファイルの指定はControllerでやってるthis.Layout = “layouts/application.html”です。

html
<!DOCTYPE html>
 
<html>
    <head>
        <title>{{.title}}</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <link rel="stylesheet" type="text/css" href="/static/css/bootstrap.min.css">
        <script src="/static/js/jquery-1.11.1.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="/static/js/bootstrap.min.js" type="text/javascript" charset="utf-8"></script>
        {{range .moreStyles}}
        <link rel="stylesheet" type="text/css" href="/static/{{.}}">
        {{end}}
        {{range .moreScripts}}
        <script src="/public/{{.}}" type="text/javascript" charset="utf-8"></script>
        {{end}}
    </head>
    <body>
        <div class="navbar navbar-default navbar-fixed-top">
            <div class="container">
                <div class="navbar-header">
                    <a class="navbar-brand" href="/">Beego BBS</a>
                </div>
            </div>
        </div>
 
        <div class="container">
            {{template "flash.html" .}}
            <div class="content">
                {{.LayoutContent}}
            </div>
        </div>
    </body>
</html>

Bootstrapのcssなどを設定しました。
staticファイルはデフォルトでは/static以下に置くようです。

View

createdとかのtime.TimeはUTCで保存されるのですが、ロードすると時刻はそのままに+0900 JSTが付加される謎な動作をするので、テンプレート関数で補正しています。(main.goのinitに実装)

html
<!— views/post/index.html —>
{{range $post := .posts}}
    <h4>名無しさん@Beego {{$post.Created|dateformatJst}}</h4>
    {{$post.Content|htmlquote|nl2br|str2html}}
    <hr />
{{end}}
 
<div class="col-md-6">
    <form action="/post/create" method="post" accept-charset="utf-8">
        <div class="form-group">
            <label>Content</label>
            <textarea name="content" class="form-control" required="required">
            </textarea>
        </div>
        <input class="btn btn-primary" type="submit" value="Post”/>
    </form>
</div>

いけましたね!

ソースコードをGithubに置きました。

※参考URL
http://beego.me/
http://d.hatena.ne.jp/yuheiomori0718/20140102/1388664225



前へ 次へ