この記事は、Elixir Advent Calendar 2023 シリーズ11 の18日目です
piacere です、ご覧いただいてありがとございます
前回は、マイクロサービスの現状と「マイクロサービスパターン」に触れた後、Elixirがマイクロサービス構築に有利な面の紹介 をしましたが、今回は、Elixirマイクロサービス化(デマイクロサービス化も)の有力な機能の1つである「Unmbrella」について実践交えて解説します
あと、このコラムが、面白かったり、役に立ったら、 をお願いします
事前準備:モノリシックからスタートする
マイクロサービス/デマイクロサービスの経過をより実感しやすくするため、最初は、モノリシックな構成からスタートすることとしましょう
ブログとそのコメント、それらから参照される画像ストアの3つのサービスを持たせます(本来であればログインユーザーで投稿者やコメント者を管理/認証するところですが今回は割愛)
モノリシックなPhoenix PJを作成し、Phoenix起動します
mkdir apps
cd apps
mix phx.new basic
cd basic
mix ecto.create
cd apps/basic_web
mix phx.gen.live Pictures Picture pictures image:string
mix phx.gen.live Posts Post posts title:string body:text picture_id:integer
mix phx.gen.live Posts Comment comments post_id:integer body:text
mix ecto.migrate
cd ../..
iex -S mix phx.server
下記コード修正も行ってください
…
defmodule Basic.Posts.Post do
use Ecto.Schema
import Ecto.Changeset
schema "posts" do
field :title, :string
field :body, :string
field :picture_id, :integer
+ has_many :pictures, Basic.Posts.Comment
timestamps()
end
…
defmodule Basic.Posts.Comment do
use Ecto.Schema
import Ecto.Changeset
schema "comments" do
field :body, :string
field :post_id, :integer
+ has_many :posts, Basic.Posts.Post
timestamps()
end
…
defmodule BasicWeb.Router do
…
scope "/", BasicWeb do
pipe_through :browser
+ live "/pictures", PictureLive.Index, :index
+ live "/pictures/new", PictureLive.Index, :new
+ live "/pictures/:id/edit", PictureLive.Index, :edit
+ live "/pictures/:id", PictureLive.Show, :show
+ live "/pictures/:id/show/edit", PictureLive.Show, :edit
+
+ live "/posts", PostLive.Index, :index
+ live "/posts/new", PostLive.Index, :new
+ live "/posts/:id/edit", PostLive.Index, :edit
+ live "/posts/:id", PostLive.Show, :show
+ live "/posts/:id/show/edit", PostLive.Show, :edit
+
+ live "/comments", CommentLive.Index, :index
+ live "/comments/new", CommentLive.Index, :new
+ live "/comments/:id/edit", CommentLive.Index, :edit
+ live "/comments/:id", CommentLive.Show, :show
+ live "/comments/:id/show/edit", CommentLive.Show, :edit
get "/", PageController, :home
…
ここまでだと、通常PJと同様で、ブラウザでトップ/Scaffold先が普通に見れます(各Scaffold先に適当なサンプルデータを数件入れておいてください)
なお、フロント側の表示(posts
配下に comments
が並んだり、picture
が並ぶ)は、本来のアプリケーションであれば必須ですが、今回はマイクロサービス解説のみに限定します
Umbrellaでマイクロサービス化
Umbrella PJを作成し、その配下にモノリシックから切り離したいサービスを移動し、動作を確認してから、配下Phoenix PJ単位で別のPCやVM(Dockerなど)へサービス移動できます
別PC/VMが用意できない方は、同じPC内の別フォルダで体験いただいてもOKです(ただし、ポート番号が重ならない工夫が要ります)
サービス移動元でUmbrella PJを作成します
mix new a --umbrella
cd a/app
Umbrella構成だと、apps
フォルダ配下にPJフォルダを配置するので、apps
フォルダ配下に上記で作成したPhoenix PJをコピーします
deps
はUmbrella PJ直下で複数PJが共有する形となります
なお、apps
フォルダ配下で複数PJを作成できますが、その際は、config/
フォルダ配下のファイルに各PJ設定が追記書き込みされていきます(これにより、esbuildやtailwind、logger、json_library、import_configが重複し、エラーとなるので手動調整が必要ですが、これは別の機会に扱い、今回は割愛)
あと、今後のScaffoldもUmbrella PJ直下では無く、apps
フォルダ配下PJで実行します
なお複数Phoenixが配下にいても、Umbrella PJ直下からの起動は、全てのPhoenixを一斉に起動してくれます
では、2台目のPC/VM用のUmbrella PJを作りましょう
mix new b --umbrella
cd b/apps
mix phx.new basic
cd basic
mix ecto.create
この2台目からDB接続先を1台目のPCのIPアドレスに指定しておきましょう
import Config
# Configure your database
config :basic_2, Basic2.Repo,
username: "postgres",
password: "postgres",
- hostname: "localhost",
+ hostname: "172.24.82.141",
database: "basic_dev",
同一PC内であれば、Phoenixのポート番号を変更しておきましょう
import Config
…
config :basic_2, Basic2Web.Endpoint,
# Binding to loopback ipv4 address prevents access from other machines.
# Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
- http: [ip: {127, 0, 0, 1}, port: 4000],
+ http: [ip: {127, 0, 0, 1}, port: 4001],
…
Scaffoldをした上で、Phoenix起動します
mix phx.gen.live Memos Memo memos title:string body:text
mix ecto.migrate
cd ../..
iex -S mix phx.server
defmodule Basic2Web.Router do
…
scope "/", Basic2Web do
pipe_through :browser
+ live "/memos", MemoLive.Index, :index
+ live "/memos/new", MemoLive.Index, :new
+ live "/memos/:id/edit", MemoLive.Index, :edit
+ live "/memos/:id", MemoLive.Show, :show
+ live "/memos/:id/show/edit", MemoLive.Show, :edit
get "/", PageController, :home
…
ブラウザで http://localhost:4001
を確認しておきます(別PCやDockerであれば4000番のままでOK)
では、このPJ配下に、序盤で作成した pictures
をサービス移動するので、basic_web/live/picture_live
と `` を持っていきましょう
同一PC内であれば、下記コマンドです
cd ~
mkdir serive_b/basic_umbrella/apps/basic_web/lib/basic_web/live/
mv a/basic_umbrella/apps/basic_web/lib/basic_web/live/picture_live/ basic_b_umbrella/apps/basic_b_web/live/
routerを各々、変更します
defmodule BasicWeb.Router do
…
scope "/", BasicWeb do
pipe_through :browser
- live "/pictures", PictureLive.Index, :index
- live "/pictures/new", PictureLive.Index, :new
- live "/pictures/:id/edit", PictureLive.Index, :edit
- live "/pictures/:id", PictureLive.Show, :show
- live "/pictures/:id/show/edit", PictureLive.Show, :edit
-
+ live "/posts", PostLive.Index, :index
+ live "/posts/new", PostLive.Index, :new
+ live "/posts/:id/edit", PostLive.Index, :edit
+ live "/posts/:id", PostLive.Show, :show
+ live "/posts/:id/show/edit", PostLive.Show, :edit
+
+ live "/comments", CommentLive.Index, :index
+ live "/comments/new", CommentLive.Index, :new
+ live "/comments/:id/edit", CommentLive.Index, :edit
+ live "/comments/:id", CommentLive.Show, :show
+ live "/comments/:id/show/edit", CommentLive.Show, :edit
get "/", PageController, :home
…
defmodule BasicWeb.Router do
…
scope "/", BasicWeb do
pipe_through :browser
live "/memos", MemoLive.Index, :index
live "/memos/new", MemoLive.Index, :new
live "/memos/:id/edit", MemoLive.Index, :edit
live "/memos/:id", MemoLive.Show, :show
live "/memos/:id/show/edit", MemoLive.Show, :edit
+ live "/pictures", PictureLive.Index, :index
+ live "/pictures/new", PictureLive.Index, :new
+ live "/pictures/:id/edit", PictureLive.Index, :edit
+ live "/pictures/:id", PictureLive.Show, :show
+ live "/pictures/:id/show/edit", PictureLive.Show, :edit
get "/", PageController, :home
…
あとは、この切り離したPhoenix PJを、移動先のUmbrella PJ配下に移動します
実は、Elixir Umbrellaにおけるサービス移動は、たったこれだけです
Umbrellaでデマイクロサービス化
Phoenix PJごと、元のUmbrella配下に戻すだけです
この方式のメリット/デメリット
ここで注目したいのは、サービスを移動したにも関わらず、APIは1つも作っていないし、DB移動/移行も伴っていないという観点です
純粋な開発効率向上としては、このパターンで充分なケースも多くあるのですが、「マイクロサービス」と言うと、どうしても「それら全てを移動しなければならない」という固定観念があるようです
しかし、それは実は「技術スタックの自由度とエンジニアにかかる負荷を天秤にかけている」、もっと言えば「開発経済性をちゃんと計算した上で技術選定できているのか?」という疑問が残る領域だったりします
ただし、この方式には、下記のデメリットも存在します
- DBが移動していないことで、チームのマイクロ化は厳密には成されていない
- いわゆる「逆コンウェイの法則」は実現していない
- 同一DBに対してEctoサーバが2箇所のため、トランザクションの不整合があり得る
- 通常のマイクロサービスであれば、APIサーバ毎にトランザクションは閉じる
- もっともXAトランザクション的なことにそもそも不利なので程度問題かも?
- 通常のマイクロサービスであれば、APIサーバ毎にトランザクションは閉じる
- 分散されていないことに伴う性能劣化もあり得る
ここをカバーする仕組みは、続編で扱いたいと思います
とは言え、もっとも重要なのは、「サービス分割による開発効率向上」、マイクロサービスパターンで言うところの2つの「Decompose by XXXXXXX」を、API作成/DB移行不要で、Elixirはいとも簡単に叶えてしまうという点です
コンテナを使わずマイクロサービスを構成するメリット
- 仮想化層を含まないため、性能劣化が無く、メモリ利用がヘルシー
終わりに
Elixirにおけるマイクロサービス/デマイクロサービスが、いかにサービス移動がカンタンで、更にAPI Hellに陥らないかが実感できたでしょうか?
これは、LiveViewを使えば、SPA開発でAPI Hellに陥らない優位性のマイクロサービス版みたいなものです
次回は、このDB移動を伴うパターンについて、これまたElixirならではのテクニックについて解説します