HATEOAS untuk Layanan REST Musim Semi

REST Top

Saya baru saja mengumumkan kursus Learn Spring baru , yang berfokus pada dasar-dasar Spring 5 dan Spring Boot 2:

>> LIHAT KURSUSnya

1. Ikhtisar

Artikel ini akan berfokus pada penerapan kemampuan untuk dapat ditemukan dalam Layanan REST Musim Semi dan pada pemenuhan batasan HATEOAS.

Artikel ini berfokus pada Spring MVC. Artikel kami An Intro to Spring HATEOAS menjelaskan cara menggunakan HATEOAS di Spring Boot.

2. Memisahkan Dapat Ditemukan Melalui Peristiwa

Kemudahan untuk ditemukan sebagai aspek atau perhatian terpisah dari lapisan web harus dipisahkan dari pengontrol yang menangani permintaan HTTP. Untuk tujuan ini, Kontroler akan mengaktifkan peristiwa untuk semua tindakan yang memerlukan manipulasi tambahan pada respons.

Pertama, mari buat acara:

public class SingleResourceRetrieved extends ApplicationEvent { private HttpServletResponse response; public SingleResourceRetrieved(Object source, HttpServletResponse response) { super(source); this.response = response; } public HttpServletResponse getResponse() { return response; } } public class ResourceCreated extends ApplicationEvent { private HttpServletResponse response; private long idOfNewResource; public ResourceCreated(Object source, HttpServletResponse response, long idOfNewResource) { super(source); this.response = response; this.idOfNewResource = idOfNewResource; } public HttpServletResponse getResponse() { return response; } public long getIdOfNewResource() { return idOfNewResource; } }

Kemudian, Controller, dengan 2 operasi sederhana - temukan berdasarkan id dan buat :

@RestController @RequestMapping(value = "/foos") public class FooController { @Autowired private ApplicationEventPublisher eventPublisher; @Autowired private IFooService service; @GetMapping(value = "foos/{id}") public Foo findById(@PathVariable("id") Long id, HttpServletResponse response) { Foo resourceById = Preconditions.checkNotNull(service.findOne(id)); eventPublisher.publishEvent(new SingleResourceRetrieved(this, response)); return resourceById; } @PostMapping @ResponseStatus(HttpStatus.CREATED) public void create(@RequestBody Foo resource, HttpServletResponse response) { Preconditions.checkNotNull(resource); Long newId = service.create(resource).getId(); eventPublisher.publishEvent(new ResourceCreated(this, response, newId)); } }

Kami kemudian dapat menangani peristiwa ini dengan sejumlah pendengar yang dipisahkan. Masing-masing dapat berfokus pada kasusnya sendiri dan membantu memenuhi batasan HATEOAS secara keseluruhan.

Listener harus menjadi objek terakhir dalam stack panggilan dan tidak diperlukan akses langsung ke objek tersebut; karena itu mereka bukan untuk umum.

3. Membuat URI dari Resource yang Baru Dibuat Dapat Ditemukan

Seperti yang dibahas dalam posting sebelumnya di HATEOAS, operasi pembuatan Sumber Daya baru harus mengembalikan URI sumber daya itu di header HTTP Lokasi dari respons.

Kami akan menangani ini menggunakan pendengar:

@Component class ResourceCreatedDiscoverabilityListener implements ApplicationListener{ @Override public void onApplicationEvent(ResourceCreated resourceCreatedEvent){ Preconditions.checkNotNull(resourceCreatedEvent); HttpServletResponse response = resourceCreatedEvent.getResponse(); long idOfNewResource = resourceCreatedEvent.getIdOfNewResource(); addLinkHeaderOnResourceCreation(response, idOfNewResource); } void addLinkHeaderOnResourceCreation (HttpServletResponse response, long idOfNewResource){ URI uri = ServletUriComponentsBuilder.fromCurrentRequestUri(). path("/{idOfNewResource}").buildAndExpand(idOfNewResource).toUri(); response.setHeader("Location", uri.toASCIIString()); } }

Dalam contoh ini, kami menggunakan ServletUriComponentsBuilder - yang membantu menggunakan Request saat ini. Dengan cara ini, kita tidak perlu menyebarkan apapun dan kita dapat mengaksesnya secara statis.

Jika API akan mengembalikan ResponseEntity - kita juga bisa menggunakan dukungan Lokasi .

4. Mendapatkan Sumber Daya Tunggal

Saat mengambil satu Sumber Daya, klien harus bisa menemukan URI untuk mendapatkan semua Sumber Daya jenis itu:

