Edited at

ElixirでSI開発入門 #3 主キーが"id "じゃない既存DBへの接続

More than 1 year has passed since last update.

(この記事は、「Elixir or Phoenix Advent Calendar 2017」の12日目です)

昨日は、@piacere さんの「ExcelでElixirマスター3回目:WebにDBデータ表示でした


はじめに

Elixirで実際にプロダクト開発した経験からサンプルコードを交えて解説する本連載

前回までに引き続きDB関連のお話です。

Elixirで実装された軽量ORマッパーであるEctoはデフォルトでidというサロゲートキーを主キーとするスキーマ実装とマイグレーションファイルを生成します。

ORマッパーが一般的でなかった時代からDB設計をやっていたおじさんからすると、そもそも"id"という味もそっけもない主キーが気持ち悪くもあるのですが、モダンなフレームワークの潮流なのでそこは慣れるとして・・・

既存のシステムで作成されたデータベースに繋ぎたい場合ってありますよね?


  • 創り込んじゃった基幹系を全改修するお金はないけど、追加開発する情報系はサーバーコストもかけたくないからElixirで・・・とか

  • IOTからデータ流れるサーバーが既存言語で耐えられなくなってるのでElixirで・・・とか

  • あとは自分が単にElixirやってみたいから!とか(これエンジニアにとっては大事なモチベーションですよね)

でも既存DBは主キーが item_code:stringとかだったりします。

今回はそんな id:integer じゃない主キーを設定する方法についてです。

本連載の記事はこちら

|> ElixirでSI開発入門 #1 Ectoで悲観的ロック

|> ElixirでSI開発入門 #2 Ectoで楽観的ロック

|> ElixirでSI開発入門 #3 主キーが"id "じゃない既存DBへの接続

:stars::stars::stars::stars::stars: お知らせ :stars::stars::stars::stars::stars:

「fukuoka.ex#11:DB/データサイエンスにコネクトするElixir」を6/22(金)19時に開催します

私も本連載で出してないSI開発ネタを出す予定ですので、

Elixirでプロダクト開発最前線に興味ある方はぜひぜひご応募ください!

image.png


カスタム・プライマリキー設定で主キーを変更

こちらも公式にサポートされており、phoenixframework.orgにわかりやすい記事があがっています。

http://phoenixframework.org/blog/custom-primary-key


実装の前提

今回は以下の実装を例に考えます


  • item_code:stringを主キーにもつitemsテーブルに接続する

  • 今回はサンプル実装のためにマイグレーションファイルも生成する

また、以下の環境で実装しました


  • Elixir     v1.6.1

  • Phoenix v1.3.2

  • Ecto      v2.2.10

  • PostgreSQL v10.2


開発手順 


プロジェクト〜モデルの作成

PhoenixプロジェクトとDBを作成

> mix phx.new ecto_custome_primary_key_sample --no-brunch

> cd ecto_custome_primary_key_sample
> mix ecto.create

モデルを作成

> mix phx.gen.html Posts Comment comments name comment lock_version:integer

※既存DBに接続する場合は、マイグレーションは不要なので--no-schemaオプションを指定すると不要なマイグレーションファイルが作成されない

> mix phx.gen.html Posts Comment comments name comment lock_version:integer --no-schema

ルーティングを追加


lib/ecto_custome_primary_key_sample_web/router.ex


defmodule EctoCustomePrimaryKeySampleWeb.Router do
use EctoCustomePrimaryKeySampleWeb, :router

pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
end

pipeline :api do
plug :accepts, ["json"]
end

scope "/", EctoCustomePrimaryKeySampleWeb do
pipe_through :browser # Use the default browser stack

get "/", PageController, :index
resources "/items", ItemController # <-- 作成したモデルのルーティングを追加
end

end



デフォルトの実装

一旦マイグレーションを実行して、出来上がった実装を確認してみる

> mix ecto.migrate

スキーマにもマイグレーションファイルにもidの項目はない


