12
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Rails × Hasuraの共存環境を作ってみる

Last updated at Posted at 2021-12-24

こんにちは!
気持ちは永遠の新卒1年目、いつの間にか3年目の株式会社エイチームコマーステック北村 (@NamedPython )です。

今日は、コマーステックがもしかしたら辿るかもしれない技術スタックを先行して検証していきます。
試す技術はタイトルにもある通り「Rails × Hasura」です。
役割分担としては、

  • Rails(API mode)
    • マイグレーション
    • ビジネスロジック
  • Hasura
    • GraphQL Engine
      • 単純なCRUD
    • ゲートウェイ
    • スケジューラー

みたいな感じにしたいな〜と想像しています。
シンプルなCRUDのGraphQLはHasuraにお任せ、複雑な処理はActionsRailsに流したり、リゾルバを書いてRemote SchemasHasuraに取り込んだりしてもいい。Railsでリゾルバを書くのが面倒なら別のバックエンドを立ててしまえばいい。これが実現できると、分厚くなりがちなRailsを徐々にマイクロにしていきつつ、他のスタックに移行するなり、Railsのアップグレードや改善に取り組める。という妄想をしているのです。

本当はMySQLなRails × HasuraのMySQL-previewな構成で試したかったんですが、IssueやDiscussionを見るにHasura社内の開発的にもMySQL対応の難易度が高いようで、公開されているもの↓は実用レベルになかったのでPostgreSQL構成で書いています。

環境をつくっていく

それでは早速作っていきましょう。
以下のような構成を作ります。

プロジェクト構成
rails-hasura/        <- current, project dir
  ├ hasura
  │   └ config.yaml
  ├ rails
  │   ├ Dockerfile
  │   ├ Gemfile
  │   └ Gemfile.lock
  └ docker-compose.yaml

./rails/Gemfile./rails/Dockerfile./docker-compose.yaml、は以下です。

./rails/Gemfile
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

gem 'rails', '~> 6.1.4', '>= 6.1.4.4'
./rails/Dockerfile
FROM ruby:2.6.5

RUN apt-get update && apt-get install -y \
  build-essential \
  libpq-dev \
  nodejs \
  default-mysql-client \
  yarn

WORKDIR /rails-app

COPY Gemfile Gemfile.lock /rails-app/

RUN bundle install
./docker-compose.yml
version: '3.8'

services:
  rails:
    build:
      dockerfile: Dockerfile
      context: ./rails
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    ports:
        - 3000:3000
    volumes:
      - ./rails:/rails-app
    tty: true
    stdin_open: true
  hasura:
    image: hasura/graphql-engine:v2.1.1.cli-migrations-v3
    ports:
      - 8080:8080
    depends_on:
      - db
    restart: always
    environment:
      HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:password@db:5432/postgres
      HASURA_GRAPHQL_ENABLE_TELEMETRY: 'false'
      HASURA_GRAPHQL_ENABLE_CONSOLE: 'true'
      HASURA_GRAPHQL_DEV_MODE: 'true'
      HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log
      HASURA_GRAPHQL_UNAUTHORIZED_ROLE: anonymous
      HASURA_GRAPHQL_ADMIN_SECRET: devenvsecret
  db:
    image: postgres:12
    restart: always
    volumes:
      - db_data:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: password
volumes:
  db_data:

続いて./hasura/config.yamlです。

version: 3
endpoint: http://localhost:8080/
admin_secret: devenvsecret
enable_telemetry: false
api_paths:
  v1_query: v1/query
  v2_query: v2/query
  v1_metadata: v1/metadata
  graphql: v1/graphql
  config: v1alpha1/config
  pg_dump: v1alpha1/pg_dump
  version: v1/version
metadata_directory: metadata
migrations_directory: migrations
seeds_directory: seeds
actions:
  kind: synchronous
  handler_webhook_baseurl: http://localhost:3000
  codegen:
    framework: ''
    output_dir: ''

さて、ここまで来たらあとはrails newの儀式です。

docker compose run rails rails new . --force --database=postgresql --skip-webpack-install

そうすると、ガーっとファイルが生成され、bundle installなども実行されるので待ちます。Gemfile, Gemfile.lockの中身も書き変わりますよ。

続いては、./rails/config/database.ymlをいじる必要があるので、お好みのエディタで開き、次のように編集します。

./rails/config/database.yml
# PostgreSQL. Versions 9.3 and up are supported.
#
# Install the pg driver:
#   gem install pg
# On macOS with Homebrew:
#   gem install pg -- --with-pg-config=/usr/local/bin/pg_config
# On macOS with MacPorts:
#   gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config
# On Windows:
#   gem install pg
#       Choose the win32 build.
#       Install PostgreSQL and put its /bin directory on your path.
#
# Configure Using Gemfile
# gem 'pg'
#
default: &default
  adapter: postgresql
  encoding: unicode
  # For details on connection pooling, see Rails configuration guide
  # https://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
