Feign使用上の注意点からRESTFULインタフェース設計仕様へ

55353 ワード

最近のプロジェクトではSpring Cloud Feignを使ってhttpインタフェースをドッキングし、多くの穴を踏んで、RESTFULインタフェースの設計に対する考えも生まれています.
SpringMVCの要求パラメータバインドメカニズム
Feignの歴史を知る友人には、Feign自体がNetflixの製品であり、Spring Cloud Feignは原生Feignをベースにパッケージ化され、大量のSpringMVC注釈サポートが導入されていることがわかります.この点で、広範なSpringユーザーに開梱されやすくなりますが、小さな混同作用も生じています.そこでSpring Cloud Feignを使用する前に、SpringMVCのパラメータメカニズムを紹介します.RestControllerをプリセットし、httpリクエストを受信するためのアプリケーションをローカルの8080ポートで開始します.
1
2
3
4
5
6
7
8
9
@RestController
public class BookController {

    @RequestMapping(value = "/hello") // <1>
    public String hello(String name) { // <2>
        return "hello " + name;
    }

}

このインタフェースは非常に簡単に書かれていますが、実際のspringmvcは非常に多くの互換性を持っており、このインタフェースが多くのリクエスト方式を受け入れることができます.
<1>RequestMappingはマッピングのパスを表し,GET,POST,PUT,DELETE方式を用いてその端点にマッピングできる.
<2>SpringMVCでよく使われるリクエストパラメータ注記(@RequestParam,@RequestBody,@PathVariable)などがあります.nameは@RequestParamとしてデフォルト設定されています.パラメータString nameは、フレームワークによってバイトコード技術を用いてnameという名前を取得し、要求パラメータのkey値がnameであるパラメータを自動的に検出し、@RequestParam("name")を用いて変数自体の名前を上書きすることもできる.urlにnameパラメータまたはformフォームにnameパラメータを携帯すると、取得されます.
1
2
3
4
5
POST /hello HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded

name=formParam

または
1
2
GET /hello?name=queryString HTTP/1.1
Host: localhost:8080

Feignの要求パラメータバインドメカニズム
上記のSpringMVCパラメータバインドメカニズムは、よく知られているはずですが、Feignでは少し違います.
非常に簡単なインタフェースの書き方を見てみましょう.
1
2
3
4
5
6
7
8
//  :       
@FeignClient("book")
public interface BookApi {

    @RequestMapping(value = "/hello",method = RequestMethod.GET)
    String hello(String name);

}

要求アドレスの設定:
1
2
3
4
5
6
7
ribbon:
  eureka:
   enabled: false

book:
  ribbon:
    listOfServers: http://localhost:8080

SpringMVCのRestControllerを書く習慣に従ってFeignClientを書きましたが、私たちの最初の考えでは、リクエスト方式がGETであることが指定されているので、nameはQueryStringとしてUrlにつづられるのではないでしょうか.このようなGETリクエストを発行します.
1
2
GET /hello?name=xxx HTTP/1.1
Host: localhost:8080

実際、RestControllerは受信していません.RestController側のアプリケーションでいくつかのヒントを得ました.
サービス側DEBUG情報
所望のGET方式で要求を送信のではなく、POST方式である.
nameパラメータはカプセル化されずnull値が得られた.
ドキュメントを見ると、デフォルトの注記を付けないとFeignはパラメータのデフォルトに@RequestBody注記を付けますが、RequestBodyはリクエストボディに含まれているに違いありません.GET方式では含めることができません.だから上記の2つの現象は説明された.Feignは、GET要求にRequestBodyが含まれている場合、エラーではなくPOST要求に強制的に移行する.
この機構を理解するとFeignインタフェースを開発して多くのピットを避けることができる.この問題を解決するのも簡単です
Feignインタフェースでnameに@RequestParam(「name」)注記を追加します.nameは、SpringMVCバイトコードのメカニズムを使用して、Feignの要求パラメータにデフォルトの名前が自動的に与えられないことを指定する必要があります.
Feignでは@RequestBodyがデフォルトで使用されているため、RestControlを改造して@RequestBodyで受信することもできます.ただし、要求パラメータは通常複数であり、上記の@RequestParamを使用することを推奨しますが、@RequestBodyは一般的にオブジェクトの転送にのみ使用されます.
Feignバインド複合パラメータ
要求パラメータのタイプと要求方式を指定し,上記の問題の発生は実際にFeignの内部機構を理解していない前提でSpringMVCと当然類比したためである.同様に、パラメータとしてオブジェクトを使用する場合も、このような問題に注意する必要があります.
このようなインタフェースに対して
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@FeignClient("book")
public interface BookApi {