@Component class SingleResourceRetrievedDiscoverabilityListener implements ApplicationListener{ @Override public void onApplicationEvent(SingleResourceRetrieved resourceRetrievedEvent){ Preconditions.checkNotNull(resourceRetrievedEvent); HttpServletResponse response = resourceRetrievedEvent.getResponse(); addLinkHeaderOnSingleResourceRetrieval(request, response); } void addLinkHeaderOnSingleResourceRetrieval(HttpServletResponse response){ String requestURL = ServletUriComponentsBuilder.fromCurrentRequestUri(). build().toUri().toASCIIString(); int positionOfLastSlash = requestURL.lastIndexOf("/"); String uriForResourceCreation = requestURL.substring(0, positionOfLastSlash); String linkHeaderValue = LinkUtil .createLinkHeader(uriForResourceCreation, "collection"); response.addHeader(LINK_HEADER, linkHeaderValue); } }

Perhatikan bahwa semantik dari relasi link menggunakan tipe relasi "collection" , yang dispesifikasikan dan digunakan di beberapa microformats, tetapi belum distandarisasi.

The link header salah satu yang paling digunakan HTTP header untuk tujuan untuk ditemukan. Utilitas untuk membuat tajuk ini cukup sederhana:

public class LinkUtil { public static String createLinkHeader(String uri, String rel) { return "; rel=\"" + rel + "\""; } }

5. Dapat ditemukan di Akar

Akar adalah titik masuk di seluruh layanan - yang berhubungan dengan klien saat menggunakan API untuk pertama kalinya.

Jika batasan HATEOAS akan dipertimbangkan dan diterapkan secara keseluruhan, maka ini adalah tempat untuk memulai. Oleh karena itu, semua URI utama sistem harus dapat ditemukan dari root.

Sekarang mari kita lihat pengontrol untuk ini:

@GetMapping("/") @ResponseStatus(value = HttpStatus.NO_CONTENT) public void adminRoot(final HttpServletRequest request, final HttpServletResponse response) { String rootUri = request.getRequestURL().toString(); URI fooUri = new UriTemplate("{rootUri}{resource}").expand(rootUri, "foos"); String linkToFoos = LinkUtil.createLinkHeader(fooUri.toASCIIString(), "collection"); response.addHeader("Link", linkToFoos); }

Ini, tentu saja, adalah ilustrasi konsep, yang berfokus pada satu URI sampel, untuk Foo Resources. Implementasi nyata harus menambahkan, serupa, URI untuk semua Resource yang dipublikasikan ke klien.

5.1. Kemudahan untuk Ditemukan Bukan Tentang Mengubah URI

Ini bisa menjadi poin yang kontroversial - di satu sisi, tujuan HATEOAS adalah agar klien menemukan URI API dan tidak bergantung pada nilai hardcode. Di sisi lain - ini bukanlah cara kerja web: ya, URI ditemukan, tetapi juga diberi bookmark.

Perbedaan halus namun penting adalah evolusi API - URI lama harus tetap berfungsi, tetapi klien mana pun yang akan menemukan API harus menemukan URI baru - yang memungkinkan API berubah secara dinamis, dan klien yang baik untuk bekerja dengan baik bahkan ketika Perubahan API.

Kesimpulannya - hanya karena semua URI dari layanan web RESTful harus dianggap sebagai URI keren (dan URI keren tidak berubah) - itu tidak berarti bahwa mengikuti batasan HATEOAS tidak terlalu berguna saat mengembangkan API.

6. Peringatan tentang Dapat Ditemukan

Seperti yang dinyatakan oleh beberapa diskusi seputar artikel sebelumnya, tujuan pertama dari kemudahan untuk dapat ditemukan adalah untuk meminimalkan atau tidak menggunakan dokumentasi dan meminta klien mempelajari dan memahami cara menggunakan API melalui tanggapan yang didapatnya.

Faktanya, ini seharusnya tidak dianggap sebagai ide yang terlalu jauh - begitulah cara kami mengonsumsi setiap halaman web baru - tanpa dokumentasi apa pun. Jadi, jika konsepnya lebih bermasalah dalam konteks REST, maka itu harus menjadi masalah implementasi teknis, bukan pertanyaan apakah itu mungkin atau tidak.

Namun demikian, secara teknis, kami masih jauh dari solusi yang berfungsi penuh - dukungan spesifikasi dan kerangka kerja masih berkembang, dan karena itu, kami harus melakukan beberapa kompromi.

7. Kesimpulan

Artikel ini membahas implementasi beberapa ciri kemampuan untuk dapat ditemukan dalam konteks Layanan RESTful dengan Spring MVC dan menyentuh konsep kemampuan untuk dapat ditemukan di root.

Penerapan semua contoh dan cuplikan kode ini dapat ditemukan di GitHub - ini adalah proyek berbasis Maven, jadi semestinya mudah untuk mengimpor dan menjalankannya apa adanya.

REST bawah

Saya baru saja mengumumkan kursus Learn Spring baru , yang berfokus pada dasar-dasar Spring 5 dan Spring Boot 2:

>> LIHAT KURSUSnya