2016-10-01 22 views
6

私はJavaの新しい残りのAPIで開発を開始しようとしています。 私の質問は、パッチの使用についてです - なぜですか?Java REST APIで、PATCHとPUTを使用してエンティティを更新する

次のリクエストで

POST http://localhost:8080/addresses 

は、我々は新しいアドレスを作成するにはAddress.java

public class Address { 

    @Id 
    private Long id 

    @NotNull 
    private String line1; 

    private String line2;  //optional 

    @NotNull 
    private String city; 

    @NotNull 
    private String state; 
} 

という名前のエンティティを持っている、と言うことができます、私は、このHTTPリクエストを行うだろう

{ 
    "line1" : "mandatory Address line 1", 
    "line2" : "optional Address line 2", 
    "city" : "mandatory City", 
    "state" : "cd" 
} 

作成されたレコードのIDが1であると仮定します。

対応@RestController AddressResource.javaは、この方法を有することになる。

@PostMapping(value = "/addresses") 
public ResponseEntity<Address> create(@valid Address newAddress) { 
    addressRepo.save(newAddress); 
} 

@Validは、エンティティテーブルにデータを格納する前に有効であることを確認します。

ここで、私は上のアパートから通りの家に移動します。私はPATCHを使用する場合は、要求ペイロードと

PATCH http://localhost:8080/addresses/1 

次のようになります。

{ 
    "line1" : "1234 NewAddressDownTheStreet ST", 
    "line2" : null 
} 

@RestController対応する方法は次のようになります。あなたがテーブルを照会すると

@PatchMapping(value = "/addresses/{id}") 
public ResponseEntity<Address> patchAddress(@PathVariable Long id, Address partialAddress) 
{ 
    Address dbAddress = addressRepo.findOne(id); 
    if (partialAddress.getLine1() != null) { 
     dbAddress.setLine1(partialAddress.getLine1()); 
    } 
    if (partialAddress.getLine2() != null) { 
     dbAddress.setLine2(partialAddress.getLine2()); 
    } 
    if (partialAddress.getCity() != null) { 
     dbAddress.setCity(partialAddress.getCity()); 
    } 
    if (partialAddress.getState() != null) { 
     dbAddress.setState(partialAddress.getState()); 
    } 

    addressRepo.save(dbAddress) 
} 

は今、」勝ちました私の住所は?

"line1" : "1234 NewAddressDownTheStreet ST", 
"line2" : "optional Address line 2",  <-- INCORRECT. Should be null. 
"city" : "mandatory City", 
"state" : "cd" 

上記の更新によって、line2の値が正しくないことがわかります。 これは、Javaでは、Addressクラスのすべてのインスタンス変数が、クラスがインスタンス化されるときにnull(またはプリミティブの場合はデフォルトの初期値)に初期化されるためです。したがって、nullに変更されるline2とデフォルト値を区別する方法はありません。

質問1)これを回避する標準的な方法はありますか?


もう一つの欠点は、私がエントリポイントで要求を検証するために@Validアノテーションを使用することができない、ということである - それは一部だけですだって。したがって、無効なデータがシステムに入る可能性があります。 、それは失敗しないだろう

@Min(0) 
@Max(100) 
private Integer lengthOfResidencyInYears, 

を、ユーザーが誤って(彼らは本当に19年を意味したときに)190を入力:

例えば、追加のフィールドは、次のように定義していたと想像。


PATCHではなく、PUTを使用した場合、クライアントは完全なアドレスオブジェクトを送信する必要があります。 は、これは私が1のGETが常に更新を行う前に、なぜだろうではない1回の使用PUTを行わなければならないという前提を作る場合はアドレスが実際に


有効であることを確認するために@Validを使用できるという利点を有しますオーバーパッチ? 何か不足していますか?

別に

私の結論は、私は静的型付け言語のラインJavaやC#からそれを使用するすべての利点を見ることができないように動的型付け言語を使用して、開発者がパッチを使用しての支持者であるということです。それはちょうど複雑さを加えるようです。

+2

は、PATCHリクエスト後に、dbAddress.getLine2()!= nullをチェックしているため、dbクエリで行「省略可能なアドレス行2」を表示する必要があります。 – kuhajeyan

+0

パッチ要求には、サーバーが一部のリソースの状態Aを状態Bに変換するために使用できるクライアントによって計算された命令が含まれている必要があり、単純化された部分的な更新ではありません。さらに読む:[SOドキュメント](http://stackoverflow.com/documentation/http/3423/http-for-apis/11812/edit-a-resource#t=201610031245323472567)と[良いブログ投稿](http: /williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/) –

+0

これは良い質問です。 line2のnullについては、手動でフィールドをマップし、上書きするためにnull以外の値をチェックするためです。現在私は[Dozer](http://dozer.sourceforge.net/documentation/usage.html)を使用するプロジェクトに取り組んでおり、nullをマップすることを選択できます。とにかくPOSTを使ってリソースを追加/作成し、PUTを使ってそれらを修正しますが、このマッパーでは部分的なマッピングを行うことができます。だから私はこれが好ましい方法であるか、POST(作成)、PUT(完全更新)、PATCH(部分更新)を使用すべきかどうかについて議論しています。 –

答えて

7

PATCHを使用して既存のオブジェクトの修正版をアップロードすることは、あなたが概説した理由のためにほとんど常に問題になります。 JSONでPATCHを使用する場合は、を強くと指定すると、またはRFC 7396のいずれかになることが示唆されます。私はそれに精通していないので7396とは話しませんが、6902に従うと、PATCH操作のための別個のリソースを定義します。

PATCH http://localhost:8080/addresses/1 
[ 
    { "op": "replace", "path": "/line1", "value": "1234 NewAddressDownTheStreet ST" }, 
    { "op": "remove", "path": "/line2" } 
] 

あなたは、現在のサーバの状態で開始し、PATCHの変更を適用し、新しいエンティティオブジェクトを作り、これを処理します:あなたが与えた例では、それは次のようになります。新しいエンティティオブジェクトに対して検証を実行します。それが通過する場合、それをデータレイヤーに押します。失敗した場合は、エラーコードを返します。

PUTにオーバーヘッドが多すぎると、それは良い考えです。 Idempotencyはいいことです。トレードオフは、より多くのデータをワイヤでプッシュすることです。あなたのリソースが大きくなく、頻繁にアクセスされていない場合、それほど大きな問題ではないかもしれません。リソースが大きく、頻繁にアクセスされるリソースは、かなりのオーバーヘッドを追加する可能性があります。もちろん、私たちはあなたに転倒ポイントを伝えることはできません。

また、リソース・モデルをデータベース・モデルに完全に結んでいるようです。優れたデータベーステーブル設計と優れたリソース設計は、重要ではないプロジェクトではしばしば大きく異なって見えます。私は、多くのフレームワークがあなたをその方向に導くことを理解していますが、あなたが真剣にそれを切り離すことを考えていないなら、あなたは望むかもしれません。

+0

SpringDataRestで見た例では、単純なJSONペイロードだと思っていましたが、ここにリストされているリクエストフォーマットは私にとって意味があります。トランザクションの境界として@serviceを使用する場合、このAddress dbData = getId()とtopの変更を適用することは、おそらくServiceImplで(コントローラではなく)発生する必要があります。面白い答え。しかし、今のところこの質問を開いて、追加の提案を見てください。 – SGB

関連する問題