    @RequestMapping(value = "/book",method = RequestMethod.POST)
    Book book(@RequestBody Book book); // <1>
  
    @RequestMapping(value = "/book",method = RequestMethod.POST)
    Book book(@RequestParam("id") String id,@RequestParam("name") String name); // <2>
  
    @RequestMapping(value = "/book",method = RequestMethod.POST)
    Book book(@RequestParam Map map); // <3>
  
    //     
  	@RequestMapping(value = "/book",method = RequestMethod.POST)
    Book book(@RequestParam Book book); // <4>

}

<1>@RequestBodyを使用してオブジェクトを渡すのが最も一般的です.
<2>パラメータがそれほど多くない場合は、@RequestParamをフラットに使用できます.
<3>Mapを使用することも可能であるが,オブジェクト指向の考え方にはあまり合致せず,コードからそのインタフェースにどのようなパラメータが必要かをすぐに見ることはできない.
<4>誤った使い方で、Feignはこのようなメカニズムを提供していません.
Feignで@PathVariableとRESTFUL仕様を使用
これはRESTFULインタフェースをどのように設計するかに関する話題で、私たちはRESTFULが2000年初めに提出されてから、資源、契約規範、CRUD対応の削除・変更操作などについての文章が少なくないことを知っています.次に筆者は2つの実際のインタフェースから自分の見方を話します.
idに基づいてユーザーインタフェースを検索するには、次の手順に従います.
1
2
3
4
5
6
7
@FeignClient("user")
public interface UserApi {

    @RequestMapping(value = "/user/{userId}",method = RequestMethod.GET)
    String findById(@PathVariable("id") String userId);

}

これは論争がないはずですが、前に強調したように、@PathVariable("id")の括弧の中のidは忘れてはいけません.「メールボックスに基づいてユーザーを検索する」としたら?このようなインタフェースを無意識に書く可能性が高い.
1
2
3
4
5
6
7
@FeignClient("user")
public interface UserApi {
  
    @RequestMapping(value = "/user/{email}",method = RequestMethod.GET)
    String findByEmail(@PathVariable("email") String email);

}

まずFeignの問題を見てみましょう.Emailには通常'.'という特殊な文字が含まれており、パスに含まれると予想外の結果が出ます.私はそれをどのように解決するかを検討したくありません(実際には{email:.+}を使用することができます).これは設計に合わないと思います.仕様の問題について説明します.この2つのインタフェースは似ているかどうか、emailはpathに配置されるべきかどうか.これはRESTFULの初心について話します.なぜuserIdという属性はRESTFULパスに現れるのに適していると一般的に考えられていますか.id自体がリソースの位置付けの役割を果たしているため、彼はリソースのマークです.emailは異なり、それは唯一かもしれませんが、より多くは、リソースです.のプロパティですので、パスに非位置決めの動的パラメータが現れるべきではないと思います.emailを@RequestParamパラメータとします.RESUFTL構造化クエリー
筆者はFeignの話題からRESTFULインタフェースの設計問題に至るまで成功し,本稿の紙面が長くなったが,もう一つの文章を書くつもりはない.
もう一つのインタフェースの設計を考えて、ある月のあるユーザーの注文を問い合わせると、ページングパラメータを携帯する可能性があります.このときパラメータは多くなります.従来の設計によると、これはクエリー操作であるべきです.つまり、GET要求に対応しているはずです.それは、これらのパラメータをurlにつなぐことを意味しているのではないでしょうか.Feignを考えてみると、本稿の第2段で述べたように、GEはサポートされていませんTは実体類の持ち込みを要求し、これは私たちの設計を困難な状況に陥らせた.実際にいくつかのDSL言語の設計、例えばelasticSearchを参考にして、POST JSONの方式を使って検索を行ったので、実際のプロジェクトの中で、筆者は特にCRUDと4種類の要求方式に対応するこのようないわゆるRESTFUL規範を愛用していない.設計RESTFULはどんな規則に従うべきかといえば範、それは契約規範や分野駆動設計などの別の名詞かもしれません.
1
2
3
4
5
6
7
@FeignClient("order")
public interface BookApi {

