9
2

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 3 years have passed since last update.

「BFFを40秒で支度しな」を叶える(Phoenix軽量APIで):複数APIを呼び出して自前JOIN (Inner JOIN→Outer JOINのオマケ付き)

Last updated at Posted at 2020-04-07

fukuoka.ex/kokura.exのpiacereです
ご覧いただいて、ありがとうございます :bow:

前作は、Phoenix軽量APIを使ったBFF的API構築を、40秒という短時間で構築しましたが、今度は、複数APIを呼び出し、自前でJOINする、本物のBFFを作ります

今回も、「40秒で支度しな」、で行きます:yum:
image.png

なお「BFF」は、「Backend for Frontend」の略で、フロントエンド毎でプレーンAPI呼出を前提としてロジックを書くのを止め、APIファサードでビジネスロジックを共通提供する考え方で、これにより、フロントエンド毎にビジネスロジックを開発するムダを省きます(詳しくは下記)

BFF(Backends For Frontends)超入門 ― Netflix、Twitter、リクルートテクノロジーズが採用する理由
https://www.atmarkit.co.jp/ait/articles/1803/12/news012.html

本コラムの検証環境

本コラムは、以下環境で検証しています(Windowsで実施していますが、Linuxやmacでも動作する想定です)

本内容の実施前提として、下記コラムの「事前準備A~E」は終えておいてください

「BFF的APIを40秒で支度しな」:Phoenix軽量APIでScaffoldした2テーブルをJOIN
https://qiita.com/piacerex/items/2e4e6629222700bf3756

更に、本コラム中盤以降の「事前準備F」も終えておいてください

手順①【0秒~36秒】:JSONテンプレートを書く

ここから、40秒間の本番です

投入済みのDBデータを、API2本の呼び出しで取得し、team_idの一致でJOINに相当するデータ処理を行うだけです

lib/bff_web/templates/api/v1/bff/team_members/index.json.eex
users = Json.get( "http://localhost:4000", "/users" )
teams = Json.get( "http://localhost:4000", "/teams" )
users[ "data" ]
|> Enum.map( fn 
	%{ 
		"name"    => name, 
		"age"     => age, 
		"team_id" => team_id 
	} 
	-> 
	team = teams[ "data" ]
		|> Enum.filter( & &1[ "id" ] == team_id )
		|> List.first
	%{ 
		"name" => name, 
		"age"  => age, 
		"team" => team[ "name" ], 
		"url"  => team[ "url" ]
	} 
end )

手順②【36秒~40秒】:動作確認

RESTクライアントで「GET http://localhost:4000/api/v1/bff/team_members」のアクセスを行うと、以下のように、JOIN済みデータ一覧が、JSON返却されます
image.png

「今回も、これだけで完成です」

はい、今回もたったこれだけ、40秒でBFF構築が完了しました

前回同様、JSONテンプレートをいじるだけで、カスタマイズやメンテナンスが可能ですし、APIバージョンアップやエントリーポイント変更も、フォルダ移動するだけだし、別のBFFを生やしたいときも、フォルダ掘って、JSONテンプレート置くだけです(ルーティング/MVCは不要)

ちなみに、シリーズ中の下記残対応についても、今回、検証できました

■全体バランス取りなど

  • Mnesiaの独自実装では無く、Ecto Queryでロジックを組んだときの配置は大丈夫か?

ここまでの連載で感じて欲しいこと

軽量APIは、JSONテンプレートを追加するだけでAPI/BFF構築できる点が特徴ですが、JSONテンプレートの中身を、今一度、見てください

Elixirプログラミングに慣れている方であれば、このJSONテンプレートの記述が、

「実に、Elixirらしいデータ処理開発に、集中できている」

という点に気付かれたのでは無いかと思います

そう、このPhoenix軽量APIは、PHPのような気軽さを提供しつつ、余計な雑事に煩わされること無く、

よりElixirらしいコードに集中したい

という、「Elixir大好きオタク」的エッセンスが、パッケージングされているのです:stuck_out_tongue_winking_eye:

「ところで、team_idがマッチしない場合はどうなる?」

前回は、Ecto.Queryの機能で、Inner JOINがSQL同様、暗黙でかかり、マッチしたもの同士だけが取得できましたが、今回のBFFでは、どうでしょう?

試しに、「PUT http://localhost:4000/users/4」で、存在しないteam_idに書き換えてみましょう
image.png

「GET http://localhost:4000/api/v1/bff/team_members」で確認すると、Outer JOINしていることが分かります
image.png

ここをInner Joinに変更すると、下記のようになります

リストでnilだったものを最後にEnum.filter()で除外するところがポイントです

