私には2つのパスがあります。
/students
_/students/{id}/addresses
_...次の動作で:
POST
から_/students
_-_201 Created
_(学生が正常に作成された場合)POST
から_/students/{id}/addresses
_-_201 Created
_(下でアドレスが正常に作成された場合要求されたユーザー)次に、クライアントがPOST
から_/students/12/addresses
_への状況を想像してください。ただし、id = 12のStudentはありません。
このシナリオをどのように処理する必要がありますか?
チェックなしで挿入し、DAOが例外をスローするのを待つべきですか、それとも挿入する前に、学生の存在をチェックする必要がありますか?
1stオプション:
StudentController.Java
_@Autowired
private AddressService addressService;
@PostMapping("/students/{id}/addresses")
public ResponseEntity<?> handleRequestOfCreateAddressToStudent(@PathVariable("id") Long studentId, @RequestBody Address address) {
address.setStudent(new Student(studentId));
Address createdAddress = addressService.saveAddress(address);
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").build(createdAddress.getId());
return ResponseEntity.created(location).build();
}
_
AddressService.Java
_@Autowired
private AddressRepository addressRepository;
public Address saveAddress(Address address) {
// omitted for brevity...
return addressRepository.save(address);
}
_
この方法では、Springフレームワークを使用しているため、return addressRepository.save(address);
行はDataIntegrityViolationException
をスローします。これは、制約に違反しているためです(そのアドレスにバインドするそのようなIDを持つStudentはありません)。また、クライアントに応答を送信する必要があるため、スローされた例外を変換する必要があります。
2ndオプション:
StudentController.Java
_@Autowired
private StudentService studentService;
@Autowired
private AddressService addressService;
@PostMapping("/students/{id}/addresses")
public ResponseEntity<?> handleRequestOfCreateAddressToStudent(@PathVariable("id") Long studentId, @RequestBody Address address) throws StudentNotFoundException {
Student student = studentService.findById(studentId);
if(student == null)
throw new StudentNotFoundException("message");
address.setStudent(new Student(studentId));
Address createdAddress = addressService.saveAddress(address);
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").build(createdAddress.getId());
return ResponseEntity.created(location).build();
}
_
この方法では、Studentが存在せず、カスタム例外をスローできるように、例外を翻訳する必要はありません。
両方のアプローチにおける私の考え:
このシナリオに最適なアプローチはどれですか?
DAOが例外をスローするのを待つ必要がありますか、それともスチューデントが存在するかどうかをチェックして、スローされないようにする必要がありますか?
フロー制御デバイスとして例外を使用しないでください。最初に生徒の存在を確認してから、アドレスを追加します。お気づきのように、これにより、送信するメッセージングをより詳細に制御でき、さらに、すべてのロジックを同じフローに維持できます(メインブロックと例外ハンドラーの両方からメッセージを返すのではありません)。
パフォーマンス上の理由から、レイテンシとバックエンドの需要を減らすことは確かに有効な懸念事項になる可能性があります。しかし、それは正確さと比較すると見劣りします。
完全にTime-Of-Check-Time-Of-Useとして記述されたTOCTOUは、厳密に線形のモデル(Windows 3.11での協調マルチスレッドのようなもの)での思考から生じる非常によく知られているエラーですが、実際には独立、同時、非同期のアクション。
まあ、あなたが学生の削除もサポートしない限り、少なくともあなたがその学生を見つけた場合、それはまだそこにあります。それを保証できますか?また、それが変わることはありませんか?より高性能で常に正しいアプローチを求めてください。
例外はパフォーマンスの独占であり、言語と実装によっては例外ではない可能性があると言う人もいます。 それを測定する、そしてそうである場合、エラーコードもオプションです。失敗が一般的である可能性があるため、多くのライブラリがTryXYZ()
に加えてさまざまなParseXYZ()
関数を備えている理由があります。
そのビジネス例外は、サービスレイヤーによってスローされます。永続化レイヤーはデータ例外をスローする必要があります。次に、コントローラーで、そのビジネス例外のみをHTTP応答に変換します。
これは例外的なケース(存在しない学生についてREST APIをリクエストしているのは誰ですか?)なので、処理はそのようにする必要があります。
HTTPに厳密になりたい場合は、HTTP 404 Not foundを返します。
URLが既存の場所を指していないため、それを見つけることができず、したがって404になります。
サービスレイヤーでは、詳細な例外処理が必要な場合、404に変換するStudentNotFoundException(または最終的にはより一般的なEntityNotFoundException)が必要です。実装では、挿入する前に生徒を検索するかどうか直接テーブルに入るのはそれほど重要ではありません。あなたが数十万回の挿入を試みるつもりでない限り。従来の「インポート初期化データ」が必要な場合は、その特定のケースを処理するために適切なコードが必要になる可能性が高く、メインサービスでその1つのケースを心配する必要はありません。
ここでの例外の使用はフロー制御ではないことに注意してください。これは実際には例外であり、標準エラーへの応答につながります。