Facebook の ent を使って簡単なウェブアプリケーションを作る
はじめに
この記事は Facebook が開発している ORM、ent を使ってどれだけ簡単にアプリケーションを作れるかをチュートリアル的に解説するものです。エンティティを操作する一通りの機能が揃っており、扱いやすいライブラリだと思います。
ent の特徴は以下の通り
- コードをスキーマとして扱う
- モデルをGoのオブジェクトとして扱います
- 簡単なグラフの横断
- クエリの実行、集合、そしてグラフ構造を簡単に横断します
- 静的型で明確な API
- 100% 静的な型でコード生成を使った明確な API を提供します
- 複数のストレージドライバ
- MySQL, PostgreSQL, SQLite と Gremlin をサポートします
- 拡張性
- Go のテンプレートを使って簡単に拡張できます
この記事では ent を使って1行掲示板を作ってみたいと思います。
前準備
ent を使うには始めに CLI の entc をインストールする必要があります。以下の手順でインストールします。
$ go get github.com/facebook/ent/cmd/entc
予め空の Go のプロジェクトディレクトリを作っておきます。今回は1行掲示板を作るので以下の様に実行しました。
$ go mod init github.com/mattn/entgo-bbs
まずは初期化
まずスキーマを作ります。掲示板の1行をエンティティとして扱うのでまず Entry
を作りましょう。
$ entc init Entry
以下の様に幾らかファイルが生成されます。
├── ent
│ ├── generate.go
│ └── schema
│ └── entry.go
├── go.mod
└── go.sum
ent/schema/entry.go
が生成されたスキーマ(コード)です。ファイルの中身は以下の様になっています。
package schema
import "github.com/facebook/ent"
// Entry holds the schema definition for the Entry entity.
type Entry struct {
ent.Schema
}
// Fields of the Entry.
func (Entry) Fields() []ent.Field {
return nil
}
// Edges of the Entry.
func (Entry) Edges() []ent.Edge {
return nil
}
フィールドを追加
デフォルトでは Entry
のフィールドは空になっています。コンテンツを示す content
と、作成日を示す created_at
というフィールドを追加します。
package schema
import (
"time"
"github.com/facebook/ent"
"github.com/facebook/ent/schema/field"
)
// Entry holds the schema definition for the Entry entity.
type Entry struct {
ent.Schema
}
// Fields of the Entry.
func (Entry) Fields() []ent.Field {
return []ent.Field{
field.String("content").
Default(""),
field.Time("created_at").
Default(func() time.Time {
return time.Now()
}),
}
}
// Edges of the Entry.
func (Entry) Edges() []ent.Edge {
return nil
}
モデルの生成
ここまで出来たら go generate ./ent
を実行します。以下の様にファイルが生成されます。
├── ent
│ ├── client.go
│ ├── config.go
│ ├── context.go
│ ├── ent.go
│ ├── entry
│ │ ├── entry.go
│ │ └── where.go
│ ├── entry.go
│ ├── entry_create.go
│ ├── entry_delete.go
│ ├── entry_query.go
│ ├── entry_update.go
│ ├── enttest
│ │ └── enttest.go
│ ├── generate.go
│ ├── hook
│ │ └── hook.go
│ ├── migrate
│ │ ├── migrate.go
│ │ └── schema.go
│ ├── mutation.go
│ ├── predicate
│ │ └── predicate.go
│ ├── privacy
│ │ └── privacy.go
│ ├── runtime
│ │ └── runtime.go
│ ├── runtime.go
│ ├── schema
│ │ └── entry.go
│ └── tx.go
├── go.mod
└── go.sum
沢山ファイルが生成されましたが、殆どのファイルは開発者が見る必要も変更する必要もありません。これで Entry
を扱う為のモデルが生成されています。
アプリケーションの実装
ではアプリケーションを作りましょう。まずはマイグレーションする為のコードです。
package main
import (
"context"
"log"
"github.com/mattn/entgo-bbs/ent"
_ "github.com/mattn/go-sqlite3"
)
func main() {
client, err := ent.Open("sqlite3", "file:bbs.sqlite?mode=memory&cache=shared&_fk=1")
if err != nil {
log.Fatalf("failed opening connection to sqlite: %v", err)
}
defer client.Close()
// Run the auto migration tool.
if err := client.Schema.Create(context.Background()); err != nil {
log.Fatalf("failed creating schema resources: %v", err)
}
}
このコードを書いておくと、以降 ent/schema/entry.go
を修正したとしてもテーブルを良しなに扱ってくれる様になります。ではウェブアプリケーションを作りましょう。僕は echo が好きなので echo を使います。以下の様なハンドラを追加します。
e := echo.New()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "")
})
e.POST("/add", func(c echo.Context) error {
return c.String(http.StatusOK, "")
})
e.Logger.Fatal(e.Start(":8989"))
エンティティの操作
ここでようやく Entry
を操作する必要が出てきます。Entry
の操作は client
を介して行います。選択は Query
を使います。Entry
の created_at
をソートキーとして昇順、件数 10 件を得ます。
eq := client.Entry.Query().Order(ent.Asc(entry.FieldCreatedAt)).Limit(10)
entries := eq.AllX(context.Background())
Entry
の追加は Create
を使います。
e := client.Entry.Create()
e.SetContent("zenn 面白いやん?")
e.Save(context.Background())
あとは良しなに
これを後はアプリ側に組み込み、entries
はテンプレートエンジンにそのまま渡し、add
のハンドラでは Entry の追加とリダイレクトを行えば簡単な1行掲示板の完成です。テンプレートエンジンは Slim テンプレートエンジンの Go 言語版を使いました。
package main
import (
"context"
"log"
"net/http"
"github.com/labstack/echo"
"github.com/mattn/entgo-bbs/ent"
"github.com/mattn/entgo-bbs/ent/entry"
"github.com/mattn/go-slim"
_ "github.com/mattn/go-sqlite3"
)
type Payload struct {
Content string `json:"content"`
}
func main() {
client, err := ent.Open("sqlite3", "file:bbs.sqlite?cache=shared&_fk=1")
if err != nil {
log.Fatalf("failed opening connection to sqlite: %v", err)
}
defer client.Close()
// Run the auto migration tool.
if err := client.Schema.Create(context.Background()); err != nil {
log.Fatalf("failed creating schema resources: %v", err)
}
t, err := slim.ParseFile("template/index.slim")
if err != nil {
log.Fatal(err)
}
e := echo.New()
e.GET("/", func(c echo.Context) error {
eq := client.Entry.Query().Order(ent.Desc(entry.FieldCreatedAt)).Limit(10)
entries := eq.AllX(context.Background())
c.Request().Header.Set("content-type", "text/html")
return t.Execute(c.Response(), map[string]interface{}{
"entries": entries,
})
})
e.POST("/add", func(c echo.Context) error {
e := client.Entry.Create()
e.SetContent(c.FormValue("content"))
if _, err := e.Save(context.Background()); err != nil {
log.Println(err.Error())
return c.String(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
return c.Redirect(http.StatusFound, "/")
})
e.Logger.Fatal(e.Start(":8989"))
}
テンプレートは以下の通り。
doctype 5
html lang="ja"
head
meta charset="UTF-8"
title
body
form method=post action=add
input type=text name=content
input type=submit
ul
- for x in entries
li = x.Content
起動して1行掲示板になっているのを確認して下さい。
作った物は以下のリポジトリに置いてあります。ご自由に改変して遊んで下さい。
おわりに
Facebook が開発している ORM ライブラリ ent を使って簡単なアプリケーションを実装してみました。ご覧いただいた様にわりと分かりやすいインタフェースになっていると思います。同じ手順を踏めば、おおよそ皆さんも簡単にアプリケーションが実装できると思います。ぜひ作って遊んでみて下さい。
Discussion