Externe Verknüpfung (Deep Linking) - Im Internet ist dies die Platzierung eines Hyperlinks auf einer Website, der auf eine Seite auf einer anderen Website verweist, anstatt auf die Startseite (Startseite, Startseite) dieser Website. Solche Links werden als externe Links (Deep Links) bezeichnet.
Wikipedia
Der Begriff
"Deep Links" wird weiter verwendet, um der englischen Sprache "Deep Links" am nächsten zu kommen. Dieser Artikel konzentriert sich auf die REST-API, sodass Deep Links Links zu HTTP-Ressourcen bedeuten. Beispielsweise verweist der Deep Link
habr.com/de/post/426691 auf einen bestimmten Artikel auf habr.com.
HATEOAS ist eine Komponente der REST-Architektur, mit der API-Clients Informationen über Hypermedia bereitgestellt werden können. Der Client kennt die einzige feste Adresse, den API-Einstiegspunkt. Er lernt alle möglichen Aktionen aus den vom Server empfangenen Ressourcen. Ressourcenansichten enthalten Links zu Aktionen oder anderen Ressourcen. Der Client interagiert mit der API und wählt dynamisch eine Aktion aus den verfügbaren Links aus. Sie können mehr über HATEOAS auf
Wikipedia oder in diesem wunderbaren
Artikel über Habré lesen.
HATEOAS ist die nächste Stufe der REST-API. Dank der Verwendung von Hypermedia beantwortet er viele Fragen, die während der Entwicklung der API auftreten: Wie kann der Zugriff auf Aktionen auf der Serverseite gesteuert werden, wie kann die enge Konnektivität zwischen Client und Server beseitigt werden, und wie können die Adressen von Ressourcen bei Bedarf geändert werden. Es gibt jedoch keine Antwort auf die Frage, wie tief die Verknüpfung mit Ressourcen aussehen sollte.
In der "klassischen" REST-Implementierung kennt der Client die Struktur der Adressen und weiß, wie eine Ressource anhand einer Kennung in der REST-API abgerufen wird. Ein Benutzer folgt beispielsweise einem Deep Link zu einer Buchseite in einem Online-Shop. Die URL-Leiste
https://domain.test/books/1
in der Adressleiste des Browsers angezeigt. Der Client weiß, dass "1" die Kennung der
https://api.domain.test/api/books/{id}
. Um diese zu erhalten, müssen Sie diese Kennung in der REST-API-URL
https://api.domain.test/api/books/{id}
. Daher sieht der tiefe Link zur Ressource dieses Buches in der REST-API folgendermaßen aus:
https://api.domain.test/api/books/1
.
In HATEOAS kennt der Client keine Ressourcenkennungen oder Adressstrukturen. Er codiert nicht fest, sondern "entdeckt" Links. Darüber hinaus kann sich die Struktur von URLs ohne Wissen des Kunden ändern, was HATEOAS zulässt. Aufgrund dieser Unterschiede können Deep Links nicht auf die gleiche Weise wie die klassische REST-API implementiert werden. Überraschenderweise ergab eine Suche im Internet nach Rezepten für die Implementierung solcher Links in HATEOAS nicht viele Ergebnisse, sondern nur einige verwirrende Fragen zu Stackoverflow. Daher werden wir verschiedene mögliche Optionen in Betracht ziehen und versuchen, die beste auszuwählen.
Die Null-Option außerhalb des Wettbewerbs besteht nicht darin, Deep Links zu implementieren. Dies ist möglicherweise für einige Administratoren oder mobile Anwendungen geeignet, für die kein direkter Wechsel zu internen Ressourcen erforderlich ist. Dies ist ganz im Sinne von HATEOAS. Der Benutzer kann Seiten ab dem Einstiegspunkt nur nacheinander öffnen, da der Client nicht weiß, wie er direkt zur internen Ressource wechseln soll. Diese Option ist jedoch nicht für Webanwendungen geeignet. Wir gehen davon aus, dass der Link zur internen Seite mit einem Lesezeichen versehen werden kann und die Aktualisierung der Seite uns nicht zur Hauptseite der Website zurückführt.
Also die erste Option: der HATEOAS API URL Hardcode. Der Client kennt die Struktur der Ressourcenadressen, für die Deep Links benötigt werden, und weiß, wie die Ressourcenkennung für die Suche abgerufen wird. Beispielsweise gibt der Server die Adresse
https://api.domain.test/api/books/1
als Referenz auf die
https://api.domain.test/api/books/1
. Der Kunde weiß, dass „1“ die Kennung des Buches ist und kann diese URL selbst generieren, wenn er auf den Deep Link klickt. Dies ist sicherlich eine funktionierende Option, verstößt jedoch gegen die Grundsätze von HATEOAS. Die Adressstruktur und die Ressourcen-ID können nicht mehr geändert werden, da sonst der Client unterbrochen wird und eine starre Verbindung besteht. Dies ist kein HATEOAS, was bedeutet, dass die Option nicht zu uns passt.
Die zweite Option besteht darin, die REST-API-URL durch die Client-URL zu ersetzen. In einem Beispiel mit einem Buch sieht der Deep Link folgendermaßen aus:
https://domain.test/books?url=https://api.domain.test/api/books/1
. Hier nimmt der Client den vom Server empfangenen Ressourcenlink und ersetzt ihn vollständig durch die Seitenadresse. Dies ähnelt eher HATEOAS. Der Kunde kennt keine Kennungen und Adressstrukturen. Er erhält einen Link und verwendet ihn unverändert. Wenn Sie auf einen solchen detaillierten Link klicken, erhält der Client die gewünschte Ressource über den REST-API-Link vom URL-Parameter. Es scheint, dass die Lösung funktioniert und ganz im Sinne von HATEOAS. Wenn Sie jedoch einen solchen Link zu Ihren Lesezeichen hinzufügen, können wir die Adresse der Ressource in der API in Zukunft nicht mehr ändern (oder wir müssen immer zu einer neuen Adresse umleiten). Auch hier geht einer der Vorteile von HATEOAS verloren, auch diese Option ist nicht ideal.
Daher möchten wir Permalinks haben, die sich jedoch ändern können. Eine solche Lösung existiert und wird im Internet häufig verwendet. Viele Websites bieten kurze Links zu internen Seiten, die gemeinsam genutzt werden können. Neben der Kürze besteht ihr Vorteil darin, dass die Website die tatsächliche Adresse der Seite ändern kann, diese Links jedoch nicht beschädigt werden. Beispielsweise verwendet Microsoft Windows-Links, um Seiten des Formulars
http://go.microsoft.com/fwlink/?LinkId=XXX
. Im Laufe der Jahre wurden Microsoft-Websites mehrmals neu gestaltet, aber Links in älteren Windows-Versionen funktionieren weiterhin.
Es bleibt nur, diese Lösung an HATEOAS anzupassen. Dies ist die dritte Option - die Verwendung eindeutiger Deep Link-IDs in der REST-API. Jetzt sieht die Adresse der Buchseite folgendermaßen aus:
https://domain.test/books?deepLinkId=3f0fd552-e564-42ed-86b6-a8e3055e2763
. Wenn der Client auf einen solchen
deepLinkId
Link klickt, sollte er den Server fragen:
deepLinkId
Ressourcenlink entspricht einem solchen
deepLinkId
Bezeichner? Der Server gibt den Link
https://api.domain.test/api/books/1
(also oder sofort eine Ressource, um nicht zweimal zu gehen). Wenn sich die Ressourcenadresse in der REST-API ändert, gibt der Server einfach einen anderen Link zurück. In der Datenbank wird ein Datensatz gespeichert, dessen Referenzkennung 3f0fd552-e564-42ed-86b6-a8e3055e2763 der Entitätskennung von Buch 1 entspricht.
Zu diesem
deepLinkId
müssen Ressourcen ein
deepLinkId
Feld mit den Bezeichnern ihrer Deep Links enthalten, und der Client muss sie in der Adresse der Seite ersetzen. Diese Adresse kann sicher mit einem Lesezeichen versehen und an Freunde gesendet werden. Es ist nicht sehr gut, dass der Client unabhängig mit bestimmten Bezeichnern arbeitet, aber dies ermöglicht es Ihnen, die Vorteile von HATEOAS für die API als Ganzes beizubehalten.
Beispiel
Dieser Artikel wäre ohne eine Beispielimplementierung nicht vollständig. Betrachten Sie zum Testen des Konzepts ein Beispiel für eine hypothetische Online-Shop-Katalogseite mit einem Backend für Spring Boot / Kotlin und einem SPA-Frontend für Vue / JavaScript. Das Geschäft verkauft Bücher und Bleistifte. Die Website verfügt über zwei Bereiche, in denen Sie die Liste der Produkte anzeigen und deren Seiten öffnen können.
Abschnitt "Bücher":

