GoのORM、entを使ってGremlinServerに繋げる

egonelbre/gophersによるGitHubからの画像

要約

  • entとは
  • entの利点
  • GremlinServerに繋げる

はじめに

ORM使っていますか? ここ1年、GraphDB(AWS Neptune)を使うことがあり、GraphDBのクエリーやEntityをどのように扱うかを調べていたことがあります。その時は有力なGraphDBに対応したORMを見つけられなかったので、自前でGremlinというデータベース操作言語のQueryBuilderやORMを作っていましたが、Gremlinの仕様をすべてカバーするのは大変でした。 あとから、GraphDBに対応したORMがあることを知りました。 今回は、entをご紹介したいと思います。

entとは

Simple, yet powerful entity framework for Go, that makes it easy to build and maintain applications with large data-models.

  • Schema As Code - model any database schema as Go objects.
  • Easily Traverse Any Graph - run queries, aggregations and traverse any graph structure easily.
  • Statically Typed And Explicit API - 100% statically typed and explicit API using code generation.
  • Multi Storage Driver - supports MySQL, PostgreSQL, SQLite and Gremlin.
  • Extendable - simple to extend and customize using Go templates.

出典: ent - GitHub

特徴的なのは、Graphとしてデータを扱える点だとおもいます。 Storage DriverでMySQL, PostgreSQL, SQLite and Gremlin.に対応しています。

※Gremlin対応しているGraphDBはありますが、互換性については注意が必要です。また、2021-03-15時点ではExperimentsな機能となっています。

また、静的型付けであることも他のORMと異なる点だと思います。 go generateで自動生成されたコードを使うことになります。

entの利点

  • 静的型付け・明示的

これまでは、gormを多用してきました。 gormが動的型付けで暗黙的なのに対して、entは静的型付けで明示的という点が対照的だと思いました。 gormはinterface{}型を多用し、構造体のタグによってリレーションシップの解決を行ったりしますが、interface{}型と構造体のタグは動的に解決されるため、例えば、構造体のタグにTypoがあってもコンパイルはできてしまいます。 entでも構造体のタグを指定する部分がないわけではないですが、基本的にはDBに対する指示をメソッドチェーンで追加していく形になりますので、Typoによるバグは減らせるかなと思いました。

  • コード自動生成・自動補完

コードが自動生成されることによりエディターの補完が効くのも良いと思いました。

ORM自体のデバッグをする際にもReflectを多用しているgormより自動生成されたコードのほうが、デバッグしやすいようにも思います。

  • コード削減

DDDで設計しているとテーブルごとにRepositoryを作っているのですが、entだと自動生成されるので、これをUsecaseで直接使っています。Repositoryの実装・テストがなくなるのでコード削減できます。DDD的には特定のライブラリに依存するので賛否があると思いますが。。

これらの特徴に関しては、好みだと思いますが、Typingの恩恵を受けられるほうが、安全なコーディングができて個人的には好みです。

GremlinServerに繋げる

Gremlinのリファレンス実装である、Apache TinkerPopのGremlinServerとつなげて動かしてみようと思います。 すべてのソースコードは、こちらに配置しています。 CloudShellでローカル環境を汚さずに試すことが可能です。

Open in Cloud Shell

前提条件

  • docker
  • docker-compose
  • Go 1.16

schemaをつくる

entgo.ioを参考にent cliを利用してSchemaの雛形を作ります。

mkdir workspace/projectcd workspace/projectgo mod initgo get entgo.io/ent/cmd/entgo run entgo.io/ent/cmd/ent init User
  • ent/schema/user.go

下記の通りに編集します。

package schemaimport (    "entgo.io/ent"    "entgo.io/ent/schema/edge"    "entgo.io/ent/schema/field")type User struct {    ent.Schema}func (User) Fields() []ent.Field {    return []ent.Field{        field.String("id"),        field.String("name"),    }}func (User) Edges() []ent.Edge {    return []ent.Edge{        edge.To("friends", User.Type),    }}
  • ent/generate.go

storagegremlinに指定して、gremlinで接続するためのコードをGenerateします。

