(訳しながら)つくって覚えるRevelフレームワーク - その2
前回にひきつづき、Golangどころかフレームワークのなんたるかを理解していない僕はRevelのドキュメントを和訳しつつ、理解を深めたいでござる。
それでは
今回はOverviewからでござる。
Overview
This section gives you a taste of various parts of the framework:
このセクションでは、Revelフレームワークの構成を軽く紹介していきます。
Routing - A simple declarative routing syntax. Type-safe reverse routing.
- 宣言型のシンプルな文法で表記されるルーティグです。タイプセーフでリバースルーティング可能です。
Controllers - Revel organizes endpoints into Controllers. They provide easy data binding and form validation.
- Revelはエンドポイントとコントローラを対応させます。コントローラーは簡単なデータの取得やフォーム値のバリデーションを行います。
Templates - Revel makes Go Templates simple to use at scale.
- テンプレートは、Go Templateをよりスケールしやすくシンプルにしたものです。
Interceptors - Register functionality to be called before or after actions. They can be activated per Controller.
- インターセプタは、actionに対するいわゆるbeforeやafterの機能を提供します。コントローラーごとに設定が可能です。
Filters - More general functionality can be implemented with Filters.
- フィルターでは、より包括的な機能を独自実装することができます。
Routing
Revel uses a declarative routing syntax. It collects all routes for an app in a single file, with a simple syntax for matching requests, extracting arguments from URIs, and specifying route-specific arguments to the action. Here's a commented sample...
Revelは宣言型のルーティング表記を採用しています。一つのファイルにすべてのルート定義を集め、シンプルな文字列一致で表記され、URIに含まれるパラメータを抽出し、各ルートで定義されている変数に格納します。以下のコメント付きサンプルを見てください、
# conf/routes # すべてのルーティング定義をこのファイルに表記 (マッチング優先順) GET /login Application.Login # シンプルなルート GET /hotels/ Hotels.Index # 最後のスラッシュが無くてもマッチ GET /hotels/:id Hotels.Show # hotels/以下を変数として取得 WS /hotels/:id/feed Hotels.Feed # ウェブソケットを提供(Σ(゚◇゚;)マジデッ!? POST /hotels/:id/:action Hotels.:action # 動的なアクション定義 GET /public/*filepath Static.Serve("public") # 静的リソースへのパス * /:controller/:action :controller.:action # すべてにマッチ、完全な動的ルーティング
リバースルーティングもタイプセーフな形で提供されます。以下に例を示します、
// hotelの情報をidによって表示 func (c Hotels) Show(id int) revel.Result { hotel := HotelById(id) return c.Render(hotel) } // hotel情報の更新を保存し、上記のShowにリダイレクトする func (c Hotels) Save(hotel Hotel) revel.Result { // (ここでバリデーションとか永続化とか) return c.Redirect(routes.Hotels.Show(hotel.Id)) }
Controllers
(´-`).。oO( このあたりから原文載せるのめんどくさくなってきた...
すべてのアクションはコントローラーのメソッドです。この特徴はいくつかのイケてる機能を提供します:
- Data binding
- RevelはURLやフォーム値を値や構造体にして、各メソッド(アクション)に受け渡します。(パラメータのマッピングから直接取るのももちろんおkです)
- Validation
- バリデーションやそれに伴うエラーを扱うヘルパーがあります。
- Flash
- 1リクエストのみで有効なクッキーです(エラーや成功メッセージなどに使えますね)。
- Session
- 暗号でひもづけられたクッキーです。
map[string]
(type string)の形で取得できます。
- 暗号でひもづけられたクッキーです。
- Results
以下サンプル
// app/controllers/app.go type Application struct { *revel.Controller } func (c Application) Register() revel.Result { title := "Register" return c.Render(title) } func (c Application) SaveUser(user models.User, verifyPassword string) revel.Result { c.Validation.Required(verifyPassword) c.Validation.Required(verifyPassword == user.Password) Message("Password does not match") user.Validate(c.Validation) if c.Validation.HasErrors() { c.Validation.Keep() c.FlashParams() return c.Redirect(routes.Application.Register()) } user.HashedPassword, _ = bcrypt.GenerateFromPassword( []byte(user.Password), bcrypt.DefaultCost) err := c.Txn.Insert(&user) if err != nil { panic(err) } c.Session["user"] = user.Username c.Flash.Success("Welcome, " + user.Name) return c.Redirect(routes.Hotels.Index()) }
Templates
RevelはGoTemplate
をウェブアプリ用に統合していると考えて結構です。以下のコードは、前述のRegister action
に対応するテンプレートがレンダーされる例です。
特徴
- Revelはactionで使われいる変数名をtemplateの中から見つけ出します
field
はバリデーションのエラーやパラメータの値に名前をつけてマップするヘルパーです。ヘルパー関数はコードの中でいつでも使用可能です。title
変数はやや特殊で、あたかもレンダーパラメータで渡されたもののように参照できます。(例ではheader.html
の中で使われています)
{{/* app/views/Application/Register.html */}} {{/* ちなみにここはコメント */}} {{template "header.html" .}} <h1>Register:</h1> <form action="/register" method="POST"> {{with $field := field "user.Username" .}} <p class="{{$field.ErrorClass}}"> <strong>Username:</strong> <input type="text" name="{{$field.Name}}" size="16" value="{{$field.Flash}}"> * <span class="error">{{$field.Error}}</span> </p> {{end}} {{/* other fields */}} <p class="buttons"> <input type="submit" value="Register"> <a href="/">Cancel</a> </p> </form> {{template "footer.html" .}}
Interceptors
インターセプタは、リクエストの前後処理(もしくはレスポンスの失敗処理)を定義するコントローラのメソッドです。特定のコントローラを他のコントローラに持たせることで、共通のインターセプタを複数のコントローラで共有することができます。
たとえば、データベースモジュールを使用するときは初期化で接続を確保することになると思いますが、これを共通のハンドラで定義・利用できるようになります。また、db.Transactional
型を持たせることで、sql.Txn
フィールドが使用できるようになり、加えて、トランザクションを開始しコミット(および失敗時のロールバック)できるインターセプタも使用できるようになります。
インターセプタの振る舞いは以下の例(負の値のエラーハンドリング)を見れば分かるかと思います。
// github.com/robfig/revel/modules/db/app/db.go var Db *sql.DB func Init() { // 設定を読み... Driver, _ = revel.Config.String("db.driver") Spec, _ = revel.Config.String("db.spec") // 接続の開始する Db, _ = sql.Open(Driver, Spec) } // `Transactional`はコントローラにトランザクション管理を提供します type Transactional struct { *revel.Controller Txn *sql.Tx } func (c *Transactional) Begin() revel.Result { c.Txn, _ = Db.Begin() return nil } func (c *Transactional) Commit() revel.Result { _ = c.Txn.Commit() c.Txn = nil return nil } func (c *Transactional) Rollback() revel.Result { _ = c.Txn.Rollback() c.Txn = nil return nil } // 前処理、後処理、失敗処理にそれぞれ、 // `Transactional`型の // Begin, Commit, Rollbackを割り当て(Intercept)ている func init() { revel.InterceptMethod((*Transactional).Begin, revel.BEFORE) revel.InterceptMethod((*Transactional).Commit, revel.AFTER) revel.InterceptMethod((*Transactional).Rollback, revel.PANIC) }
で、このインターセプタがアプリケーションのコントローラの中でいかに動くかというと、
// Bookings structはrevel.Controllerであり、 // db.Transactionalであり // user.Loginも持っている // (これがダックタイピングというやつか?) type Bookings struct { *revel.Controller db.Transactional // Adds .Txn user.Login // Adds .User } // BookingsコントローラーのShowFirstBookingメソッド... // の前にTransactional.Beginが呼ばれ、 // の中で失敗すればTransaction.Rollbackが呼ばれ、 // の後にTransactional.Commitが呼ばれている func (c Bookings) ShowFirstBooking() revel.Result { row := c.Txn.QueryRow(` select id, hotel_id, user_id, price, nights from Booking where UserId = ? limit 1`, c.User.Id) ... return c.Render(booking) }
Filters
Filters
は、アプリケーションのミドルウェアです。これらは特定の名前を持った関数に他なりません。
type Filter func(c *Controller, filterChain []Filter)
interceptor
などの複雑なビルドイン機能も、filter
として実装されています。
// github.com/robfig/revel/intercept.go var InterceptorFilter = func(c *Controller, fc []Filter) { defer invokeInterceptors(FINALLY, c) defer func() { if err := recover(); err != nil { invokeInterceptors(PANIC, c) panic(err) } }() // Beforeインターセプタを呼び出し、resultを得次第、returnする invokeInterceptors(BEFORE, c) if c.Result != nil { return } fc[0](c, fc[1:]) invokeInterceptors(AFTER, c) }
Revelがデフォルトで備えているFilters
のセットはいずれも、開発者によってオーバーライドすることが可能です。Filters
があることによって、フレームワークの意図した特定の部分だけ意図通りカスタマイズすることが可能です。
// github.com/robfig/revel/filter.go // Filters is the default set of global filters. // It may be set by the application on initialization. var Filters = []Filter{ PanicFilter, // Recover from panics and display an error page instead. RouterFilter, // Use the routing table to select the right Action FilterConfiguringFilter, // A hook for adding or removing per-Action filters. ParamsFilter, // Parse parameters into Controller.Params. SessionFilter, // Restore and write the session cookie. FlashFilter, // Restore and write the flash cookie. ValidationFilter, // Restore kept validation errors and save new ones from cookie. I18nFilter, // Resolve the requested language InterceptorFilter, // Run interceptors around the action. ActionInvoker, // Invoke the action. }
ほぼすべてのフレームワーク機能はこのFilters
として実装されています。また、これらはすべてフレームワークの設定として開発者にパブリックな状態で存在しています。Revelフレームワークが理解しやすく簡潔なモジュール構成なのは、このためです。
その証拠に、主要なサーバ機能が如何にシンプルかお見せしましょう
// github.com/robfig/revel/server.go func handleInternal(w http.ResponseWriter, r *http.Request, ws *websocket.Conn) { var ( req = NewRequest(r) resp = NewResponse(w) c = NewController(req, resp) ) req.Websocket = ws Filters[0](c, Filters[1:]) if c.Result != nil { c.Result.Apply(req, resp) } }
とりあえず
今回はここまで。次回からはいよいよ、Introductionをやって、アプリケーションをつくっていきたいでござる。
しかし、
英語の勉強にもなるし、すごくソフトウェアの勉強になるますまる