Eine Buchseite:

Für die Lagerung von Waren werden Spring Data JPA-Entitäten definiert:
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() }
Zum Erstellen und Speichern von Deep Link-
DeepLink
wird die
DeepLink
Entität
DeepLink
, von der mit jedem
DeepLink
eine Instanz erstellt wird. Der Bezeichner selbst wird zum Zeitpunkt der Erstellung der Entität gemäß dem UUID-Standard generiert. Die Tabelle enthält die Kennung der Deep Link, die Kennung und den Typ der Entität, zu der die Verbindung führt.
Die REST-API des Servers ist nach dem HATEOAS-Konzept organisiert. Der API-Einstiegspunkt enthält Links zu Produktsammlungen sowie einen
#deepLink
Link zum Bilden von Deep Links durch Ersetzen eines Bezeichners:
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 } } }
Der Client fordert beim Öffnen des Abschnitts "Bücher" eine Sammlung von Ressourcen unter dem Link
#books
am Einstiegspunkt an:
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" } } } ...
SPA verwendet Vue Router, für den der Pfad zur Buchseite definiert ist
{ path: '/books/:deepLinkId', name: 'book', component: Book, props: true }
, und die Links in der
<router-link :to="{name: 'book', params: {link: book._links.self.href, deepLinkId: book.deepLinkId}}">{{ book.name }}</router-link>
sehen folgendermaßen aus:
<router-link :to="{name: 'book', params: {link: book._links.self.href, deepLinkId: book.deepLinkId}}">{{ book.name }}</router-link>
.
Das heißt, wenn Sie die Seite eines bestimmten Buches öffnen, wird die
#self
aufgerufen, die zwei Parameter empfängt:
link
(Link zur
#self
in der REST-API, Wert des
href
Felds des
#self
) und
deepLinkId
von der Ressource).
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 }) }) } }
Vue Router setzt den Wert von
deepLinkId
auf die Adresse der Seite
/books/:deepLinkId
, und die Komponente fordert die Ressource per direkter Verknüpfung von der
link
. Wenn Sie eine
deepLinkId
, legt Vue Router die Komponenteneigenschaft
deepLinkId
und
deepLinkId
sie von der
deepLinkId
ab. Die
link
Eigenschaft bleibt
null
. Die Komponente prüft: Wenn aus der Sammlung ein direkter Link stammt, wird die Ressource darauf angefordert. Wenn nur die Kennung
deepLinkId
, wird sie vom Einstiegspunkt aus in den Link
#deepLink
, um die Ressource über den Deep Link zu erhalten.
Im Backend sieht die Controller-Methode für Deep Links folgendermaßen aus:
@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() }
Durch Kennung ist die Essenz der tiefen Verbindung. Abhängig vom Typ der Anwendungsentität wird eine Verknüpfung zur Controller-Methode hergestellt, die ihre Ressource über
entityId
. Die Anfrage wird an diese Adresse umgeleitet. Wenn sich also in Zukunft die Verknüpfung zum Entity-Controller ändert, kann die Logik der Verknüpfungsbildung in der
deepLink
Methode einfach
deepLink
werden.
Der vollständige Quellcode für das Beispiel ist auf
Github verfügbar.