lib/bff_web/templates/api/v1/bff/team_members/index.json.eex
users = Json.get( "http://localhost:4000", "/users" )
teams = Json.get( "http://localhost:4000", "/teams" )
users[ "data" ]
|> Enum.map( fn 
	%{ 
		"name"    => name, 
		"age"     => age, 
		"team_id" => team_id 
	} 
	-> 
	team = teams[ "data" ]
		|> Enum.filter( & &1[ "id" ] == team_id )
		|> List.first
	if team != nil do
		%{ 
			"name" => name, 
			"age"  => age, 
			"team" => team[ "name" ], 
			"url"  => team[ "url" ]
		} 
	end
end )
|> Enum.filter( & &1 != nil )

「GET http://localhost:4000/api/v1/bff/team_members」で確認すると、Inner Joinになりました
image.png

付録【事前準備F】:2テーブル分のAPI CRUDをScaffold

Phoenix標準機能のScaffold(mix phx.gen.html)を使って、2テーブル分のAPI CRUDを自動生成します

なお、テーブルおよびテストデータは、前回投入したものを、そのまま流用するので、前回のWeb CRUDを上書きするようにScaffoldします

Phoenixを一度落とし、下記コマンドでScaffoldします

mix phx.gen.json Api User users name:string age:integer team_id:integer
Would you like to proceed? [Yn] Y
Proceed with interactive overwrite? [Yn] Y
lib/bff_web/controllers/user_controller.ex already exists, overwrite? [Yn] Y
lib/bff_web/views/user_view.ex already exists, overwrite? [Yn] Y
test/bff_web/controllers/user_controller_test.exs already exists, overwrite? [Yn] Y

mix phx.gen.json Api Team teams name:string url:string
Would you like to proceed? [Yn] Y
Proceed with interactive overwrite? [Yn] Y
lib/bff_web/controllers/team_controller.ex already exists, overwrite? [Yn] Y
lib/bff_web/views/team_view.ex already exists, overwrite? [Yn] Y
test/bff_web/controllers/team_controller_test.exs already exists, overwrite? [Yn] Y

前回のWeb CRUDを、API CRUD2用のルーティングで上書きします

なお、CSRF対策を解除する必要もあるので、「:protect_from_forgery」のコメントアウトも行ってください

lib/bff_web/router.ex
defmodule BffWeb.Router do

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
#    plug :protect_from_forgery  # <-- remove here
    plug :put_secure_browser_headers
  end

  scope "/", BffWeb do
    
    resources "/users", UserController, except: [:new, :edit]  # <-- replace here
    resources "/teams", TeamController, except: [:new, :edit]  # <-- replace here
  end

Phoenix起動します(マイグレーションは不要です)

iex -S mix phx.server

一応、API単品の動作確認をしておきます

RESTクライアントで、下記データを、「POST http://localhost:4000/users」で追加しましょう

{
    "user": 
    {
        "name": "hisaway",
        "age": 23,
        "team_id": 2
    } 
}

image.png

「GET http://localhost:4000/users」でデータが追加されたことが確認できます
image.png

「GET http://localhost:4000/teams」の一覧も問題無いことも確認します
image.png

終わり

Phoenix軽量APIを使い、JSONテンプレート追加だけで、BFFを構築できました

また、Outer JOINからInner JOINに切り替える、JSONテンプレートのカスタマイズも行いました

40秒くらいで、本当に構築できるということが実感いただけたら幸いです

そして、この軽量APIの裏に流れる、

よりElixirらしいコードに集中したい

という、「Elixir大好きオタク」ならではのマインドを感じていただけたら嬉しいです

次回は、軽量APIのREST API対応に戻り、下記の残対応に着手します

■エラー処理 ※JSON内でラクに書く?

  • show.json.eex/update.json.eex/delete.json.eexのデータ未存在時の404エラー
  • サーバエラー時の500エラー

■コード最適化余地

  • JSONテンプレート評価の共通関数化(ApiView.render()と、ApiController.execute()が同じ処理)
  • :mnesia.start~:mnesia.wait_for_tables()の共通関数化
  • ApiControllerの各関数で、パス処理が同じなので、その共通関数化
  • DbMnesiaの返却の共通関数化
  • index.json.eex/show.json.eexやApiController.execute()のファイルパス指定を無くしたい

■全体バランス取りなど

  • どこまでをAPIコントローラ側に置き、どこまでをJSONテンプレートに置くか?
  • Mnesiaの独自実装では無く、Ecto Queryでロジックを組んだときの配置は大丈夫か?
  • APIでは無く、WebアプリやLiveViewでも、JSONテンプレートを応用できないか?

■Mnesiaアクセッサの不足機能補充 ※どこまでマジメにやる?

  • index.json.eex/show.json.eexのselect列指定対応
  • insert.json.eexのSQLでのinsert列指定対応
  • update.json.eexのSQLでのupdate対象列順変動/全列指定無し対応
  • whereの「=」以外対応
  • id以外のwhere対応(Mnesiaでそもそも実現できるかしら?)

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

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

9
2
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
9
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?