AWS Neptuneでは、idの型がUUIDなので、互換のためidtypestringに指定しています。

package ent//go:generate go install entgo.io/ent/cmd/ent//go:generate ent generate ./schema --storage gremlin --idtype string

generateする

go generate ./...

entディレクトリ配下に自動生成されたコードが出力されます。

GremlinServerを起動する

GremlinServerのConfigの簡単な変更内容の説明です。 docker-compose.ymlやconfはGitHubから入手できます。

  • conf/server/my-gremlin-server.yaml

channelizerをWsAndHttpChannelizerに変更。

entはWebsocketではなくHttp(REST)で接続しますので変更が必要でした。

  • conf/server/my-tinkergraph.properties

idManagerをUUIDに変更。

AWS NeptuneのIDの型がUUIDなので互換を保つためです。

GremlinServerを起動します。

docker-compose builddocker-compose up -d

実行

  • main.go

これはUserVertex(name: bob)とUserVertex(name: alice)を作ります。

FriendEdgeとしてalice - friends -> bobのように関係します。

package mainimport (    "context"    "fmt"    "log"    "github.com/cloudandbuild/blog-article-resources/content/blog/article-3/ent")func main() {    ctx := context.Background()    client, err := ent.Open("gremlin", "http://localhost:8182")    if err != nil {        log.Fatal(err)    }    defer client.Close()    bob, err := CreateUser(ctx, client, "bob")    if err != nil {        log.Fatalf("failed CreateUser err: %v", err)    }    _, err = CreateUser(ctx, client, "alice", bob)    if err != nil {        log.Fatalf("failed CreateUser err: %v", err)    }    users, err := client.User.Query().All(ctx)    if err != nil {        log.Fatalf("failed User.Query().All err: %v", err)    }    log.Println(users)}func CreateUser(ctx context.Context, client *ent.Client, name string, friends ...*ent.User) (*ent.User, error) {    u, err := client.User.        Create().        SetName(name).        Save(ctx)    if err != nil {        return nil, fmt.Errorf("failed creating user: %w", err)    }    var ids []string    for _, friend := range friends {        ids = append(ids, friend.ID)    }    if len(ids) > 0 {        if _, err := u.Update().AddFriendIDs(ids...).Save(ctx); err != nil {            return nil, fmt.Errorf("failed updating user: %w", err)        }    }    return u, nil}

実行してみます。UserのQuery結果が表示されると思います。

go run main.go 

GremlinConsole

sqlでpsqlなどCLIでConsoleが提供されているようにGremlinでもConsoleを使ってDBにアクセスすることができます。

gremlin-consoleを開く

docker-compose exec  gremlin-console /bin/bash -c bin/gremlin.sh

gremlin-serverに接続する。

:remote connect tinkerpop.server /opt/gremlin/conf/remote.yaml
:remote console

queryを実行する。

gremlin> g.V().valueMap(true)
==>{id=3c48f9e7-2ad0-403c-9398-96101b6d2cbf, label=user, name=[bob]}
==>{id=cd3900cf-79fe-4bde-8a55-8c840ce9d8e3, label=user, name=[alice]}
gremlin> g.E()
==>e[e7ca2294-a11d-4ffc-8a98-22ef1196df47][cd3900cf-79fe-4bde-8a55-8c840ce9d8e3-user_friends->3c48f9e7-2ad0-403c-9398-96101b6d2cbf]

aliceの友達は、bobですね。

まとめ

今回は、entを使ってGremlinServerに繋げることを紹介しました。 entやGremlinは利用して知見がたまってきたので、その他にも色々とご紹介できればと思います。 Graphの可視化や具体的な実用例を別途記事にできればと思います。


クラウドアンドビルド株式会社

Cloud and Build, Inc.

クラウドアンドビルド株式会社は、Google Cloud パートナー企業です。

GCP 導入から開発・コンサルティングまでワンストップでお任せください。

お問い合わせ

デジタル変革を開始しますか?

お気軽にお問い合わせください。

contact_at_cloudandbuild.jp
所在地

〒104-0061

東京都中央区銀座2-14-8

ML20191021

Designed by Freepik
© 2020 Cloud and Build, Inc.