LoginSignup
7
1

Elixirマイクロサービス②:Umbrellaでサービス移動が容易かつAPI地獄にならないマイクロサービス/デマイクロサービス

Last updated at Posted at 2023-12-25

この記事は、Elixir Advent Calendar 2023 シリーズ11 の18日目です

piacere です、ご覧いただいてありがとございます :bow:

前回は、マイクロサービスの現状と「マイクロサービスパターン」に触れた後、Elixirがマイクロサービス構築に有利な面の紹介 をしましたが、今回は、Elixirマイクロサービス化(デマイクロサービス化も)の有力な機能の1つである「Unmbrella」について実践交えて解説します
image.png

あと、このコラムが、面白かったり、役に立ったら、image.png をお願いします :bow:

事前準備:モノリシックからスタートする

マイクロサービス/デマイクロサービスの経過をより実感しやすくするため、最初は、モノリシックな構成からスタートすることとしましょう

ブログとそのコメント、それらから参照される画像ストアの3つのサービスを持ち、認証も伴う想定でいきましょう(本来であればログインユーザーで投稿者やコメント者を管理/認証するところですが今回は割愛)

モノリシックなPhoenix PJを作成し、Phoenix起動します

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

下記コード修正も行ってください

apps/basic/lib/posts/post.ex

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

apps/basic/lib/posts/comment.ex
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

apps/basic_web/router.ex
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先に適当なサンプルデータを数件入れておいてください)
image.png

なお、フロント側の表示(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を作りましょう

【2台目のPC/VM】
mix new b --umbrella
cd b/apps
mix phx.new basic
cd basic
mix ecto.create

この2台目からDB接続先を1台目のPCのIPアドレスに指定しておきましょう

config/dev.exs【2台目のPC/VM】
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のポート番号を変更しておきましょう

config/dev.exs【2台目のPC/VM相当】
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起動します

【2台目のPC/VM】
mix phx.gen.live Memos Memo memos title:string body:text
mix ecto.migrate
cd ../..
iex -S mix phx.server
apps/basic_2/lib/basic_2_web/router.ex【2台目のPC/VM】
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)
image.png

では、このPJ配下に、序盤で作成した pictures をサービス移動するので、basic_web/live/picture_live と `` を持っていきましょう

同一PC内であれば、下記コマンドです

1台目のPC/VM
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を各々、変更します

apps/basic_web/lib/basic_web/router.ex(1台目のPC/VM)
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

apps/basic_web/lib/basic_web/router.ex(2台目のPC/VM)
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におけるサービス移動は、たったこれだけです :stuck_out_tongue_winking_eye:

Umbrellaでデマイクロサービス化

Phoenix PJごと、元のUmbrella配下に戻すだけです

この方式のメリット/デメリット

ここで注目したいのは、サービスを移動したにも関わらず、APIは1つも作っていないし、DB移動/移行も伴っていないという観点です

純粋な開発効率向上としては、このパターンで充分なケースも多くあるのですが、「マイクロサービス」と言うと、どうしても「それら全てを移動しなければならない」という固定観念があるようです

しかし、それは実は「技術スタックの自由度とエンジニアにかかる負荷を天秤にかけている」、もっと言えば「開発経済性をちゃんと計算した上で技術選定できているのか?」という疑問が残る領域だったりします

ただし、この方式には、下記のデメリットも存在します

  • DBが移動していないことで、チームのマイクロ化は厳密には成されていない
    • いわゆる「逆コンウェイの法則」は実現していない
  • 同一DBに対してEctoサーバが2箇所のため、トランザクションの不整合があり得る
    • 通常のマイクロサービスであれば、APIサーバ毎にトランザクションは閉じる
      • もっともXAトランザクション的なことにそもそも不利なので程度問題かも?
  • 分散されていないことに伴う性能劣化もあり得る

ここをカバーする仕組みは、続編で扱いたいと思います

とは言え、もっとも重要なのは、「サービス分割による開発効率向上」、マイクロサービスパターンで言うところの2つの「Decompose by XXXXXXX」を、API作成/DB移行不要で、Elixirはいとも簡単に叶えてしまうという点です

コンテナを使わずマイクロサービスを構成するメリット

  • 仮想化層を含まないため、性能劣化が無く、メモリ利用がヘルシー

終わりに

Elixirにおけるマイクロサービス/デマイクロサービスが、いかにサービス移動がカンタンで、更にAPI Hellに陥らないかが実感できたでしょうか?

これは、LiveViewを使えば、SPA開発でAPI Hellに陥らない優位性のマイクロサービス版みたいなものです

次回は、このDB移動を伴うパターンについて、これまたElixirならではのテクニックについて解説します

p.s.このコラムが、面白かったり、役に立ったら…

image.png にて、どうぞ応援よろしくお願いします :bow:

7
1
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
7
1