+ host: db
+ port: 5432
+ username: postgres
+ password: password

そうしましたらいよいよ起動でございます。

docker compose up --build -d

# 起動ログを見たい方は
docker compose logs -f

こちらのコマンド実行もちょっとかかると思うので猫でも撫でてください :cat:

Railsからのマイグレーションをしていく

...とその前に個人的な好みと、現行開発の方針的に顧客の目に触れそうな識別子はUUIDv4にしているので、Railsでそれができるようにしていく。

余談: RailsでUUIDv4なプライマリキー

この手順を踏むと一瞬でPostgreSQLに依存するマイグレーションが走ったりしちゃうので注意されたし。

こちらの記事を参考にさせていただきました。インターネットに感謝 :pray:

手順書こうかと思いましたが、ほぼ記事ままなので割愛。以下からは適用済みの前提があることをご認識ください。

rails g model -> rails db:migrate

エイチームコマーステックですし、EC的にありきたりなテーブル構造を組みつつ、それがHasuraからどう認識されるかみてみましょう。一度に複数商品注文できるようにすると面倒なので、1注文1商品で。

  • お客さん customers
  • 製品 products
  • 注文 customer_orders
    • 買ったお客さん customer_orders.customer_id -> customers.id
    • 買った商品 customer_orders.product_id -> products.id
docker compose exec rails bin/rails g model customer
docker compose exec rails bin/rails g model product
docker compose exec rails bin/rails g model customer_order

./rails/db/migrate/配下にできたそれぞれのファイルを以下のように編集!

./rails/db/migrate/#{timestamp}_create_customers.rb
class CreateCustomers < ActiveRecord::Migration[6.1]
  def change
    create_table :customers, id: :uuid do |t|
      t.string :name, null: false
      t.string :email, null: false

      t.timestamps
    end
  end
end
./rails/db/migrate/#{timestamp}_create_products.rb
class CreateProducts < ActiveRecord::Migration[6.1]
  def change
    create_table :products, id: :bigint do |t|
      t.string :label_name, null: false
      t.integer :price, null: false, comment: 'tax excluded'
      t.string :description

      t.timestamps
    end
  end
end
./rails/db/migrate/#{timestamp}_create_customer_orders.rb
class CreateCustomerOrders < ActiveRecord::Migration[6.1]
  def change
    create_table :customer_orders, id: :uuid do |t|
      t.string :order_code, null: false
      t.datetime :sold_at, null: false, default: -> { 'CURRENT_TIMESTAMP' }
      t.references :customer, type: :uuid, foreign_key: true
      t.references :product, foreign_key: true
      t.integer :price, null: false
      t.integer :price_with_tax, null: false

      t.timestamps
    end
  end
end

そしていよいよマイグレーション実行〜

docker compose exec rails bin/rails db:migrate

Hasuraで確認

さて続いてHasuraでどう認識されているかみてみましょう!
一旦見るだけなので http://localhost:8080/ から見ましょう。

image.png

おや、Enter admin-secretとありますね。大丈夫、まだ慌てる時間じゃありません。
./docker-compose.ymlhasuraサービス内の環境変数にHASURA_GRAPHQL_ADMIN_SECRETとして設定されている値を入力すればOKです(今回はdevenvsecret)。

続いての画面はGraphiQLですね。ここも使うんですが今見たいのは画像内のDATAタブです。
スクリーンショット 2021-12-25 5.18.14.png

そして次もまたよくわからん画面が出るとは思いますが、続いてはpublicスキーマをポチー

スクリーンショット 2021-12-25 5.23.33.png

そうすると、こういった表示になるかと思います。

スクリーンショット 2021-12-25 5.25.09.png

おや、赤枠内をみるとcustomer_orderscustomersproductsが見えますね。
他も見えてしまっていますが、これはActiveRecordのためのメタデータ保管庫でしょう。

Untracked tables or viewsとのことなので、まあHasura的に未追跡なんでしょう。
Trackのボタンを押してしまいたくなりますが、永続化できなくなってしまうのでReal consoleを立ち上げます。

Hasura CLIのインストールとReal consoleの立ち上げ

公式ドキュメントに従い、Hasura CLIをインストールしてください。筆者はHomebrewで入れました。

続いてこんな感じでコマンドをぽちぽち。

hasura --project hasura console

すると、規定のブラウザで勝手にReal consoleが開きます。
「え、何が違うん?」と思うかもしれませんが、僕も見た目の違いはわかりません :yum:

まあポートが違うのでそこで見分けてください。
このコンソールは、さっきの:8080なコンソールと違い、スキーマや設定に影響が出る変更をすると、設定ファイルとして吐き出され、永続化できます。

Hasuraでtrack、metadataの設定

さて、これで永続化される状態になったので、./hasura/下に吐かれるファイルをみつつテーブルをTrackしたり、リレーションを定義していきましょう。

テーブルのtrack

