メインコンテンツへスキップする

· 1 分で読む

Migrating to a new ORM is not an easy process, and the transition cost can be prohibitive to many organizations. As much as we developers are enamoured by "Shiny New Things", the truth is that we rarely get a chance to work on a truly "green-field" project. Most of our careers, we operate in contexts where many technical and business constraints (a.k.a legacy systems) dictate and limit our options for moving forward. Developers of new technologies that want to succeed must offer interoperability capability and integration paths to help organizations seamlessly transition to a new way of solving an existing problem.

To help lower the cost of transitioning to Ent (or simply experimenting with it), we have started the "Schema Import Initiative" to help support many use cases for generating Ent schemas from external resources. The centrepiece of this effort is the schemast package (source code, docs) which enables developers to easily write programs that generate and manipulate Ent schemas. Using this package, developers can program in a high-level API, relieving them from worrying about code parsing and AST manipulations.

Protobuf Import Support

The first project to use this new API, is protoc-gen-ent, a protoc plugin to generate Ent schemas from .proto files (docs). Organizations that have existing schemas defined in Protobuf can use this tool to generate Ent code automatically. For example, taking a simple message definition:

syntax = "proto3";

package entpb;

option go_package = "github.com/yourorg/project/ent/proto/entpb";

message User {
string name = 1;
string email_address = 2;
}

And setting the ent.schema.gen option to true:

syntax = "proto3";

package entpb;

+import "options/opts.proto";

option go_package = "github.com/yourorg/project/ent/proto/entpb";

message User {
+ option (ent.schema).gen = true; // <-- tell protoc-gen-ent you want to generate a schema from this message
string name = 1;
string email_address = 2;
}

Developers can invoke the standard protoc (protobuf compiler) command to use this plugin:

protoc -I=proto/ --ent_out=. --ent_opt=schemadir=./schema proto/entpb/user.proto

To generate Ent schemas from these definitions:

package schema

import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
)

type User struct {
ent.Schema
}

func (User) Fields() []ent.Field {
return []ent.Field{field.String("name"), field.String("email_address")}
}
func (User) Edges() []ent.Edge {
return nil
}

To start using protoc-gen-ent today, and read about all of the different configuration options, head over to the documentation!

Join the Schema Import Initiative

Do you have schemas defined elsewhere that you would like to automatically import in to Ent? With the schemast package, it is easier than ever to write the tool that you need to do that. Not sure how to start? Want to collaborate with the community in planning and building out your idea? Reach out to our great community via our Discord server, Slack channel or start a discussion on GitHub!

For more Ent news and updates:

· 1 分で読む

ent + gRPC

前書き

言語的に中立なフォーマットで定義されたエンティティ スキーマを持つことは、ソフトウェア エンジニアリング組織の規模が大きくなるにつれ、多くの利点があります。 そのために、多くの組織ではプロトコルバッファインターフェース記述言語 (IDL) として使用しています。 また、Google社内のStubbyをモデルにしたProtobufベースのRPCフレームワークであるgRPCは、その効率性とコード生成能力の高さから人気を集めています。

IDLであるgRPCは、データアクセスレイヤーの実装に関する具体的なガイドラインを規定していないため、その実装方法は様々です。 Entは、あらゆるGoアプリケーションのデータアクセスレイヤーを構築するための自然な候補であり、この2つの技術を統合することには大きな可能性があります。

本日は、entユーザーのためにProtobufとgRPCのサポートを追加するための実験版entproto、Goパッケージ、およびコマンドラインツールを発表します。 entprotoを使えば、開発者は完全に動作するCRUD gRPCサーバを数分で立ち上げることができます。 今回の記事では、その方法を具体的にご紹介します。

セットアップ

このチュートリアルの最終版はGitHubで公開されていますので、そちらでフォローしたい方はクローンを作成してください。

まずは、プロジェクトの新しいGoモジュールを初期化しましょう。

mkdir ent-grpc-example
cd ent-grpc-example
go mod init ent-grpc-example

次に、go runを使ってentコードジェネレータを起動し、スキーマを初期化します。

go run -mod=mod entgo.io/ent/cmd/ent new User

