LastaFluteで厳しすぎるRESTful API

フレームワーク提供のRESTとは?

本日リリースのLastaFlute-1.2.1にて、とうとう!フレームワーク提供のRESTful API対応をしました。

RESTful API対応ってどういうこと?わざわざフレームワークで対応する必要があるものなの?フレームワーク提供って何?って思われるかもですが...

LastaFluteは「規約ベースのURLマッピング」ですので、あえてURLの構成を不自由にしています。なので、素直にRESTのURLを実現するのはちょっと厳しいのです。


ただ、LastaFluteはあらゆるレイヤーで拡張ポイントを用意しているので、その規約を変えてアプリ独自にRESTful API対応することも可能です。

実際に拡張してRESTful APIされている現場もあるようです。(自分わりと最近聞いたばかりで「すごい!」と思いました^^)

そういう意味では、LastaFluteは「なんでもかんでも機能を提供する」って感じじゃなく、拡張ポイントで好きなように現場フィットしてもらうってスタイルのフレームワークではあります。


でも、LastaFlute自体がRESTを意識して調整をした方がよりRESTfulにできる部分も多いでしょうし、おおげさな拡張なしでできるに越したことはないので、フレームワーク提供としてRESTful API対応をしました。

どうせやるなら厳しく

LastaFluteらしいRESTful APIに仕上がっています。

REST崩れに厳しい

(おおよそ)オーソドックスと思われるRESTful APIから外れている場合は、いつものド派手例外で落ちます。

つまり、REST崩れなやり方は、(デフォルトでは)あまりできないようにしています。


他のフレームワークを使ったコードのレビューをしてるときに、RESTの機能使ってるのに「だいぶRESTじゃないぞ」ってのけっこうあるんですよね。

色々なケースがありました。

「RESTしたいんだけど合わないから崩してる、でも多少RESTしたい」とか...

「RESTをよくわかってなくて世のサンプルコードを参考にしてるだけ」とか...

ダメってわけじゃないかもしれませんが、LastaFluteの思想としてはそういう避けたいなって思いまして。

定義ミスに厳しい

また、Actionクラスで定義ミスとかすると、これまたド派手例外で落ちます。

新たにRESTという規約が実装に求められるようになるわけですから、その規約に合致してないコードになっていたら例外メッセージ読め読め大合唱になります。

例えば、ID指定の要らない POST なのに間違えてIDを定義しちゃった場合...
 o POST /products/ (本来はこう)
 o POST /products/3 (間違えてIDを受け取ってる)

Caused by: org.lastaflute.web.exception.ExecuteMethodIllegalDefinitionException: Look! Read the message below.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Different path parameter count of RESTful action method.

[Advice]
Make sure your parameter definition of RESTful action methods.
 o Full parameter method: Single GET, PUT, PATCH, DELETE e.g. /products/1/
 o Short parameter method: List GET, POST e.g. /products/

For example: ProductsAction /products/[1]/
  (x):
    get$index(Integer productId, ...Form form) { // *Bad (if list)
  (x):
    get$index() { // *Bad (if single)
  (x):
    post$index(Integer productId, ...Body body) { // *Bad
  (x):
    put$index(...Body body) { // *Bad
  (o):
    get$index(...Form form) { // list
    get$index(Integer productId) { // single
    post$index(...Body body) {
    put$index(Integer productId, ...Body body) {

...(略、まだまだデバッグするための情報いっぱい)


IDはIntegerを想定してるのに、一箇所だけ間違えて違う型(Longとか)で定義しちゃった場合...

...
Caused by: org.lastaflute.web.exception.ExecuteMethodIllegalDefinitionException: Look! Read the message below.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Different path parameter types of RESTful action methods.

[Advice]
Path parameter types should be same in one RESTful action.
For example: ProductsAction
  (x):
    get$index(Integer productId) {
    put$index(Long productId, ...Body) { // *Bad
  (o):
    get$index(Integer productId) {
    put$index(Integer productId, ...Body) { // Good

[RESTful Action]
class org.docksidestage.app.web.products.ProductsAction

[Different1 Method]
public JsonResponse<ProductsResult> ProductsAction@get$index(Integer)

[Different2 Method]
public JsonResponse<Void> ProductsAction@put$index(Long, ProductsPutBody)
* * * * * * * * * */

例外メッセージを読めば、RESTの規約が勉強できて、開発者が自力で直せるように。これはまさしくLastaFluteらしさですね。


「こんなチェック、よく実装したなー」

って思ってソースコード読みたいって気持ちになりますよね?

はい、どうぞ!

// RestfulStructuredMethodVerifier.java
https://github.com/lastaflute/lastaflute/blob/develop/src/main/java/org/lastaflute/web/path/restful/verifier/RestfulStructuredMethodVerifier.java

詳しくはドキュメントを

使い方の詳しくはこちらのドキュメントを:

// 規約縛りのRESTful API | LastaFlute
http://dbflute.seasar.org/ja/lastaflute/howto/action/larestfulapi.html


jfluteコラムも書いてありますので、LastaFlute関係なくそもそも「RESTful APIの設計ジレンマ」に関して興味のある方はぜひ(^^

「RESTのCRUD枠にユースケースを当てはめてるのか?」

「リソースはDBそのもの?DBインターフェース?」

きっかけは幸せな強い要望

なんで今までREST対応してなかったの?って思われる方も多いかもしれません。

きっかけは現場からの強い要望です。(ありがとうございます!)


RESTの対応した方が良いだろうという気持ちは長年持っておりましたが...

皆がRESTful APIするわけでもないので、作っても使ってもらえる見込みがないと高い品質で提供できず、後で盛大な手戻りになってしまう可能性が高いからです。

(何年も寝かしてから実はダメなところがあるって発覚しても、誰が使ってるか?ってわからないので機能変更もしづらくがんじがらめになるのです)


LastaFluteは潤沢なブランド力があるわけでもないので、機能を出せば使ってもらえてすぐにフィードバックがもらえて安心というわけにはいきません。

なので、それなりにインパクトのある機能に関しては、実際に使ってもらえるユーザーが見つかるまでは保留せざるを得ないという事情があるのです。


なので今回...

現場から「使うから作って!」って言ってもらえたのは、個人フレームワークとしては幸せなこと

でした。


まあ、だいぶ缶詰プログラミングしましたけど(^^。


f:id:jflute:20210816233341j:plain