0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Go製Webアプリケーションにentを導入する方法と選定理由

Last updated at Posted at 2024-12-24

はじめに

GoでWebアプリケーションを開発する際、データベースとのやり取りをスムーズにするためにORM(Object Relational Mapper)を導入することが一般的です。この記事では、Go向けの新しいORMツールである ent を導入する方法を説明しながら、entとは何か、なぜ選んだのかについても詳しく解説します。

なお、以下の記事の続きです。

entとは?

ent は、Facebook(現Meta)が開発したGo向けのエンティティフレームワークです。他のORMと比較して次のような特徴を持っています:

特徴

  • コード生成ベースのスキーマ管理:
    • スキーマ(データベース構造)をGoのコードで定義します。
    • 定義に基づいて自動的に型安全なクエリメソッドが生成されます。
  • 型安全なクエリ:
    • クエリがGoの型に依存するため、開発中にエラーを早期に発見できます。
  • 柔軟な設計:
    • リレーションやユニーク制約など、複雑なデータ構造を直感的に扱えます。

なぜentを選んだのか?

Goでの開発において、RailsのActive Recordに慣れている経験がentの採用を後押ししました。entとActive Recordにはいくつか共通点があり、移行時の学習コストが低いと判断したからです。

Active Record経験が活きるポイント

  • スキーマ定義の親しみやすさ:
    • Active Recordではマイグレーションファイルを通じてスキーマを管理します。一方で、entではGoコードでスキーマを定義しますが、設計思想が似ており、フィールドやリレーションの設定も直感的に行えます。
  • オブジェクト指向的なデータ操作:
    • Active Recordではモデルを介してデータベース操作を行いますが、entも型安全なクライアントとメソッドを提供しており、同様の感覚でクエリを記述できます。
  • リレーションのサポート:
    • Active Recordと同様に、entもリレーション(1対1、1対多、多対多)を簡単に扱えるため、既存の知識を活かしやすいです。

導入手順

以下に、新聞コラムWebアプリにentを導入し、データベースとの接続とスキーマ管理を行う手順を説明します。

1. entのインストール

コンテナを起動しshellへ入る

docker-compose run backend sh

Goコマンドを実行してEntをインストール

# go get entgo.io/ent/cmd/ent

2. スキーマの作成とプロジェクトの初期化

# go run -mod=mod entgo.io/ent/cmd/ent new Article
# go run -mod=mod entgo.io/ent/cmd/ent new Newspaper

すると新たにent ディレクトリが作成されます。また、ent/schema ディレクトリには、 エンティティスキーマが作成されています。
作成されたent/schema/newspaper.goent/schema/article.goに変更を加えます

ent/schema/newspaper.go
package schema

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

// Newspaper holds the schema definition for the Newspaper entity.
type Newspaper struct {
	ent.Schema
}

// Fields of the Newspaper.
func (Newspaper) Fields() []ent.Field {
	return []ent.Field{
		field.String("name").
			NotEmpty().
			Unique(),
		field.String("column_name").
			NotEmpty(),
		field.Time("created_at").
			Default(time.Now),
		field.Time("updated_at").
			Default(time.Now).
			UpdateDefault(time.Now),
	}
}

// Edges of the Newspaper.
func (Newspaper) Edges() []ent.Edge {
	return []ent.Edge{
		edge.To("articles", Article.Type),
	}
}
ent/schema/article.go
package schema

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

// Article holds the schema definition for the Article entity.
type Article struct {
	ent.Schema
}

// Fields of the Article.
func (Article) Fields() []ent.Field {
	return []ent.Field{
		field.Text("content").
			NotEmpty(),
		field.Int("year").
			Positive(),
		field.Int("month").
			Range(1, 12),
		field.Int("day").
			Range(1, 31),
		field.Time("created_at").
			Default(time.Now),
		field.Time("updated_at").
			Default(time.Now).
			UpdateDefault(time.Now),
	}
}

// Edges of the Article.
func (Article) Edges() []ent.Edge {
	return []ent.Edge{
		edge.From("newspaper", Newspaper.Type).
			Ref("articles").
			Unique(),
	}
}