さて、Hasuraにテーブルを認識してもらうために、Trackしましょう。
さっきと同じ手順で、DATAタブ > publicスキーマのところまでいきましょう。そして、見覚えのあった3つのテーブルのTrackボタンを押していきます。

まずはcustomersテーブル....

image.png

おお〜〜〜〜〜〜〜〜〜〜色々読み込まれとる〜〜〜〜〜〜〜〜!

そしてファイルはどんな感じで吐かれているかというと...?

image.png

め、めちゃくちゃ増えてる...
そしてよく見てみると、今回Trackしたcustomersテーブルの名前がついたファイルがありますね(./hasura/metadata/databases/default/tables/public_customers.yaml)。
中身はどんな感じかというと...?

table:
  name: customers
  schema: public

おっ...、うん...。割とシンプルな感じですね。
まあTrackしただけならこんなもんなので、残りのテーブルもTrackしちゃいましょう。

スクリーンショット 2021-12-25 5.55.36.png

左のツリーにはテーブルが増え、なにやら別種のUntracked ...も増えてきました。
どうやらこれは、外部キー制約をもとに判断した「これrelationships貼ったほうがいいんじゃないの?」というHasuraのレコメンデーションみたいです。

ざっと見ただけでも、

  • お客さんは注文をいっぱい持ってる customers -> [ customer_orders ]
  • 製品はいっぱい注文されている products -> [ customer_orders ]
  • 注文したお客さんを辿れる customer_orders -> customers
  • 注文された製品を辿れる customer_orders -> products

と列挙されているので、いい感じですね。賢いッ!

リレーションのtrack

せっかく上記のようにレコメンドが出ていますし、全部良さそうなのでTrack Allしちゃいましょう。
alert(...)で確認され、OKするとそのままレコメンドだけが消えて、完了のtipsが出ますね。

ファイルの変更を確認してみましょう。

./hasura/metadata/databases/default/tables/public_customers.yaml
  table:
    name: customers
    schema: public
+ array_relationships:
+   - name: customer_orders
+     using:
+       foreign_key_constraint_on:
+         column: customer_id
+         table:
+           name: customer_orders
+           schema: public

お〜、やはりリレーションが追加されるのもちゃんとファイルに吐かれますね。

テストデータを入れてGraphiQLで読み書きする

さて、いよいよ下準備整ってきたので、ラストの検証します。
まずはテストデータですね。こんなところでしょうか。

  • customers
  • products
    • label_name: ゆずシトラスティー
    • price: 390
    • description: パッションティーカスタムが美味しい
  • customer_orders
    • order_code: NAMED-ORDERCODE-123
    • customer_id: ↑
    • product_id: ↑
    • price: 390
    • price_with_tax: 429

テストコードは、Rails consoleでもいいですし、Hasura consoleでもできます。
後者でやるなら以下のような感じで入力画面にたどり着けます!

スクリーンショット 2021-12-25 6.35.07.png

僕もこのタイミングで気づいたんですが、Rails製のcreated_at, updated_atはDB上にデフォルト値が設定されるわけではなくしかもNULL不可なので、後者でやる場合はcreated_at, updated_atもnow()で埋めてあげる必要があります。
ここはRails側のデフォルトのマイグレーション設定を書き換えてあげた方が良さそうなポイントですね。

テストデータを投入できたら早速クエリしてみましょう!
これまで触ってきたDATAタブの左側、APIタブを開きましょう!

スクリーンショット 2021-12-25 6.53.06.png

いつの間にか、ここにもいろいろできていますね。
では単純なところから行ってみましょうか。

query ShowProductInformations {
  products {
    id
    label_name
    price
    description
  }
}

スクリーンショット 2021-12-25 6.56.11.png

お〜取得できましたね〜!
そしたらこういうのはどうですかね?

query ShowCustomersProfileAndOrders {
  customers {
    id
    name
    email
    customer_orders {
      order_code
      price_with_tax
      product {
        label_name
        price
      }
    }
  }
}

スクリーンショット 2021-12-25 6.59.29.png

お〜、has_many / has_oneな関係もちゃんとGraphQLでとれてますね!!!!

さて、GraphQLがいい感じに叩けるのがわかったところで、今回の検証は以上にしようと思います。
実は他にもEnumだったりComputed Fieldsだったりの検証もしたかったんですが、それはまた別の機会に:information_desk_person_tone1:

おわりに + We're hiring!

ここまで読んでいただいてありがとうございました!

株式会社エイチームコマーステックには、僕みたいな新しいモノ好きがこういったことに挑戦できる土壌があり、そういった環境で「Eコマースを面白くしていきたいぜ」という方を募集しています:zap:

「ちょっとお話聞いてみたいぞ!」だったり、「Rails/Hasuraどんな感じで使ってるんだ?」が気になる方は、Twitter(@NamedPython)のDMやリプライまでお気軽にお声がけください!

コマーステックのコーポレートサイトや、採用ページもチェックしてみてくださいね!
それでは良い年末年始をお過ごしください!

12
3
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
12
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?