lib/ecto_custome_primary_key_sample/items/item.ex


defmodule EctoCustomePrimaryKeySample.Items.Item do
use Ecto.Schema
import Ecto.Changeset

schema "items" do
field :category, :string
field :item_code, :string
field :name, :string

timestamps()
end

〜中略〜
end



priv/repo/migrations/yyyyMMddhhmmss_create_items.exs

defmodule EctoCustomePrimaryKeySample.Repo.Migrations.CreateItems do

use Ecto.Migration

def change do
create table(:items) do
add :item_code, :string
add :category, :string
add :name, :string

timestamps()
end

end
end


が、出来上がったテーブルには"id"が主キーとして追加されている。

主キーがid:integerであることが暗黙的に実装されているのだ

CREATE TABLE public.items (

id bigserial NOT NULL,
item_code varchar(255) NULL,
category varchar(255) NULL,
name varchar(255) NULL,
inserted_at timestamp NOT NULL,
updated_at timestamp NOT NULL,
CONSTRAINT items_pkey PRIMARY KEY (id)
)
WITH (
OIDS=FALSE
) ;


カスタム・プライマリキーの設定

スキーマに主キー項目を指定する記述を追加する


lib/ecto_custome_primary_key_sample/items/item.ex

defmodule EctoCustomePrimaryKeySample.Items.Item do

use Ecto.Schema
import Ecto.Changeset

@primary_key {:item_code, :string, []} # <-- 主キー項目と型を指定する
 @derive {Phoenix.Param, key: :item_code} # <-- Phoenixがパラメータキーとして何を使用して変換するかを指定する
schema "items" do
field :category, :string
# field :item_code, :string <-- 重複定義となるためfield指定は削除するかコメントアウト
field :name, :string

timestamps()
end

@doc false
def changeset(item, attrs) do
item
|> cast(attrs, [:item_code, :category, :name])
|> validate_required([:item_code, :category, :name])
end
end


マイグレーションを行う場合はマイグレーションファイルにも主キー設定を追加する


priv/repo/migrations/yyyyMMddhhmmss_create_items.exs

defmodule EctoCustomePrimaryKeySample.Repo.Migrations.CreateItems do

use Ecto.Migration

def change do
create table(:items, primary_key: false) do # <-- ", primary_key: false"を追加
add :item_code, :string, primary_key: true # <-- ", primary_key: true"を追加
add :category, :string
add :name, :string

timestamps()
end

end
end


テーブルを再作成する。

一旦DBに接続してるセッションを全て切断し(DBクライアントツール、phoenix、iexなど)ecto.resetを実行するとDB再作成&シードデータの登録(priv/repo/seeds.exs)が再実行される。

> mix ecto.reset

改めて確認すると、item_code:string が主キーのテーブルが作成されている。


CREATE TABLE public.items (
item_code varchar(255) NOT NULL,
category varchar(255) NULL,
name varchar(255) NULL,
inserted_at timestamp NOT NULL,
updated_at timestamp NOT NULL,
CONSTRAINT items_pkey PRIMARY KEY (item_code)
)
WITH (
OIDS=FALSE
) ;


動かしてみる

Phoenixサーバーを起動する。

> mix ecto.migrate

> mix phx.server

以下のURLにアクセスし、適当にItemを登録してみる。

http://localhost:4000/items

URLも主キー文字列でアクセスできる

http://localhost:4000/items/X-01

ちなみに、文字列型であれば2バイト文字の主キーも登録可能

http://localhost:4000/items/%E4%BC%8A%E3%81%AE6%E5%8F%B7

まあ、SIでは基本避けますが・・・


まとめ


  • idが主キーでない時にはCustom Primary Keys


  • @primary_key@derive で主キーを指定

  • --no-schemaオプションでマイグレーション対象外

いかがだったでしょうか。

これで既存システムのElixirリプレイスもバッチリですね!

明日は、@twinbee さんの「Elixir並列処理「Flow」の2段ステージ構造を理解する」です。