これで私たちのディレクトリは次のようになります。

.
├── ent
│   ├── generate.go
│   └── schema
│   └── user.go
├── go.mod
└── go.sum

次に、entprotoパッケージをプロジェクトに追加してみましょう。

go get -u entgo.io/contrib/entproto

次に、Userエンティティのスキーマを定義します。 ent/schema/user.goを開き、以下のように編集します。

package schema

import (
"entgo.io/ent"
"entgo.io/ent/schema"
)

// Userは、Userエンティティのスキーマ定義を保持しています。
type User struct {
ent.Schema
}

// Userのフィールド
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
Unique(),
field.String("email_address").
Unique(),
}
}

このステップでは、Userエンティティに2つのユニークなフィールドを追加しました。nameemail_addressです。 ent.Schemaは単なるスキーマの定義であり、そこから使用可能なプロダクションコードを作成するには、Entのコード生成ツールを実行する必要があります。 以下を実行してください。

go generate ./...

ここで、スキーマ定義からたくさんの新しいファイルが作成されていることに注目してください。

├── ent
│   ├── client.go
│   ├── config.go
// .... many more
│   ├── user
│   ├── user.go
│   ├── user_create.go
│   ├── user_delete.go
│   ├── user_query.go
│   └── user_update.go
├── go.mod
└── go.sum

この時点で、データベースへの接続を開き、マイグレーションを実行してusersテーブルを作成し、データの読み書きを開始することができます。 この点についてはチュートリアルのセットアップで説明していますので、ここでは本題に入り、スキーマからProtobuf定義とgRPCサーバーを生成する方法を学びましょう。

entproto でGo Protobufsを生成する

entとProtobufのスキーマは同一ではないので、entprotoがProtobufの定義 (protobufの用語では「メッセージ」と呼ばれます) を生成する方法を正確に把握できるように、スキーマにいくつかのアノテーションを提供する必要があります。

まず必要なのは、entproto.Message()アノテーションを追加することです。 これはProtobufスキーマ生成のオプトインです。私たちは必ずしもスキーマエンティティのすべてからprotoメッセージやgRPCサービス定義を生成したいわけではありません。このアノテーションによりその制御が可能になります。 それをUserに追加したい場合は、ent/schema/user.goに追加します。

func (User) Annotations() []schema.Annotation {
return []schema.Annotation{
entproto.Message(),
}
}

次に、各フィールドにアノテーションを付けて、フィールド番号を割り当てる必要があります。 protobufのメッセージタイプを定義するとき、各フィールドには一意の番号を割り当てる必要があることを思い出してください。 そのためには、各フィールドにentproto.Fieldアノテーションを追加します。 ent/schema/user.goFieldsを以下のように更新します。

// Userのフィールド
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
Unique().
Annotations(
entproto.Field(2),
),
field.String("email_address").
Unique().
Annotations(
entproto.Field(3),
),
}
}

フィールドの番号を1から始めていないことに注意してください。これは、entが暗黙のうちにエンティティのIDフィールドを作成し、そのフィールドには自動的に1の番号が割り当てられるためです。 これで、protobufのメッセージタイプ定義を生成することができます。 そのためには、ent/generate.goに、entprotoコマンドラインツールを呼び出すgo:generateディレクティブを追加します。 以下のようになります。

package ent

//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate ./schema
//go:generate go run -mod=mod entgo.io/contrib/entproto/cmd/entproto -path ./schema

コードを再生成してみましょう。

go generate ./...

すべてのprotobuf関連の生成コードを含む新しいディレクトリが作成されたことを確認してください。ent/protoです。 以下が含まれています。

ent/proto
└── entpb
├── entpb.proto
└── generate.go

2つのファイルが作成されました。 その内容を見てみましょう。

// Code generated by entproto. DO NOT EDIT.
syntax = "proto3";

package entpb;

option go_package = "ent-grpc-example/ent/proto/entpb";

message User {
int32 id = 1;

string user_name = 2;

string email_address = 3;
}

いいね! Userスキーマにマッピングするメッセージタイプ定義を含む新しい.protoファイルが作成されました!