再度コンテナのshellに入る

docker-compose run backend sh

go generateコマンドを実行

# go generate ./ent

go generate ./entを実行すると、Entの自動コード生成ツールが起動し、schemaパッケージで定義したスキーマを使用して、データベースと対話するためのGoコードを生成されます。
そして、自動生成されたパッケージに必要なモジュールを取得するために、以下のコマンドを実行します。

docker-compose run backend sh
# go mod tidy

3. PostgreSQLとの接続

公式サイトには以下のように記述されています。
https://entgo.io/ja/docs/getting-started/

entdemo/start.go
package main

import (
    "context"
    "log"

    "entdemo/ent"

    _ "github.com/lib/pq"
)

func main() {
    client, err := ent.Open("postgres","host=<host> port=<port> user=<user> dbname=<database> password=<pass>")
    if err != nil {
        log.Fatalf("failed opening connection to postgres: %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)
    }
}

こちらを参考にして、main.goの内容を変更します。

backend/main.go
package main

import (
	"github.com/gin-gonic/gin"
	"context"
	"log"
	"os"
	"github.com/joho/godotenv"
	"github.com/jijimama/newspaper-app/ent"
	_ "github.com/lib/pq"
)

func main() {
	router := gin.Default()

	// .envファイルを読み込む
	err := godotenv.Load()
	if err != nil {
		log.Fatalf("Error loading .env file: %v", err)
	}

	// 環境変数から取得
	dbHost := os.Getenv("DB_HOST")
	dbPort := os.Getenv("DB_PORT")
	dbUser := os.Getenv("DB_USER")
	dbName := os.Getenv("DB_NAME")
	dbPassword := os.Getenv("DB_PASSWORD")

	// 接続文字列を作成
	dsn := "host=" + dbHost + " port=" + dbPort + " user=" + dbUser + " dbname=" + dbName + " password=" + dbPassword + " sslmode=disable"

    //PostgreSQLに接続
	client, err := ent.Open("postgres", dsn)
	if err != nil {
		log.Fatalf("failed opening connection to postgres: %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)
	}
	
  	router.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{
      		"message": "Hello, World!",
		})
  	})

  	router.Run(":8080")
}

bakend配下に.envファイルを作成し、以下のようにしました。

DB_HOST=db
DB_PORT=5432
DB_USER=postgres
DB_NAME=db
DB_PASSWORD=password

4. DBの確認

コンテナを起動し、dbのbashに入ります。

docker-compose up backend -d
docker compose exec db bash

そして、DBに接続します。

psql -U postgres -d db

そして、テーブルを確認します。

db=# \dt
           List of relations
 Schema |    Name    | Type  |  Owner   
--------+------------+-------+----------
 public | articles   | table | postgres
 public | newspapers | table | postgres
(2 rows)

db=# \d articles
                                         Table "public.articles"
       Column       |           Type           | Collation | Nullable |             Default              
--------------------+--------------------------+-----------+----------+----------------------------------
 id                 | bigint                   |           | not null | generated by default as identity
 content            | text                     |           | not null | 
 year               | bigint                   |           | not null | 
 month              | bigint                   |           | not null | 
 day                | bigint                   |           | not null | 
 created_at         | timestamp with time zone |           | not null | 
 updated_at         | timestamp with time zone |           | not null | 
 newspaper_articles | bigint                   |           |          | 
Indexes:
    "articles_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
    "articles_newspapers_articles" FOREIGN KEY (newspaper_articles) REFERENCES newspapers(id) ON DELETE SET NULL

無事に、テーブルが生成されていることを確認できました。

遭遇したエラー

column(コラム)テーブルを作成しようとしました。

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

すると、

ent/ent.go:78:11: column.Table undefined (type string has no field or method Table)

というエラーになりました。予約語であることを失念していました。

参考文献

ent リファレンスマニュアル
https://qiita.com/takakou/items/4f8cd3686c7ec84c141d

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?