    @RequestMapping(value = "/order/history",method = RequestMethod.POST)
    Page> queryOrderHistory(@RequestBody QueryVO queryVO);

}

RESTFUL動作限定
実际のインタフェースの设计の中で、私はこのような需要に出会って、ユーザーのモジュールのインタフェースはユーザーのパスワードを修正することを支持する必要があって、ユーザーのメールボックスを修正して、ユーザーの名前を修正して、笔者は前に1篇の文章を読んだことがあって、CRUDを舍ててて分野の駆动の设计でRESTFULインタフェースの定义を规范することを言って、プロジェクトの中で私の考えと一致しません.ボリュームクラスの3つのプロパティは、次のように設計できます.
1
2
3
4
5
6
7
@FeignClient("user")
public interface UserApi {

    @RequestMapping(value = "/user",method = RequestMethod.PUT)
    User update(@RequestBody User user);

}

しかし、実際には、この3つの機能に必要な権限が一致しているのか、本当に1つのインタフェースに置くべきなのか、実際には、インタフェース呼び出し側がエンティティを渡すことを望んでいない.このような行為は制御不可能であり、どのような属性を修正したのか、本当に行を制限するなら、まったく分からないからだ.操作タイプのフィールドをUserに追加し、インタフェース実装側で検証するのは面倒ですが、実際には、仕様の設計は次のようになると思います.
1
2
3
4
5
6
7
8
9
10
11
12
13
@FeignClient("user")
public interface UserApi {

    @RequestMapping(value = "/user/{userId}/password",method = RequestMethod.PUT)
    ResultBean updatePassword(@PathVariable("userId) String userId,@RequestParam("password") password);
    
    @RequestMapping(value = "/user/{userId}/email",method = RequestMethod.PUT)
    ResultBean updateEmail(@PathVariable("userId) String userId,@RequestParam("email") String email);
    
    @RequestMapping(value = "/user/{userId}/username",method = RequestMethod.PUT)
    ResultBean updateUsername(@PathVariable("userId) String userId,@RequestParam("username") String username);

}

一般的にRESTFULインタフェースに動詞が現れるべきではない.修正操作の推奨使用の要求方式はPUT であるべきである.
password,email,usernameはすべてuserの属性であり、userIdはuserの識別記号であるため、userIdはPathVariableの形でurlに現れ、3つの属性はReqeustParamに現れる.論理削除について
1
2
@RequestMapping(value = "/user/{userId}",method = RequestMethod.DELETE)
ResultBean updateEmail(@PathVariable("userId") String userId);

まとめ
本文はFeignの使用上の注意点から、RESTFULインタフェースの設計問題について話したが、実は互いに補完する行為である.インタフェースの設計にはキャリアが必要であるため、私はFeignのインタフェーススタイルで自分のRESTFUL設計に対する理解を話したが、Feignの中のいくつかのピットも、まさに私がRESTFUL設計を規範化したい出発点である.RESTFUL設計に対する異なる理解があれば、私との溝を歓迎する通じる.