package entpb
//go:generate protoc -I=.. --go_out=.. --go-grpc_out=.. --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative --entgrpc_out=.. --entgrpc_opt=paths=source_relative,schema_path=../../schema entpb/entpb.proto

新しい generate.go ファイルが作成され、.proto ファイルから Go コードを生成する方法を指示する protobuf コードジェネレータ protoc への呼び出しが行われました。 このコマンドを動作させるためには、まずprotocと3つのprotobufプラグインをインストールする必要があります。protoc-gen-go (Go Protobuf構造体を生成する)、protoc-gen-go-grpc (Go gRPCサービスインターフェースとクライアントを生成する)、protoc-gen-entgrpc (サービスインターフェースの実装を生成する) です。 これらがインストールされていない場合は、以下の指示に従ってください。

これらの依存関係をインストールした後、コード生成を再実行します。

go generate ./...

ent/proto/entpb/entpb.pb.goという名前の新しいファイルが作成され、エンティティ用に生成されたGo構造体が含まれていることを確認してください。

これを使って、すべてが正しく配線されていることを確認するテストを書いてみましょう。 pb_test.goという名前の新規ファイルを作成し、書き込みます。

package main

import (
"testing"

"ent-grpc-example/ent/proto/entpb"
)

func TestUserProto(t *testing.T) {
user := entpb.User{
Name: "rotemtam",
EmailAddress: "rotemtam@example.com",
}
if user.GetName() != "rotemtam" {
t.Fatal("expected user name to be rotemtam")
}
if user.GetEmailAddress() != "rotemtam@example.com" {
t.Fatal("expected email address to be rotemtam@example.com")
}
}

こちらを実行してください。

go get -u./... # 生成されたパッケージの依存先をインストールする
go test ./...

ヨシッ! テストは合格です。 Entスキーマから正しく機能するGo Protobuf構造体の生成に成功しました。 次に、スキーマからCRUD gRPC serverを自動的に生成する方法を見てみましょう。

スキーマから完全に動作する gRPC サーバーを生成する

ent.SchemaからProtobuf構造体を生成させることは便利ですが、私たちが本当に興味を持っているのは、実際のデータベースからエンティティを作成、読み取り、更新、削除できる実際のサーバーを手に入れることです。 そのためには、たった1行のコードを更新するだけでいいのです! スキーマへentproto.Serviceというアノテーションを追加すると、gRPCサービス定義の生成に興味があることをentprotoのコードジェネレータに伝え、protoc-gen-entgrpcがその定義を読み込んでサービスの実装を生成します。 ent/schema/user.goを編集し、スキーマのAnnotationsを修正します。

func (User) Annotations() []schema.Annotation {
return []schema.Annotation{
entproto.Message(),
+ entproto.Service(), // <-- この行を追加
}
}

ここでコード生成を再実行します。

go generate ./...

すると、ent/proto/entpbに興味深い変化が見られます。

ent/proto/entpb
├── entpb.pb.go
├── entpb.proto
├── entpb_grpc.pb.go
├── entpb_user_service.go
└── generate.go

まず、entprotoでは、entpb.protoにサービス定義を追加しました。

service UserService {
rpc Create ( CreateUserRequest ) returns ( User );

rpc Get ( GetUserRequest ) returns ( User );

rpc Update ( UpdateUserRequest ) returns ( User );

rpc Delete ( DeleteUserRequest ) returns ( google.protobuf.Empty );
}

また、新たに2つのファイルが作成されました。 1つ目のent_grpc.pb.goには、gRPCクライアントスタブとインターフェース定義が含まれています。 ファイルを開くと、その中に (他の多くのものの中から) 見つけることができます。

