Railsから別のフレームワーク(Flask)を使う選択をしたときに考慮したこと、あとから気づいた反省点
[!] このスクラップは更新中です。途中で内容が変更・修正される可能性が非常に高いです。ご注意下さい。
はじまり
Railsはいろいろな機能をもともと備えていて、RailsWayに乗ればその恩恵に授かれるようになっている。しかし、Railsに頼りすぎるがあまり、Rails以外のフレームワークを利用してWebアプリケーション開発していくときに考慮しなければならないことというのを、自分の場合は複数見落としてしまっていた。この記事では、その見落としていたことたちを書きなぐっていこうと思っている。
そのプロジェクトで自分が使用した別のフレームワークはPythonのFlaskというもの。FlaskはRubyでいうSinatraというライブラリに近いかもしれない。いわゆるスモールフレームワークと呼ばれるもので、もともと搭載されている機能は少なく、カスタマイズ性が高い。それゆえに、Railsを使っていたときは意識していなかったことを意識せざるを得ない場面が良くも悪くも増える。
今回のプロジェクトで、自分たちのチームは、「Flask + Vue.js + (Flutter)」という技術選定でアプリケーション開発を開始した。FlaskをRESTfulAPIとして機能させる、いわゆるSPA構成だ。
今回のように、一般公開向けのAPI開発でなく、SPAのために社内のみで利用されるRESTfulAPIを開発するとき、フロントエンドエンジニアやアプリケーションエンジニアにとっては、そのAPIは「状況に応じたHTTPリクエスト、ボディを送れば、ビジネスロジックの調整やデータ永続化などに関してはうまくやっていてくれたのちに、適切なJSONを返してくれる」ものであることを望んでいる(と思う)。
そのことを踏まえて、大きく以下のようにAPI実装に対してまずはじめに懸念すべき主要事項をわけた。
- ルーティング
- リクエストパーサーによる値の受け取り
- ビジネスロジックの実装
- 値のバリデーション(ビジネス要件・永続化)
- 値の永続化(DB保存)
- シリアライズした値の返答(JSON)
実際は、認証やDB設計、環境に応じたConfig設定や、メール送信、CORS対応など、個別に対応が必要な事項は多岐にわたるが、一旦上の6つに絞ろうと思う。その理由は、この上の6つが「クライアントリクエストからサーバーレスポンスまでの一連のフロー」として必要なものと考えられ、そしてそのフローを適切にコントロールすることが、Webアプリケーションの設計において、非常に重要になってくるからだ。
Webアプリケーションにおいて、設計(アーキテクチャ)は最重要事項の一つだと思う。これはFlaskを使って開発を続ける中で、気付かされたことだ。今から書こうとしているコードの役割さえ理解していれば、適切な設計があったとき、設計が該当のディレクトリやファイルまで導いてくれる。逆に、設計が曖昧で、「どこになにを書いてもいい」とき、「どこに書けばいいか」「この場所にはなにを書いていこうか」を常に判断させられることになる。結果、とりあえず全ての複雑な処理を順次処理で扱うような「サービスクラス」が生まれ、あとから読み直したとき、非常に手が加えにくいものになってしまうという自体に陥りやすくなる。
ここからは、各事項について、それぞれ比べながら見ていくこととする。
- ルーティング について
Railsの場合は、ルーティングについての記述を行えるroutes.rbというファイルが最初から存在するが、Flaskを利用するさいは、たとえばroutes.pyなどといったファイルは存在しない。
とはいえ、結局のところ、Flaskを利用する場合でも、RESTfulAPIを開発する上ではルーティング・エンドポイントについては一箇所にまとまっていてほしいので、自分の場合は、routes.pyというファイルを作り、そのファイル内で、「このエンドポイントは、このコントローラーで処理して」という記述をまとめて書き、一つの関数にして保存しておいた。
def set_routes(api):
# ---------- /admin ----------#
api.add_resource(admin_apis.HogeAPI, '/admin/hoge')
# ...... #
end
みたく。
ルーティングについては、エンドポイントの命名と、あるエンドポイントに送られてきたHTTPリクエストを解釈し、必要なモジュール(コントローラ)に対して受け渡す責務を負っている。
後者については、ほとんどのライブラリでサポートされているので、そのライブラリが求める実装に乗ればいいでしょう。Railsの場合は、route.rbにまとめて記載する( only: [:index] みたく)ケースが多いですね。Flaskの場合は、コントローラ側で、受け取るHTTPメソッドを記述する形になってます。
class TestAPI(API):
def get(self)
# .... #
end
そして、前者のエンドポイントの命名については、Flask、Railsともに悩むポイントは同じになってくると思ってます。基本的なものはRESTful通りに作ればいいでしょうが、「固有のユースケース」などに対しては、エンドポイントも固有に作成することで対応するのが自分はいいと思っています。
たとえば、ユーザー検索のエンドポイントは
GET /users/search?name=bob
といったように、searchといった動詞をエンドポイントに含ませる形が慣習的に多いですね。 社内用でもあるので、特定のエンドポイントが様々なユースケースに対応する形よりも、ユースケースに応じてエンドポイント自体を多くしたほうが「現存のAPIでフロントが勝手なことしたからユーザーが消えちゃったよ!」みたいな事故が減りやすくなるため、メリットが大きいと思っています。
自分の場合は、/call, /send など、たくさん設定していっています、 デメリットは、APIが煩雑になることでしょう。もし煩雑性を回避しながら、エンドポイントを減らせるならそれがベストです。ただし、「フロントエンド側」の手違いにより、大きな問題(たとえばデータ不整合や500エラー返答)が起きてしまうぐらいならば、おとなしくエンドポイントを増やしたほうがいいというのが自分の見解です。このあたりは、チームと相談していきながら決めるのがよいでしょうか。
適切な命名による制限をうまく使っていきたいですね。