beegoで実装した掲示板サービスの Go言語を読む|その1

Go言語でbeego のコード自動作成機能を試すまでマニュアルと雰囲気でなすがままに実装をしていて、フルスタックフレームワークでのコード自動生成という強力なサポートもありながら、Go 自体をあまり知らなくともできていました。
しかし細かい実装をするには自分の書いている言語、そして自動生成されたコード自体について最低限理解しておくべきというのは公然の事実と言えます。
ってことで自動生成したコードを中心に読んでいき、A Tour of Go のリンクなどを交えて解説していこうと思います。

なお筆者は A Tour of Go を一週した程度なのでそんなに高度な解説は期待しないでください。しかし初心者なりに初心者の目線でわかりやすく説明を行うことを心掛けていこうと思います。
教材は前回までやってきた go言語+beegoで掲示板を作る で作成した 掲示板風アプリ です。

/models/post.go

記念すべき読んでいく第一号はやはりローレイヤということで models の中かなと思い、/models/post.go を見ていこうと思います。

init 関数まで

https://github.com/haruhikonyan/beegotest/blob/master/haruch/models/post.go#L1-L20
まずはこの諸々宣言や go では特別な意味を持つ init() までを確認していこうと思います。

  • package

    package models

    Goのプログラムは、パッケージ( package )で構成されます。ということで、これは beego というフレームワークにおける models のファイル群であることを示しています。

  • import

    import (
    "errors"
    "fmt"
    "reflect"
    "strings"
    
    "github.com/astaxie/beego/orm"
    )

    この post.go で使用する必要なパッケージを import しています。
    一旦個々の import しているパッケージの説明は省略します。

  • type Post struct

    type Post struct {
    Id int64 orm:"auto"
    Title string orm:"size(128)"
    Body string orm:"type(longtext)"
    }

struct とは構造体のことで、個人的には java などの class みたいなもだと思っています。C言語にもあるみたいですね。(筆者はCわからない)
Post という struct を new すると、各 field に . つなぎでアクセスしたり、そこに値を代入できたりします。
Post というのは前回まで作っていた掲示板のDBへ保存する書き込みレコード1つ分に当たり、field として一意の Id と Title、Body を持ちます。
それぞれスペース区切りの二つ目が型を表しており、3つ目は orm が内部的に使う tag です。
タグについては struct の機能で この辺 参照していただければと思います。
ちょっと orm の公式を探しても tag の意味がまとまってるページが見つかりませんでしたが orm のドキュメントにはちょいちょい意味が書いてあったりします。

  • func init()

    func init() {
    orm.RegisterModel(new(Post))
    }

    init() は特別な関数で、 main() よりも先に実行され、パッケージの初期化などに使われます。
    ここでは import した github.com/astaxie/beego/ormRegisterModel() を呼び出していて、orm で扱う Post モデルの初期化をしています。

各種 CRUD

https://github.com/haruhikonyan/beegotest/blob/master/haruch/models/post.go#L22-L143
続いて実際にCRUDのためのコードを読んでいきます。これも自動生成されたコード達です。

  • AddPost

    // AddPost insert a new Post into database and returns
    // last inserted Id on success.
    func AddPost(m *Post) (id int64, err error) {
    o := orm.NewOrm()
    id, err = o.Insert(m)
    return
    }

    公式ドキュメントのそのままで orm の機能を使い、DBに書き込むメソッドです。
    任意の Post を引数 m として取り、orm.NewOrm()orm のインスタンスを o という変数に取り、Insert() へ引数として受け取った Post を渡してしてDBに保存しています。
    Insert() の返り値としては保存したレコードの id とエラーがあれば err 返し、それをそのまま AddPost の返り値として return しています。
    return としか書いていなくとも関数の宣言で (id int64, err error) と引数に名前がついているため、その名前の変数が自動的に return されます。これを Go におけるnaked returnといいます。
    また、Go の特徴の一つに返り値に複数を返せるというところがあります。

  • GetPostById

    // GetPostById retrieves Post by Id. Returns error if
    // Id doesn't exist
    func GetPostById(id int64) (v *Post, err error) {
    o := orm.NewOrm()
    v = &Post{Id: id}
    if err = o.QueryTable(new(Post)).Filter("Id", id).RelatedSel().One(v); err == nil {
        return v, nil
    }
    return nil, err
    }

    こちらは id を受け取って Post モデルの実体を DB から取得して返すメソッドです。
    o は先ほどと同じく orm のインスタンスを生成しており、 v = &Post{Id: id} にて上部で定義した Post struct を新たに定義し、そのフィールドの Id に 引数で受け取った id をセットしてして、変数 v へ &がついているのでアドレスを代入しています。
    struct のフィールドや定義についてはこの辺りを参照ください。
    また、ポインタについてはこの辺りを参照ください。
    o.QueryTable(new(Post)).Filter("Id", id).RelatedSel().One(v) この処理ですが順に、

  • o.QueryTable(new(Post))
    まず ormQueryTable()メソッドで一番上の例の2つ目のオブジェクト自体をテーブル名として使い、 QuerySeter オブジェクトを取得します。(QuerySeterってなんだ?)
    QuerySeter は任意の table にクエリを実行するためのものという認識です。

  • .Filter("Id", id).RelatedSel().One(v)
    Filter()Id を指定することで SQL でいう WHERE Id = 1 で絞り込みを行い、RelatedSel() で、何も引数を指定しないことで、関連テーブルをすべて Join します。最後に One(v) で、 v つまり Post レコードを一つ取得します。

  • if err = 式; err == nil {}
    if文の書き方ですね。errを取得し、もし err が nil であれば、エラーが無いということなので、取得した Post verr を nil で返し、もし err が存在したらエラーを retrun するという構造になっています。

まとめ

今回は一旦ここまでにします。
最初にも言った通り筆者は A Tour of Go を軽く一週した程度だけなのですが、フレームワークのドキュメントを見つつ、Goの静的型付け言語という特性でエディタの保管をうまく活用していけばそこまで身構える必要も無いという印象でした。
次回も引き続き /models/post.go を読んでいこうと思います。 GetAllPost() では interface も出てくるので今一度読み直そうかなと思っているところでもあります。