// UserServiceClient は、UserService サービスのクライアント API である
//
// ctxの使用およびストリーミングRPCの終了/終了に関するセマンティクスについては、https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream をご参照いただきたい
type UserServiceClient interface {
Create(ctx context.Context, in *CreateUserRequest, opts ...grpc.CallOption) (*User, error)
Get(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*User, error)
Update(ctx context.Context, in *UpdateUserRequest, opts ...grpc.CallOption) (*User, error)
Delete(ctx context.Context, in *DeleteUserRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
}

2つ目のファイル、entpub_user_service.goには、このインターフェイスの実装が生成されています。 例えば、Getメソッドの実装です。

// Get は UserServiceServer.Get の実装である
func (svc *UserService) Get(ctx context.Context, req *GetUserRequest) (*User, error) {
get, err := svc.client.User.Get(ctx, int(req.GetId()))
switch {
case err == nil:
return toProtoUser(get), nil
case ent.IsNotFound(err):
return nil, status.Errorf(codes.NotFound, "not found: %s", err)
default:
return nil, status.Errorf(codes.Internal, "internal error: %s", err)
}
}

悪くない! 次に、サービスへのリクエストを提供するgRPCサーバーを作成します。

サーバーを作成する

新規ファイルcmd/server/main.goを作成し、以下のように書き込みます。

package main

import (
"context"
"log"
"net"

_ "github.com/mattn/go-sqlite3"
"ent-grpc-example/ent"
"ent-grpc-example/ent/proto/entpb"
"google.golang.org/grpc"
)

func main() {
// entクライアントを初期化する
client, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
if err != nil {
log.Fatalf("failed opening connection to sqlite: %v", err)
}
defer client.Close()

// マイグレーションツールを実行する (テーブルの作成など)
if err := client.Schema.Create(context.Background()); err != nil {
log.Fatalf("failed creating schema resources: %v", err)
}

// 生成されたUserサービスを初期化する
svc := entpb.NewUserService(client)

// 新しいgRPCサーバーを作成する (1つのサーバーに複数のサービスを配線することが可能)
server := grpc.NewServer()

// Userサービスをサーバーに登録する
entpb.RegisterUserServiceServer(server, svc)

// トラフィックを待ち受けするために5000番ポートを開く
lis, err := net.Listen("tcp", ":5000")
if err != nil {
log.Fatalf("failed listening: %s", err)
}

// 無期限にトラフィックを待ち受けすることが可能
if err := server.Serve(lis); err != nil {
log.Fatalf("server ended: %s", err)
}
}

github.com/mattn/go-sqlite3のインポートを追加したことに注目してしてください。これをモジュールに追加する必要があります。

go get -u github.com/mattn/go-sqlite3

次に、サーバーを実行しながら、そのサーバーと通信するクライアントを書きましょう。

go run -mod=mod ./cmd/server

クライアントを作成する

サーバーにいくつかの呼び出しを行うシンプルなクライアントを作ってみましょう。 cmd/client/main.goという名前の新規ファイルを作成し、以下を書き込みます。

package main

import (
"context"
"fmt"
"log"
"math/rand"
"time"

"ent-grpc-example/ent/proto/entpb"
"google.golang.org/grpc"
"google.golang.org/grpc/status"
)

func main() {
rand.Seed(time.Now().UnixNano())

// サーバーへの接続を開始する
conn, err := grpc.Dial(":5000", grpc.WithInsecure())
if err != nil {
log.Fatalf("failed connecting to server: %s", err)
}
defer conn.Close()

// 接続上にUserサービスのクライアントを作成する
client := entpb.NewUserServiceClient(conn)

// サーバーにランダムなUserの作成を依頼する
ctx := context.Background()
user := randomUser()
created, err := client.Create(ctx, &entpb.CreateUserRequest{
User: user,
})
if err != nil {
se, _ := status.FromError(err)
log.Fatalf("failed creating user: status=%s message=%s", se.Code(), se.Message())
}
log.Printf("user created with id: %d", created.Id)

// 別のRPC呼び出しで、先に保存したUserを取得する
get, err := client.Get(ctx, &entpb.GetUserRequest{
Id: created.Id,
})
if err != nil {
se, _ := status.FromError(err)
log.Fatalf("failed retrieving user: status=%s message=%s", se.Code(), se.Message())
}
log.Printf("retrieved user with id=%d: %v", get.Id, get)
}

func randomUser() *entpb.User {
return &entpb.User{
Name: fmt.Sprintf("user_%d", rand.Int()),
EmailAddress: fmt.Sprintf("user_%d@example.com", rand.Int()),
}
}

クライアントは、当社のサーバーが待ち受けているポート5000への接続を確立し、新しいユーザーを作成するためにCreateリクエストを発行し、データベースからユーザーを取得するために2回目のGetリクエストを発行します。 それではクライアントのコードを実行しましょう。

go run ./cmd/client

アウトプットを観察する

2021/03/18 10:42:58 user created with id: 1
2021/03/18 10:42:58 retrieved user with id=1: id:1 name:"user_730811260095307266" email_address:"user_7338662242574055998@example.com"

素晴らしい! スキーマにいくつかのアノテーションを付けることで、コード生成の超能力を使って、あっという間に動くgRPCサーバーを作りました!

注意点と制限事項

entprotoはまだ実験段階で、いくつかの基本的な機能がありません。 例えば、多くのアプリケーションでは、サービスにListFindメソッドが必要になると思いますが、これらはまだサポートされていません。 また、その他の課題についても、今後取り組んでいく予定です。

  • 現在は"一意"なエッジのみがサポートされています (O2O、O2M)
  • 生成された "更新系"メソッド (Create/Update) は、現在、ゼロ/null値やフィールドのnull可能性を無視して、すべてのフィールドを設定しています
  • すべてのフィールドは、gRPCリクエストからentクライアントにコピーされます。また、フィールド/エッジアノテーションを追加することで、一部のフィールドをサービスで設定できないようにする機能もサポートされる予定です

次のステップ

私たちは、ent + gRPCが、Goでサーバーアプリケーションを構築するための素晴らしい方法になると信じています。 例えば、アプリケーションが管理するエンティティに対してきめ細かなアクセスコントロールを設定するために、開発者はすでに、gRPCインテグレーションですぐに機能するプライバシーポリシーを使用することができます。 エンティティのさまざまなライフサイクルイベントに対して任意のGoコードを実行するために、開発者はカスタムのフックを利用することができます。

entでgRPCサーバを構築したいですか? If you want some help setting up or want the integration to support your use case, please reach out to us via our Discussions Page on GitHub or in the #ent channel on the Gophers Slack or our Discord server.

より多くのEntのニュースと最新情報をお届けします

· 1 分で読む

この数ヶ月間、Entプロジェクトのissuesでは、One-to-OneまたはOne-to-Manyのエッジを持つエンティティを検索する際に、外部キーフィールドの検索サポートを追加することについて多くの議論がなされました。 v0.7.0からentがこの機能をサポートしたことをお知らせします。

エッジフィールドのサポート以前

このブランチがマージされる前は、エンティティの foreign-key フィールドを取得したいユーザーは、eager-loadingを使用する必要がありました。 スキーマは以下のようになります。

// ent/schema/user.go:

// Userは、Userエンティティのスキーマ定義を保持しています。
type User struct {
ent.Schema
}

// Userのフィールド
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
Unique().
NotEmpty(),
}
}

