HATEOAS مشكلة الارتباط العميق

الارتباط الخارجي (الارتباط العميق) - على الإنترنت ، هذا هو موضع ارتباط تشعبي على موقع يشير إلى صفحة على موقع ويب آخر ، بدلاً من الإشارة إلى الصفحة الرئيسية (الصفحة الرئيسية ، البداية) لهذا الموقع. هذه الروابط تسمى الروابط الخارجية (الروابط العميقة).
ويكيبيديا
سيتم استخدام مصطلح "الروابط العميقة" بشكل أقرب إلى اللغة الإنجليزية "الروابط العميقة". ستركز هذه المقالة على واجهة برمجة تطبيقات REST ، لذا فإن الروابط العميقة تعني روابط لموارد HTTP. على سبيل المثال ، يشير الرابط العميق habr.com/en/post/426691 إلى مقالة محددة على habr.com.

HATEOAS هو أحد مكونات بنية REST التي تتيح تزويد عملاء API بمعلومات من خلال الوسائط التشعبية. يعرف العميل العنوان الثابت الوحيد ، نقطة دخول API ؛ يتعلم جميع الإجراءات الممكنة من الموارد الواردة من الخادم. طرق عرض الموارد تحتوي على روابط لإجراءات أو موارد أخرى ؛ يتفاعل العميل مع واجهة برمجة التطبيقات (API) ، ويتم تحديد إجراء بشكل ديناميكي من الروابط المتاحة. يمكنك قراءة المزيد عن HATEOAS على ويكيبيديا أو في هذا المقال الرائع عن حبري.

HATEOAS هو المستوى التالي من REST API. بفضل استخدام الوسائط التشعبية ، يجيب على العديد من الأسئلة التي تنشأ أثناء تطوير واجهة برمجة التطبيقات: كيفية التحكم في الوصول إلى الإجراءات من جانب الخادم ، وكيفية التخلص من الاتصال المحكم بين العميل والخادم ، وكيفية تغيير عناوين الموارد إذا لزم الأمر. لكنه لا يقدم إجابة على سؤال حول كيفية ظهور الروابط العميقة بالموارد.

في تطبيق REST "الكلاسيكي" ، يعرف العميل بنية العناوين ؛ إنه يعرف كيفية الحصول على مورد بمعرف في واجهة برمجة تطبيقات REST. على سبيل المثال ، يتبع المستخدم رابطًا عميقًا بصفحة كتاب في متجر على الإنترنت. يتم عرض شريط URL https://domain.test/books/1 في شريط عنوان المتصفح. يعلم العميل أن الرقم "1" هو معرّف مورد الكتاب ، ولكي تحصل عليه تحتاج إلى استبدال هذا المعرف في عنوان URL لواجهة برمجة تطبيقات REST https://api.domain.test/api/books/{id} . وبالتالي ، يبدو الارتباط العميق لمورد هذا الكتاب في واجهة برمجة تطبيقات REST كما يلي: https://api.domain.test/api/books/1 .

في HATEOAS ، لا يعرف العميل عن معرفات المورد أو بنية العنوان. انه لا يصعب ، ولكن "يكتشف" الروابط. علاوة على ذلك ، يمكن أن تتغير بنية عناوين URL دون علم العميل ، حيث تسمح HATEOAS بذلك. بسبب هذه الاختلافات ، لا يمكن تنفيذ الروابط العميقة بنفس طريقة تطبيق REST API الكلاسيكي. من المثير للدهشة ، أن البحث على الإنترنت عن وصفات لتنفيذ مثل هذه الروابط في HATEOAS لم تسفر عن عدد كبير من النتائج ، فقط بعض الأسئلة المحيرة حول Stackoverflow. لذلك ، سننظر في العديد من الخيارات الممكنة وسنحاول اختيار الأفضل.

