HATEOAS untuk Perkhidmatan Spring REST

REST Teratas

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

>> SEMAK KURSUS

1. Gambaran keseluruhan

Artikel ini akan menumpukan pada pelaksanaan kebolehtemuan dalam Spring REST Service dan memuaskan batasan HATEOAS.

Artikel ini memberi tumpuan kepada Spring MVC. Artikel kami Intro to Spring HATEOAS menerangkan cara menggunakan HATEOAS di Spring Boot.

2. Menurunkan Kebolehtemuan Melalui Acara

Kebolehtemuan sebagai aspek atau perhatian yang terpisah dari lapisan web harus dipisahkan dari pengawal yang menangani permintaan HTTP. Untuk tujuan ini, Pengawal akan mematikan acara untuk semua tindakan yang memerlukan manipulasi tindak balas tambahan.

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, Pengawal, dengan 2 operasi mudah - cari dengan 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 acara ini dengan sebilangan besar pendengar yang dipisahkan. Masing-masing boleh menumpukan perhatian pada kesnya sendiri dan membantu memenuhi kekangan keseluruhan HATEOAS.

Pendengar harus menjadi objek terakhir dalam timbunan panggilan dan tidak ada akses langsung ke dalamnya; dengan itu mereka tidak terbuka.

3. Menjadikan URI dari Sumber yang Baru Dibuat Dapat Diketahui

Seperti yang dibincangkan dalam post sebelumnya pada HATEOAS, operasi mewujudkan Sumber baru perlu mengembalikan URI sumber bahawa dalam Lokasi HTTP header respon.

Kami akan mengatasinya dengan 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 dengan menggunakan Permintaan semasa. Dengan cara ini, kita tidak perlu menyampaikan apa-apa dan kita boleh mengaksesnya secara statik.

Sekiranya API mengembalikan ResponseEntity - kami juga boleh menggunakan sokongan Lokasi .

4. Mendapatkan Satu Sumber

Semasa mendapatkan satu Sumber, pelanggan seharusnya dapat menemui URI untuk mendapatkan semua Sumber seperti 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 bahawa semantik hubungan pautan menggunakan jenis hubungan "koleksi" , yang ditentukan dan digunakan dalam beberapa mikroformat, tetapi belum diseragamkan.

The Link header adalah salah satu yang paling digunakan HTTP header bagi maksud kebolehan ditemui. Utiliti untuk membuat tajuk ini cukup mudah:

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

5. Kebolehtemuan di Akar

Akarnya adalah titik masuk dalam keseluruhan perkhidmatan - inilah yang disentuh oleh klien ketika pertama kali menggunakan API.

Sekiranya kekangan HATEOAS harus dipertimbangkan dan dilaksanakan sepanjang masa, maka inilah tempat untuk memulakannya. Oleh itu, semua URI utama sistem mesti dicari dari akarnya.

Sekarang mari kita lihat pengawal 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); }

Sudah tentu ini adalah ilustrasi konsep, yang memfokuskan pada URI sampel tunggal, untuk Foo Resources. Pelaksanaan yang sebenar harus menambah, sama, URI untuk semua Sumber yang diterbitkan kepada pelanggan.

5.1. Kebolehcapaian Bukan Mengenai URI yang Mengubah

Ini boleh menjadi titik kontroversi - di satu pihak, tujuan HATEOAS adalah untuk meminta pelanggan menemui URI API dan tidak bergantung pada nilai kod keras. Sebaliknya - ini bukan bagaimana web berfungsi: ya, URI ditemui, tetapi juga ditanda buku.

Perbezaan yang halus tetapi penting adalah evolusi API - URI lama masih boleh berfungsi, tetapi mana-mana pelanggan yang akan menemui API harus menemui URI baru - yang membolehkan API berubah secara dinamik, dan klien yang baik dapat berfungsi dengan baik walaupun ketika API berubah.

Kesimpulannya - hanya kerana semua URI perkhidmatan web RESTful harus dianggap URI yang keren (dan URI yang sejuk tidak berubah) - itu tidak bermakna bahawa mematuhi kekangan HATEOAS tidak begitu berguna ketika mengembangkan API.

6. Gua Kebolehtemuan

Sebagaimana dinyatakan oleh beberapa perbincangan mengenai artikel sebelumnya, tujuan pertama untuk ditemui adalah dengan menggunakan dokumentasi yang minimum atau tidak dan membuat klien belajar dan memahami cara menggunakan API melalui respons yang diterima.

Sebenarnya, ini tidak boleh dianggap sebagai ideal jauh - seperti bagaimana kita menggunakan setiap laman web baru - tanpa dokumentasi. Oleh itu, jika konsep itu lebih bermasalah dalam konteks REST, maka ia harus menjadi masalah pelaksanaan teknikal, bukan persoalan sama ada mungkin atau tidak.

Yang dikatakan, secara teknis, kami masih jauh dari solusi yang sepenuhnya berfungsi - spesifikasi dan sokongan kerangka masih berkembang, dan kerana itu, kami harus membuat beberapa kompromi.

7. Kesimpulannya

Artikel ini merangkumi pelaksanaan beberapa ciri kebolehtemuan dalam konteks Perkhidmatan RESTful dengan Spring MVC dan menyentuh konsep kebolehtemuan yang dapat ditemui.

Pelaksanaan semua contoh dan coretan kod ini dapat dilihat di GitHub - ini adalah projek berasaskan Maven, jadi mudah untuk diimport dan dijalankan sebagaimana adanya.

REST bawah

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

>> SEMAK KURSUS