// Userのエッジ
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.From("pets", Pet.Type).
Ref("owner"),
}
}

// ent/schema/pet.go

// Petは、Petエンティティのスキーマ定義を保持しています。
type Pet struct {
ent.Schema
}

// Petのフィールド
func (Pet) Fields() []ent.Field {
return []ent.Field{
field.String("name").
NotEmpty(),
}
}

// Petのエッジ
func (Pet) Edges() []ent.Edge {
return []ent.Edge{
edge.To("owner", User.Type).
Unique().
Required(),
}
}

このスキーマは2つの関連するエンティティを記述しています。UserPet、両者の間には一対多のエッジがあります。ユーザーは多くのペットを所有することができ、ペットは1人の飼い主を持つことができます。

データストレージからペットを取得する場合、開発者はペットの外部キーフィールドにアクセスしたいと考えるのが一般的です。 しかし、このフィールドはownerエッジから暗黙的に作成されるため、エンティティを取得する際に自動的にアクセスできました。 これをストレージから取り出すには、開発者は次のような作業が必要です。

func Test(t *testing.T) {
ctx := context.Background()
c := enttest.Open(t, dialect.SQLite, "file:ent?mode=memory&cache=shared&_fk=1")
defer c.Close()

// Userを作成する
u := c.User.Create().
SetUserName("rotem").
SaveX(ctx)

// Petを作成する
p := c.Pet.
Create().
SetOwner(u). // PetをUserに関連付ける
SetName("donut").
SaveX(ctx)

petWithOwnerId := c.Pet.Query().
Where(pet.ID(p.ID)).
WithOwner(func(query *ent.UserQuery) {
query.Select(user.FieldID)
}).
OnlyX(ctx)
fmt.Println(petWithOwnerId.Edges.Owner.ID)
// 出力: 1
}