خيار الصفر خارج المنافسة هو عدم تنفيذ روابط عميقة. قد يكون هذا مناسبًا لبعض المشرفين أو تطبيقات الأجهزة المحمولة التي لا تتطلب القدرة على التبديل مباشرةً إلى الموارد الداخلية. هذا هو تماما في روح HATEOAS ، يمكن للمستخدم فتح صفحات فقط بالتتابع ، بدءا من نقطة الدخول ، لأن العميل لا يعرف كيفية الانتقال إلى المورد الداخلي مباشرة. لكن هذا الخيار غير مناسب لتطبيقات الويب - نتوقع أن يتم وضع إشارة مرجعية على رابط الصفحة الداخلية ، ولن يؤدي تحديث الصفحة إلى إعادة نقلنا إلى الصفحة الرئيسية للموقع.

لذلك ، الخيار الأول: القرص الصلب URL HATEOAS API. يعرف العميل هيكل عناوين الموارد التي تحتاج إلى ارتباطات عميقة ، ويعرف كيفية الحصول على معرف المورد للبحث. على سبيل المثال ، يعرض الخادم العنوان https://api.domain.test/api/books/1 كمرجع إلى مورد الكتاب. يعرف العميل أن الرقم "1" هو معرف الكتاب ويمكنه إنشاء عنوان URL هذا من تلقاء نفسه عند النقر على الرابط العميق. هذا هو بالتأكيد خيار العمل ، لكنه ينتهك مبادئ HATEOAS. لم يعد بالإمكان تغيير بنية العنوان ومعرف المورد ، وإلا فإن العميل سوف ينقطع ، فهناك اتصال صلب. هذا ليس هيتواس ، مما يعني أن الخيار لا يناسبنا.

الخيار الثاني هو استبدال عنوان URL الخاص بـ REST API في عنوان URL الخاص بالعميل. للحصول على مثال مع كتاب ، سيبدو الارتباط العميق كما يلي: https://domain.test/books?url=https://api.domain.test/api/books/1 . هنا ، يأخذ العميل رابط المورد الذي تم استلامه من الخادم ويستبدله بالكامل في عنوان الصفحة. هذا أشبه HATEOAS ، العميل لا يعرف عن المعرفات وبنية العنوان ، يتلقى رابط ويستخدمه كما هو. عند النقر على هذا الرابط المتعمق ، سيتلقى العميل المورد المطلوب عبر رابط REST API من معلمة url. يبدو أن الحل يعمل ، وبروح HATEOAS. ولكن إذا أضفت مثل هذا الرابط إلى إشاراتك المرجعية ، فلن نتمكن في المستقبل من تغيير عنوان المورد في واجهة برمجة التطبيقات (أو سيكون علينا دائمًا إعادة التوجيه إلى عنوان جديد). مرة أخرى ، تضيع إحدى مزايا HATEOAS ؛ هذا الخيار ليس مثاليًا أيضًا.

وبالتالي ، نريد أن يكون هناك روابط دائمة ، والتي ، مع ذلك ، قد تتغير. يوجد مثل هذا الحل ويستخدم على نطاق واسع على الإنترنت - توفر العديد من المواقع روابط قصيرة للصفحات الداخلية التي يمكن مشاركتها. بالإضافة إلى الإيجاز ، فإن مصلحتها هي أن الموقع يمكنه تغيير العنوان الحقيقي للصفحة ، لكن هذه الروابط لن تنقطع. على سبيل المثال ، تستخدم Microsoft ارتباطات Windows لمساعدة صفحات النموذج http://go.microsoft.com/fwlink/?LinkId=XXX . على مر السنين ، أعيد تصميم مواقع Microsoft عدة مرات ، ولكن الروابط في الإصدارات القديمة من Windows تستمر في العمل.

يبقى فقط لتكييف هذا الحل مع HATEOAS. وهذا هو الخيار الثالث - باستخدام معرفات الوصلات العميقة الفريدة في واجهة برمجة تطبيقات REST. سيبدو عنوان صفحة الكتاب الآن كما يلي: https://domain.test/books?deepLinkId=3f0fd552-e564-42ed-86b6-a8e3055e2763 . عند النقر فوق هذا الارتباط المتعمق ، يجب أن يسأل العميل الخادم: ما هو ارتباط المورد الذي يتوافق مع معرف deepLinkId ؟ سيعود الخادم الرابط https://api.domain.test/api/books/1 (جيدًا ، أو موردًا على الفور ، حتى لا يذهب مرتين). إذا تغير عنوان المورد في واجهة برمجة تطبيقات REST ، فسيرجع الخادم ببساطة رابط آخر. يتم حفظ سجل في قاعدة البيانات التي يتطابق معرف المرجع 3f0fd552-e564-42ed-86b6-a8e3055e2763 مع معرف الكيان للكتاب 1.

