حذف ناعم في REST API

الصورة

حتى لا يشعر المستخدم بألم من البيانات المفقودة بشكل لا رجعة فيه ، يجدر التفكير في الحذف الناعم. مع الحذف الناعم ، لا يتم حذف السجل فعليًا من قاعدة البيانات ، ولكن يتم تمييزه فقط على أنه محذوف. هذا يجعل من السهل استعادة البيانات عن طريق إعادة تعيين العلم.

لقد قمت مؤخرًا بتطبيق الحذف الناعم في إحدى خدمات REST الخاصة بنا. أولئك الذين يهتمون بما فعلته ، أدعوكم إلى القط.

الإدخال المطلوب


النقاش حول ما إذا كنت تريد استخدام إزالة خفيفة هو قديم جدا. مجرد إلقاء نظرة على holivars طويلة هنا وهنا .

الأكثر منطقية هو الموقف الذي يعتمد كل شيء على الموقف. هناك حالات يكون فيها الحذف الناعم مناسبًا أو ضروريًا ؛ فهناك حالات عندما تكون حجج معارضي الحذف الناعم جديرة بالاهتمام. بالمناسبة ، فإن الحجة المهمة ضد الحذف الناعم هي الإجابة التي جاءت من عام 2018: إذا كنا نتحدث عن حسابات المستخدمين ، فإن الحذف الناعم يتعارض مع الناتج المحلي الإجمالي .

قررنا أنه في خدمتنا لتخزين المستندات ، يعد الحذف الناعم ضروريًا.

نهج مريح


إذا كنا نريد تنفيذ الحذف الناعم في إحدى الخدمات ، فنحن بحاجة إلى فهم كيف ينبغي أن تبدو من وجهة نظر الواجهة. أظهر البحث على الإنترنت أن السؤال المعتاد لدى الأشخاص هو ما إذا كان يجب استخدام DELETE {resource} كما كان من قبل ، أم أنه من الأفضل استخدام طريقة PATCH بدلاً من ذلك مع نص يشتمل على شيء مثل {status: 'delete'} .

هنا رأي الناس لا لبس فيه: من الضروري استخدام DELETE كما كان من قبل. من وجهة نظر العميل ، الحذف هو أيضًا حذف في إفريقيا. يجب ألا يتغير أي شيء: إذا تم حذف مورد ، فسيصبح الوصول إليه غير ممكن ؛ إذا أراد العميل حذف المورد ، فهو يعلم أن طريقة HTTP DELETE مخصصة لهذا الغرض. ليس من الضروري تكريس العميل لتفاصيل بالضبط كيف تنفذ الخدمة الحذف.

لكن إلى جانب ذلك ، كنت قلقًا بشأن مسألة كيفية استرداد الموارد المحذوفة. بالطبع ، يتم حل هذه المشكلة عن طريق إدارة قاعدة البيانات. ومع ذلك ، أود أن أكون قادرًا على القيام بذلك من خلال REST API. وهنا نأتي إلى الصراع. اتضح أن العميل لا يزال بحاجة إلى تخصيص تفاصيل التنفيذ؟

لم يسفر البحث لمدة طويلة عن أي نتائج ، حتى صادفت مقالًا جيدًا لـ Dan Yoder يفحص المقال دلالات طلبات HTTP المختلفة ويقترح أنه بدلاً من الحذف المادي ، انقل الموارد البعيدة إلى الأرشيف . بالإضافة إلى ذلك ، سيكون من الرائع أن تقوم DELETE بإرجاع رابط إلى المورد المؤرشفة. يمكن للمستخدم دائمًا استعادة المورد المحذوف عن طريق إرسال طلب POST إلى الأرشيف.

تصميم


خدمة REST الخاصة بنا مبنية على ASP.NET Web API باستخدام Entity Framework. كما قلت ، أقوم بحذف ضعيف لمورد يسمى المستند.

لذلك ، تحتاج أولاً إلى إضافة الأعمدة إلى الجدول المقابل. كعلم ، يمكنني استخدام طابع زمني يسمى المحذوفة. إذا لم تكن القيمة فارغة ، فسيتم اعتبار المورد محذوفًا. بالإضافة إلى ذلك ، من المفيد الحصول على معلومات حول من حذف المورد.

ALTER TABLE Documents ADD Deleted datetime NULL, DeletedBy int NULL GO 

سيقوم الإجراء DELETE في وحدة التحكم الآن ببساطة بتعيين قيم هذه الحقول بدلاً من حذف السجل فعليًا. بالإضافة إلى ذلك ، سيعيد DELETE نصًا له مرجع قياسي للوثيقة المؤرشفة:

 { "links": { "archive": "documents/{id}/deleted" } } 

في الحقيقة ، هذه نقطة مهمة: الارتباط يساعد العميل على فهم أنه لا يتم حذف المستند ، ولكن يتم نقله .