非常に冗長であるだけでなく、この方法でペットと飼い主を検索することは、データベースのクエリとしては非効率的でした。 このクエリを.Debug()で実行すると、この呼び出しを満たすためにentが生成するDBクエリを見ることができます。

SELECT DISTINCT `pets`.`id`, `pets`.`name`, `pets`.`pet_owner` FROM `pets` WHERE `pets`.`id` = ? LIMIT 2 
SELECT DISTINCT `users`.`id` FROM `users` WHERE `users`.`id` IN (?)

この例では、EntはまずIDが1のPetを取得し、次にIDが1のユーザーに対してusersテーブルからidフィールドを冗長的に取得します。

エッジフィールドのサポートあり

エッジフィールドサポートは、このフローを大幅に簡素化し、効率化します。 この機能により、開発者はスキーマのFields()の一部として外部キーフィールドを定義し、エッジ定義の.Field(..)修飾子を使用して、Entに外部カラムをこのフィールドに公開してマッピングするように指示することができます。 そこで、今回のスキーマ例では、次のように修正します。

// user.go は同様のまま

// pet.go
// Petのフィールド
func (Pet) Fields() []ent.Field {
return []ent.Field{
field.String("name").
NotEmpty(),
field.Int("owner_id"), // <-- explicitly add the field we want to contain the FK
}
}

// Edges of the Pet.
func (Pet) Edges() []ent.Edge {
return []ent.Edge{
edge.To("owner", User.Type).
Field("owner_id"). // <-- どのフィールドが飼い主への参照を保持しているのかをentに伝える
Unique().
Required(),
}
}

クライアントコードを更新するには、以下のようにコード生成を再実行する必要があります。

go generate ./...

これで、クエリをもっとシンプルなものに変更することができます。

func Test(t *testing.T) {
ctx := context.Background()
c := enttest.Open(t, dialect.SQLite, "file:ent?mode=memory&cache=shared&_fk=1")
defer c.Close()

u := c.User.Create().
SetUserName("rotem").
SaveX(ctx)

p := c.Pet.Create().
SetOwner(u).
SetName("donut").
SaveX(ctx)

petWithOwnerId := c.Pet.GetX(ctx, p.ID) // <-- ペットを取り出すだけ

fmt.Println(petWithOwnerId.OwnerID)
// 出力: 1
}

.Debug()修飾子を使って実行すると、DBのクエリがよりセンスあるものになったことがわかります。

SELECT DISTINCT `pets`.`id`, `pets`.`name`, `pets`.`owner_id` FROM `pets` WHERE `pets`.`id` = ? LIMIT 2

ヨシッ 🎉!

既存のスキーマをエッジフィールドに移行する

既に既存のスキーマでEntを使用している場合、外部キーのカラムを持つO2Mリレーションが既にデータベースに存在している可能性があります。 スキーマの構成によっては、今から追加するフィールドとは別の名前のカラムに格納されている可能性があります。 例えば、owner_idフィールドを作成したいのに、Entはすでに外部キーのカラムをpet_ownerとして自動作成しているとします。

Entがこのフィールドに使用しているカラム名を確認するには、./ent/migrate/schema.goファイルを参照してください。

PetsColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt, Increment: true},
{Name: "name", Type: field.TypeString},
{Name: "pet_owner", Type: field.TypeInt, Nullable: true}, // <-- これがFKです
}