لهذا ، يجب أن تحتوي الموارد على حقل deepLinkId مع معرّفات روابطها العميقة ، ويجب على العميل استبدالها في عنوان الصفحة. يمكن وضع إشارة مرجعية على هذا العنوان وإرساله إلى الأصدقاء. ليس من الجيد أن يعمل العميل بشكل مستقل مع بعض المعرفات ، ولكن هذا يتيح لك الحفاظ على مزايا HATEOAS لواجهة برمجة التطبيقات ككل.

مثال


لن تكتمل هذه المقالة بدون تطبيق مثال. لاختبار المفهوم ، ضع في اعتبارك مثال لموقع كتالوج افتراضي على الإنترنت مع واجهة خلفية على Spring Boot / Kotlin وواجهة أمام SPA على Vue / JavaScript. يبيع المتجر الكتب وأقلام الرصاص ، ويحتوي الموقع على قسمين يمكنك من خلالهما رؤية قائمة المنتجات وفتح صفحاتها.

قسم "الكتب":



صفحة كتاب واحد:



لتخزين البضائع ، يتم تعريف كيانات Spring Data JPA:

 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 الارتباطات العميقة وتخزينها ، يتم DeepLink كيان DeepLink ، ويتم إنشاء مثيل له مع كل كائن مجال. يتم إنشاء المعرف نفسه وفقًا لمعيار UUID في وقت إنشاء الكيان. يحتوي الجدول الخاص به على معرف الرابط العميق والمعرف ونوع الكيان الذي يؤدي إليه الارتباط.

يتم تنظيم واجهة برمجة تطبيقات REST الخاصة بالخادم وفقًا لمفهوم HATEOAS ، وتحتوي نقطة إدخال واجهة برمجة التطبيقات على روابط لمجموعات المنتجات ، بالإضافة إلى رابط #deepLink لتشكيل روابط عميقة عن طريق استبدال معرف:

 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 عند نقطة الدخول:

 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 Vue Router ، حيث يتم تعريف المسار إلى صفحة الكتاب { path: '/books/:deepLinkId', name: 'book', component: Book, props: true } ، والروابط في قائمة الكتب تبدو كما يلي: <router-link :to="{name: 'book', params: {link: book._links.self.href, deepLinkId: book.deepLinkId}}">{{ book.name }}</router-link> .

بمعنى أنه عند فتح صفحة كتاب معين ، يتم استدعاء مكون Book ، والذي يستقبل معلمتين: link (ارتباط إلى مورد الكتاب في واجهة برمجة تطبيقات REST وقيمة الحقل href الخاص #self ) و deepLinkId من المورد).

 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 قيمة deepLinkId على عنوان الصفحة /books/:deepLinkId ، ويطلب المكون المورد من خلال الرابط المباشر من خاصية link . عند فرض تحديث الصفحة ، يقوم Vue Router بتعيين خاصية المكون deepLinkId ، والحصول عليها من عنوان الصفحة. تبقى خاصية link null . يتحقق المكون: إذا كان هناك رابط مباشر تم الحصول عليه من المجموعة ، فسيطلب المورد ذلك. إذا كان معرف deepLinkId فقط ، deepLinkId استبداله في ارتباط #deepLink من نقطة الإدخال لاستلام المورد من خلال الرابط العميق.

في الخلفية ، تبدو طريقة التحكم في الروابط العميقة كما يلي:

 @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 بواسطة entityId . تتم إعادة توجيه الطلب إلى هذا العنوان. وبالتالي ، إذا تغير رابط وحدة التحكم في المستقبل ، فسيكون من الممكن ببساطة تغيير منطق تكوين الارتباط في طريقة deepLink .

شفرة المصدر الكاملة للمثال متاحة على جيثب .

Source: https://habr.com/ru/post/ar445092/


All Articles