يجب أن توفر وحدة التحكم الجديدة للمستندات المؤرشفة الطرق التالية:
الحصول على وثائق / حذفالحصول على مجموعة من جميع الوثائق المحذوفة
الحصول على المستندات / {id} / المحذوفةإرجاع المستند المحذوف
مستندات POST / {id} / محذوفةيستعيد حذف الوثيقة ؛
لا يتطلب الجسم ؛ إرجاع 201 تم الإنشاء
حذف المستندات / {id} / محذوفحذف مستند ماديًا

التنفيذ


في البداية ، خططت لإضافة طريقتين إلى قاعدة البيانات الخاصة بي:

 CREATE VIEW DeletedDocuments AS SELECT * FROM Documents WHERE Deleted IS NOT NULL GO CREATE VIEW AvailableDocuments AS SELECT * FROM Documents WHERE Deleted IS NULL GO 

يبدو لي أن هذا سيكون مشكلة أقل: بدلاً من تعيين الشروط في التعليمات البرمجية ، أحصل على اثنين فقط من خصائص DbSet مختلفة في سياق DB الخاص بي. صحيح ، يجب أن يكون لديك كيانان متطابقان في النموذج ، ولكن هذه هي خاصية كائنات POCO في سياق EF - بالضبط كيان واحد يتوافق مع كل جدول.

بالمناسبة ، يمكن أن تكون التمثيلات في SQL مفيدة لإطار الكيان من نواح أخرى: مع مساعدتهم ، على سبيل المثال ، يمكنك الرجوع إلى الجداول من قاعدة بيانات أخرى إذا كنت لا ترغب في إنشاء عدة سياقات DB.

ومع ذلك ، في حالتي ، فإن العدد مع وجهات النظر لم يمر. أثناء الترخيص ، تحتاج إلى العمل مع جميع المستندات ، لأن المستخدمين لديهم نفس الحقوق في المستندات المحذوفة مثل تلك الموجودة.

لذلك ، قررت أن يكون لدي مستندات DbSet واحدة فقط في DbContext ، وفي التعليمة البرمجية في كل مرة أقوم فيها بتحديد ما هو مطلوب بالضبط في الوقت الحالي:

 var availableDocuments = DbContext.Documents.Where(d => d.Deleted == null); var deletedDocuments = DbContext.Documents.Where(d => d.Deleted != null); var allDocuments = DbContext.Documents; 

الموارد ذات الصلة


المستند هو مورد ترتبط به الموارد الأخرى. على سبيل المثال ، لدينا اسم مستعار للمستند. بمعنى أنه يمكنك الحصول على مستند ليس فقط عن طريق مسار المستندات / {id} ، ولكن أيضًا عن طريق مسار / {alias} ، حيث يكون الاسم المستعار عبارة عن سلسلة فريدة.

بعد حذف مستند ، يجب أن تصبح جميع الأسماء المستعارة المرتبطة بها "غير مرئية": إذا تلقى العميل في وقت سابق قائمة بجميع الأسماء المستعارة باستخدام مستندات GET / الأسماء المستعارة ، ثم بعد حذف المستند ، ستختفي الأسماء المستعارة من القائمة.

لكنهم بقوا في قاعدة البيانات! نريد توفير القدرة على استعادة المستند في الحالة التي تم حذفها فيه. قد يتسبب هذا في حدوث ارتباك للعميل: إنه يحاول إضافة اسم مستعار جديد لمستند آخر ، والقائمة التي تم إرجاعها من مستندات GET / الأسماء المستعارة لا تحتوي على مثل هذا الخط ، ومع ذلك ترفض الخدمة إضافته.

لا أعتقد أن هذه مشكلة خطيرة. ومع ذلك ، إذا كنت بحاجة إلى حلها ، فيمكنك إضافة مستندات GET لنقاط النهاية / حذف / الأسماء المستعارة . ثم يصبح كل شيء في مكانه: لا يمكن للخدمة إضافة اسم مستعار ، نظرًا لأن هذه القيمة مستخدمة بالفعل من قبل المستند البعيد.

قد يطرح السؤال: هل يستحق رمي اسم مستعار من القائمة التي تم إرجاعها من المستندات / الأسماء المستعارة ؟ دعهم يبقون! لا أعتقد أن هذا القرار سيكون صحيحًا. ثم اتضح أن قائمة الأسماء المستعارة ستحتوي على روابط مقطوعة ، لأن الخدمة ستُرجع 404 غير موجود إذا حاول العميل الحصول على المستند المحذوف بواسطة الاسم المستعار. إذا كان الأمر يتعلق بالموارد الفرعية المرتبطة بالوثيقة ، فينبغي أن يكون السلوك هو نفسه تمامًا كما لو كنا نحذف المستند فعليًا.

تنظيف الأرشيف


الحذف الناعم ، بالإضافة إلى القدرة على استرداد البيانات بسهولة ، له العديد من المزايا الأخرى. عملية الحذف في قواعد البيانات العلائقية هي عملية مكلفة. وحتى إذا أدى حذف سجل واحد إلى حذف متتالي للسجلات في الجداول الأخرى ، فهذا محفوف بالمأزق. لذلك ، تكون الإزالة الناعمة أسرع وأكثر موثوقية من الإزالة البدنية.

ولكن هناك عيب واحد كبير. بدأت القاعدة تنمو.

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

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

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


All Articles