スムーズな移行を可能にするためには、既存のカラム名を使い続けるようにEntに明示的に伝える必要があります。 StorageKey 修飾子 (フィールドまたはエッジのいずれか) を使用して、これを行うことができます。 以下の例をご覧ください。

// In schema/pet.go:

// Petのフィールド
func (Pet) Fields() []ent.Field {
return []ent.Field{
field.String("name").
NotEmpty(),
field.Int("owner_id").
StorageKey("pet_owner"), // <-- カラム名を明示的に設定する
}
}

近い将来、スキーマバージョニングを実装し、コードと一緒にスキーマの変更履歴を保存することを計画しています。 この情報があれば、entは自動的かつ予測可能な方法で、このような移行をサポートすることができます。

まとめ

エッジフィールドのサポートはすぐに利用できます。go get -u entgo.io/ent@v0.7.0でインストールしてください。

フィードバックに時間を割き、この機能を適切に設計するために協力してくれた善良な人々に多くの感謝🙏を捧げます。Alex SnastRuben de VriesMarwan SulaimanAndy DaySebastian FeketeJoe Harveyの皆さんです。

より多くのEntのニュースと最新情報をお届けします

· 1 分で読む

テルアビブのFacebook ConnectivityチームにおけるGoの活況について

20ヶ月前、私はテルアビブにあるFacebook Connectivity (FBC) チームに参加しました。Goでのプログラミングを約5年間行い、いくつかの企業に組み込んでいました。
私が参加したチームは新しいプロジェクトに取り組んでいて、このミッションのために言語を選択する必要がありました。 我々はいくつかの言語を比較し、Goと共に行くことにしました。

それ以来、Goは他のFBCプロジェクトでも普及し続け、テルアビブだけで約15人のGoエンジニアを抱える大成功を収めました。 新しいサービスがGoで書かれるようになりました

Goで新しいORMを書いた動機について

Facebook以前の5年間の私の仕事のほとんどは、インフラツールやマイクロサービスに関するもので、データモデルに関する仕事はあまりありませんでした。 SQLデータベースでちょっとした作業をする必要があるサービスでは、既存のオープンソースのソリューションを使用していましたが、複雑なデータモデルを扱っていたサービスでは、堅牢なORMを用いて別の言語で書かれていました。 例えば、SQLAlchemy を使った Python です。

Facebookでは、データモデルをグラフの概念で考えています。 このモデルは社内でも良い経験になっています。
Goのための適切なグラフベースのORMがないため、私たちは次のような原則に基づいてここでORMを書きました。

  • Schema As Code - 型、関係、制約の定義は、(structタグではなく) Goコードで行う必要があり、CLIツールを使って検証する必要があります。 Facebookの社内でも同じようなツールを使って良い経験を得ています。
  • codegenを用いた静的型付けと明示的なAPI - 至る所にinterface{}があるAPIは、開発者の作業効率に影響を与えます。(特にプロジェクトの初心者に対して)
  • 問い合わせ、集計、グラフ のトラバーサル (横断) はシンプルであるべき - 開発者は、生のSQLクエリやSQL用語を扱いたくありません。
  • 述語は静的に型付けされるべき どこにも文字列は持ちません。
  • context.Contextの完全なサポート - これは、トレースやログのシステムで完全な可視性を得るのに役立ち、キャンセルなどの他の機能にも重要です。
  • ストレージに依存しない - 当初はGremlin (AWS Neptune) で開発を開始し、後にMySQLに切り替えたため、codegenテンプレートを使用してストレージ層を動的に保つようにしました。

entのオープンソース化について

entは、上述の原則に基づいて構築された、Goのためのエンティティフレームワーク (ORM) です。 entは、任意のデータモデルやグラフ構造をGoコードで簡単に定義することを可能にします。スキーマ構成は、entc (entのcodegen) によって検証され、Go開発者の生産性と満足度を維持するための慣用的で静的に型付けされたAPIを生成します。 MySQL、MariaDB、PostgreSQL、SQLite、およびGremlinベースのグラフデータベースをサポートしています。

本日、entをオープンソース化し、entgo.io/docs/getting-startedから始めていただけるようにしました。