追加のライブラリと数行の追加コードを使用して、アプリケーションの概要にハイパーメディアを追加しました。ただし、サービスをRESTfulにするために必要な条件はこれだけではありません。RESTの重要な側面は、RESTがテクノロジースタックでも単一の標準でもないことです。
RESTはアーキテクチャ上の制約のコレクションであり、これらの制約を使用すると、アプリケーションの回復力を高めることができます。回復力の重要な要素は、サービスをアップグレードするときにダウンタイムの問題を回避できることです。
過去のログでは、アップグレードによってクライアントが破壊されたことが悪評でした。つまり、サーバーをアップグレードするには、クライアントを更新する必要があります。今日の時代では、電話料金のアップグレードに数時間または数分のダウンタイムが発生すると、何百万ドルもの収益が失われる可能性があります。
一部の企業は、当社の経営陣が最初にダウンタイムを最小限に抑える計画を提案することを要求しています。以前は、負荷が最小の日曜日の午前2時にアップグレードすることができました。ただし、今日のインターネットベースのeコマースで国際的な顧客との取引では、この戦略はそれほど効果的ではありません。
SOAPベースのサービスとCORBAベースのサービスは非常に脆弱です。古いクライアントと新しいクライアントの両方をサポートできるサーバーを起動することは困難です。RESTベースのアプローチを使用すると、はるかに簡単になります。特にSpringスタックを使用しています。
この設計上の問題を想像:私たちはベース開始したEmployee
記録システムを。このシステムの使用量は非常に大きいです。システムを無数の企業に販売してきました。突然、従業員名を分割する必要がありますfirstName
とlastName
。
ああ、私は前にそれを期待していませんでした。
オープンではEmployee
、クラスと単一のフィールドname
に交換firstName
し、lastName
停止して、もう一度考えるように、前に。それはクライアントを悩ませますか?アップグレードにどのくらい時間がかかりますか。私たちのサービスにアクセスするすべてのクライアントを制御しますか?
ダウンタイム=損失。経営陣はこれに備えていますか?
RESTの何年も前に古代の戦略があります。
データベースの列は削除しないでください。
—不明
データベーステーブルにはいつでも列(フィールド)を追加できます。ただし、列は削除しないでください。RESTfulサービスの原則は同じです。JSONプレゼンテーションに新しいフィールドを追加しますが、労力を無駄にしないでください。このように:
複数のクライアントのJSONサポート
{
"id": 1,
"firstName": "Bilbo",
"lastName": "Baggins",
"role": "burglar",
"name": "Bilbo Baggins",
"_links": {
"self": {
"href": "http://localhost:8080/employees/1"
},
"employees": {
"href": "http://localhost:8080/employees"
}
}
}
(注)この形式はどのように表示することfirstName
、lastName
およびname
?それは情報を繰り返す機能を持ち、目的は新旧の顧客をサポートすることです。つまり、クライアントを同時にアップグレードしなくてもサーバーをアップグレードできます。適切な対策を講じることで、ダウンタイムを削減できます。
さらに、情報を「古い方法」と「新しい方法」で表示するだけでなく、受信データを2つの方法で処理する必要があります。
どうやって?このように単純です:
「古い」および「新しい」クライアント従業員レコードを処理する
package payroll;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Data
@Entity
class Employee {
private @Id @GeneratedValue Long id;
private String firstName;
private String lastName;
private String role;
Employee() {}
Employee(String firstName, String lastName, String role) {
this.firstName = firstName;
this.lastName = lastName;
this.role = role;
}
public String getName() {
return this.firstName + " " + this.lastName;
}
public void setName(String name) {
String[] parts =name.split(" ");
this.firstName = parts[0];
this.lastName = parts[1];
}
}
このクラスおよびEmployee
それ以前のバージョンは非常に似ています。これらの変更を見てみましょう:
- フィールドには
name
なっているfirstName
とlastName
置き換えます。Lombokはこれらのゲッターとセッターを生成します。 - 旧への
name
「仮想」ゲッター、プロパティの定義getName()
、用途firstName
およびlastName
値を生成するフィールド。 - また、古いのための
name
「仮想」setterプロパティの定義setName()
。入力文字列を解析し、適切なフィールドに格納します。
もちろん、APIへのすべての変更は、文字列の分割や2つの文字列のマージほど単純ではありません。しかし、ほとんどの場合、一連の変換方法を考え出す必要がありますよね?
もう1つの微調整は、各RESTメソッドが正しい応答を返すようにすることです。次のようにPOSTメソッドを更新します。
POSTは「古い」および「新しい」クライアント要求を処理します
@PostMapping("/employees")
ResponseEntity<?> newEmployee(@RequestBody Employee newEmployee) throws URISyntaxException {
EntityModel<Employee> entityModel = assembler.toModel(repository.save(newEmployee));
return ResponseEntity
.created(entityModel.getRequiredLink(IanaLinkRelations.SELF).toUri())
.body(entityModel);
}
- 新しい
Employee
オブジェクトは、以前のように保存されます。しかし、使用EmployeeModelAssembler
生成パックオブジェクト。 - Spring MVCのがされて
ResponseEntity
作成するために使用されるHTTP 201作成されたステータスメッセージを。このタイプの応答には通常Location応答ヘッダーが含まれ、モデルの自己相関リンクから派生したURIを使用します。 - さらに、保存されたオブジェクトのリソースベースのバージョンを返します。
調整後、私たちは、新しい従業員のリソースを作成するために、同じエンドポイントを使用して、古い使用できるname
フィールドを:
$ curl -v -X POST localhost:8080/employees -H 'Content-Type:application/json' -d '{"name": "Samwise Gamgee", "role": "gardener"}'
出力は次のとおりです。
> POST /employees HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type:application/json
> Content-Length: 46
>
< Location: http://localhost:8080/employees/3
< Content-Type: application/hal+json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Fri, 10 Aug 2018 19:44:43 GMT
<
{
"id": 3,
"firstName": "Samwise",
"lastName": "Gamgee",
"role": "gardener",
"name": "Samwise Gamgee",
"_links": {
"self": {
"href": "http://localhost:8080/employees/3"
},
"employees": {
"href": "http://localhost:8080/employees"
}
}
}
これにより、結果オブジェクトがHAL(name
およびfirstName
/ lastName
)で表示されるだけでなく、Locationヘッダー情報がhttp:// localhost:8080 / employees / 3に追加されます。ハイパーメディア機能を備えたクライアントは、新しいリソースを「参照」して、引き続き操作することができます。
PUTコントローラーメソッドでは、同様の調整が必要です。
さまざまなクライアントのPUTの処理
@PutMapping("/employees/{id}")
ResponseEntity<?> replaceEmployee(@RequestBody Employee newEmployee, @PathVariable Long id) throws URISyntaxException {
Employee updatedEmployee = repository.findById(id)
.map(employee -> {
employee.setName(newEmployee.getName());
employee.setRole(newEmployee.getRole());
return repository.save(employee);
})
.orElseGet(() -> {
newEmployee.setId(id);
return repository.save(newEmployee);
});
EntityModel<Employee> entityModel = assembler.toModel(updatedEmployee);
return ResponseEntity
.created(entityModel.getRequiredLink(IanaLinkRelations.SELF).toUri())
.body(entityModel);
}
次に、使用しEmployeeModelAssembler
てsave()
構築の操作Employee
対象にパッケージをEntityModel<Employee>
、オブジェクトを。使用getRequiredLink()
方法を、あなたが取得することができているSELF
関連するEmployeeModelAssembler
作成しましたLink
。この方法は、あなたが使用する必要があり、リンクを返すtoUri
URIに変換する方法を。
我々はより多く必要とするので200 OK我々が使用しますので、より詳細なHTTPレスポンスコードをSpring MVCのは、あるResponseEntity
臓器を維持します。これにはcreated()
、リソースURIを挿入できる便利な静的メソッドがあります。場合はHTTP 201が作成さになる権利と私たちは新しいリソースを「作成」する必要はありませんので、これは、議論があります。ただし、Locationレスポンスヘッダー情報が事前に読み込まれているため、実行してください。
$ curl -v -X PUT localhost:8080/employees/3 -H 'Content-Type:application/json' -d '{"name": "Samwise Gamgee", "role": "ring bearer"}'
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> PUT /employees/3 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type:application/json
> Content-Length: 49
>
< HTTP/1.1 201
< Location: http://localhost:8080/employees/3
< Content-Type: application/hal+json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Fri, 10 Aug 2018 19:52:56 GMT
{
"id": 3,
"firstName": "Samwise",
"lastName": "Gamgee",
"role": "ring bearer",
"name": "Samwise Gamgee",
"_links": {
"self": {
"href": "http://localhost:8080/employees/3"
},
"employees": {
"href": "http://localhost:8080/employees"
}
}
}
これで従業員リソースが更新され、場所のURIが送り返されます。最後に、DELETE操作を適切に更新します。
DELETEリクエストの処理
@DeleteMapping("/employees/{id}")
ResponseEntity<?> deleteEmployee(@PathVariable Long id) {
repository.deleteById(id);
return ResponseEntity.noContent().build();
}
これにより、HTTP 204 No Content応答が返されます。
$ curl -v -X DELETE localhost:8080/employees/1
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> DELETE /employees/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 204
< Date: Fri, 10 Aug 2018 21:30:26 GMT
上の
Employee
クラスフィールドの変更は、それらが適切に新しい列に既存のコンテンツを移行できるよう、データベースチームと調整する必要があります。
これでアップグレードが可能になり、既存のクライアントに影響を与えることなく、新しいクライアントがこれらの拡張機能を利用できます!
ちなみに、ネットワーク経由で送信する情報が多すぎるのではないですか。すべてのバイトが重要な一部のシステムでは、APIの開発が2行目に後退する必要がある場合があります。ただし、測定を行う前にこの時期尚早の最適化を追求しないでください。