External linking (deep linking) - on the Internet, this is a hyperlink to a site that points to a page located on another web site, instead of pointing to the starting (home, start) page of that site. Such links are called external links (deep links).The term “deep links” will be used further, as the closest to the English-language “deep links”. Speech in this article will be about REST API, so the deep links will be meant links to HTTP-resources. For example, the deep link habr.com/ru/post/426691 points to a specific article on the site habr.com.
Wikipedia
https://domain.test/books/1
. The client knows that “1” is the resource identifier of the book, and to obtain it, you must substitute this identifier in the REST API URL https://api.domain.test/api/books/{id}
. Thus, the deep link to the resource of this book in the REST API looks like this: https://api.domain.test/api/books/1
.https://api.domain.test/api/books/1
as a link to the book resource. The client knows that “1” is the book identifier and can form this URL on his / her own when clicking on the depth link. This is certainly a working option, but violates the principles of HATEOAS. The structure of the address and the identifier of the resource cannot be changed, otherwise the client will break, there is a rigid connectivity. This is not HATEOAS, which means that the option does not suit us.https://domain.test/books?url=https://api.domain.test/api/books/1
. Here the client takes the resource link received from the server and substitutes it entirely in the address of the page. This is more like HATEOAS, the client does not know about the identifiers and the address structure, he gets the link and uses it as is. When clicking on such a deep link, the client will get the necessary resource via the REST API link from the url parameter. It would seem that the solution is working, and quite in the spirit of HATEOAS. But if you add such a link to your bookmarks, in the future we will not be able to change the address of the resource in the API (or we will have to always keep forwarding to a new address). Again, one of the advantages of HATEOAS is lost, this option is also not perfect.http://go.microsoft.com/fwlink/?LinkId=XXX
. Over the years, the Microsoft sites have been reworked several times, but the links in older versions of Windows continue to work.https://domain.test/books?deepLinkId=3f0fd552-e564-42ed-86b6-a8e3055e2763
. When clicking on such a deep link, the client should ask the server: which link to the resource corresponds to such a deepLinkId
identifier? The server will return the https://api.domain.test/api/books/1
(or the resource at once, so that it does not go twice). If the address of the resource in the REST API changes, the server simply returns another link. A record is saved in the database that the reference identifier 3f0fd552-e564-42ed-86b6-a8e3055e2763 corresponds to the entity identifier of book 1.deepLinkId
field with identifiers of their deep links, and the client must substitute them into the page address. Such an address can safely be bookmarked and sent to friends. It’s not at all good that the client works independently with certain identifiers, but this allows you to retain the advantages of HATEOAS for the API as a whole. enum class EntityType { PEN, BOOK } @Entity class Pen(val color: String) { @Id @Column(columnDefinition = "uuid") val id: UUID = UUID.randomUUID() @OneToOne(cascade = [CascadeType.ALL]) val deepLink: DeepLink = DeepLink(EntityType.PEN, id) } @Entity class Book(val name: String) { @Id @Column(columnDefinition = "uuid") val id: UUID = UUID.randomUUID() @OneToOne(cascade = [CascadeType.ALL]) val deepLink: DeepLink = DeepLink(EntityType.BOOK, id) } @Entity class DeepLink( @Enumerated(EnumType.STRING) val entityType: EntityType, @Column(columnDefinition = "uuid") val entityId: UUID ) { @Id @Column(columnDefinition = "uuid") val id: UUID = UUID.randomUUID() }
DeepLink
entity is DeepLink
, an instance of which is created with each domain object. The identifier itself is generated according to the UUID standard at the time the entity is created. Its table stores the identifier of the deep link, the identifier and the type of the entity being referenced.#deepLink
link for the formation of deep references by identifier substitution: GET http://localhost:8080/api { "_links": { "pens": { "href": "http://localhost:8080/api/pens" }, "books": { "href": "http://localhost:8080/api/books" }, "deepLink": { "href": "http://localhost:8080/api/links/{id}", "templated": true } } }
#books
link at the entry point: GET http://localhost:8080/api/books ... { "name": "Harry Potter", "deepLinkId": "4bda3c65-e5f7-4e9b-a8ec-42d16488276f", "_links": { "self": { "href": "http://localhost:8080/api/books/1272e287-07a5-4ebc-9170-2588b9cf4e20" } } }, { "name": "Cryptonomicon", "deepLinkId": "a23d92c2-0b7f-48d5-88bc-18f45df02345", "_links": { "self": { "href": "http://localhost:8080/api/books/5d04a6d0-5bbc-463e-a951-a9ff8405cc70" } } } ...
{ path: '/books/:deepLinkId', name: 'book', component: Book, props: true }
, and the links in the list of books look like this: <router-link :to="{name: 'book', params: {link: book._links.self.href, deepLinkId: book.deepLinkId}}">{{ book.name }}</router-link>
.Book
component is called, to which two parameters are passed: link
(link to the book resource in the REST API, href
field value of the #self
link) and deepLinkId
from the resource). const Book = { template: `<div>{{ 'Book: ' + book.name }}</div>`, props: { link: null, deepLinkId: null }, data() { return { book: { name: "" } } }, mounted() { let url = this.link == null ? '/api/links/' + this.deepLinkId : this.link; fetch(url).then((response) => { return response.json().then((json) => { this.book = json }) }) } }
deepLinkId
Vue Router sets the address of the page /books/:deepLinkId
, and the component requests the resource by a direct link from the link
property. When you force a refresh of the page, the Vue Router sets the property of the deepLinkId
component, retrieving it from the page address. The link
property remains null
. The component checks: if there is a direct link obtained from the collection, the resource is requested by it. If only the deepLinkId
identifier is deepLinkId
, it is inserted into the #deepLink
link from the entry point to get the resource via the deep link. @GetMapping("/links/{id}") fun deepLink(@PathVariable id: UUID?, response: HttpServletResponse?): ResponseEntity<Any> { id!!; response!! val deepLink = deepLinkRepo.getOne(id) val path: String = when (deepLink.entityType) { EntityType.PEN -> linkTo(methodOn(MainController::class.java).getPen(deepLink.entityId)) EntityType.BOOK -> linkTo(methodOn(MainController::class.java).getBook(deepLink.entityId)) }.toUri().path response.sendRedirect(path) return ResponseEntity.notFound().build() }
entityId
. Request redirected to this address. Thus, if in the future the link to the entity controller changes, you can simply change the link formation logic in the deepLink
method.Source: https://habr.com/ru/post/445092/
All Articles