تم النشر بتاريخ في تكنولوجيا

نموذج البيانات وراء مرونة Notion

بواسطة Jake Teton-Landis

الهندسة

12 دقيقة للقراءة

رأى جيل من الرواد (دوغ إنجلبارت، تيد نيلسون، آلان كاي، والعديد غيرهم) الكمبيوتر كأداة لتعزيز حل المشكلات البشرية من خلال منح الناس القوة على المعلومات.

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

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

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

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

يجعل نموذج الكتل Notion فريداً، وهو الأساس لكيفية تفكير Notion في إحياء ما اعتقده الرواد أن الحوسبة، كوسيلة، يمكن أن تصبح.

أساسيات الكتل

تعتبر كتل Notion القطع الفردية التي تمثل جميع وحدات المعلومات داخل محرر Notion. تحدد خصائص الكتلة كيفية عرض تلك المعلومات وتنظيمها.

كل كتلة لها الخصائص التالية:

  • ID-كل كتلة يمكن التعرف عليها بشكل فريد من خلال معرفها. يمكنك رؤية معرف كتل الصفحة في نهاية عنوان URL في متصفحك. نستخدم UUIDs (UUID v4) التي تم إنشاؤها عشوائياً للمعرفات في Notion.

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

  • النوع-كل كتلة لها نوع، يحدد كيفية عرض الكتلة، وكيف يتم تفسير خصائص الكتلة. يدعم Notion العديد من أنواع الكتل، معظمها يمكنك رؤيته في قائمة "كتلة جديدة" التي تظهر عندما تضغط على زر + أو في قائمة /:

بالإضافة إلى الخصائص التي تصف الكتلة نفسها، كل كتلة لها خصائص تحدد علاقتها مع كتل أخرى:

  • المحتوى-مصفوفة (أو مجموعة مرتبة) من معرفات الكتل تمثل المحتوى داخل هذه الكتلة، مثل العناصر النقطية المتداخلة في قائمة نقطية أو النص داخل مفتاح تبديل.

  • الأب-معرف الكتلة للأب. تستخدم الكتلة الأم فقط لأغراض الأذونات.

كيف تتناسب الكتل معاً

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

النوع والخصائص

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

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

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

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

المحتوى وشجرة العرض

مرونة نموذج الكتلة تسمح أيضاً بتداخل الكتل داخل كتل أخرى، مثل النص داخل مفتاح أو صفحات فرعية متداخلة بلا حدود داخل الصفحات. سمة المحتوى لكتلة هي ما يخزن مصفوفة معرفات الكتل (أو المؤشرات) التي تشير إلى تلك الكتل المتداخلة.

في مثال قائمة المهام، لدينا كتلة قائمة المهام ("اكتب منشور مدونة عن الكتل") مع ثلاثة معرفات كتل في مصفوفة محتواها. نحن نفكر في هذه المعرفات على أنها "مؤشرات نحو الأسفل"، ونسمي الكتل التي تشير إليها "محتوى" أو "أطفال العرض".

كل كتلة تحدد الموضع والترتيب الذي يتم فيه عرض كتل محتواها. نحن نسمي هذه العلاقة الهرمية بين الكتل وأطفال العرض الخاص بهم "شجرة العرض". لكن، لا تبدو كشجرة ذات فروع-أنواع الكتل المختلفة تعرض أطفالها بطرق مختلفة.

إليك بعض الأمثلة على كيفية عرض سمة المحتوى بواسطة أنواع كتل مختلفة:

  • كتل القوائم-نص، قائمة نقطية، و قائمة المهام. كتل القوائم تعرض محتواها بشكل متداخل.

  • المفاتيح-كتل قائمة المفاتيح تعرض المحتوى فقط عند التوسع. بخلاف ذلك، فإنها تعرض فقط خاصية العنوان.

  • الصفحات-كتل صفحة تعرض محتواها في صفحة جديدة، بدلاً من عرضها متداخلة في الصفحة الحالية. لرؤية هذا المحتوى، ستحتاج إلى النقر على الصفحة الجديدة.

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

تحرير شجرة العرض

هل سبق لك أن تفاجأت بكيفية عمل المسافة البادئة في Notion؟ في معالجات الكلمات التقليدية، تكون المسافة البادئة تقديمية: تؤثر فقط على تباعد النص عن الهوامش. في Notion، تكون المسافة البادئة هيكلية: إنها تعكس هيكل شجرة العرض. بعبارة أخرى، عندما تقوم بإضافة مسافة بادئة لشيء ما في Notion، فإنك تتلاعب بالعلاقات بين الكتل ومحتواها، وليس مجرد إضافة نمط.

على سبيل المثال، الضغط على المسافة البادئة في كتلة المحتوى يحاول إضافة تلك الكتلة إلى محتوى أقرب كتلة شقيقة في شجرة المحتوى.

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

الأذونات

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

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

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

  2. السبب الثاني هو ميكانيكي. لتنفيذ فحوصات الأذونات لكتلة، نحتاج إلى البحث في الشجرة، والحصول على أسلاف تلك الكتلة حتى جذر الشجرة (وهو مساحة العمل). محاولة العثور على مسار السلف هذا من خلال البحث في جميع مصفوفات محتوى الكتل غير فعالة، خاصة على العميل.

بدلاً من ذلك، نستخدم "مؤشر صاعد" - خاصية الوالد - لنظام الأذونات. تشير مؤشرات الوالد الصاعدة، ومؤشرات المحتوى الهابطة إلى بعضها البعض (خارج بعض الحالات النادرة التي نعمل على تنظيفها).

حياة الكتلة

تبدأ حياة الكتلة على العميل.

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

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

إنشاء وتحديث الكتل

تضغط على إدخال - هذا ينشئ كتلة جديدة لائحة المهام.

أولاً، يحدد العميل جميع الخصائص الأولية للكتلة، وينشئ معرفاً فريداً جديداً، ويحدد نوع الكتلة المناسب (to_do)، ويملأ خصائص الكتلة (عنوان فارغ title، و checked: [["لا"]]). يبني عمليات لتمثيل إنشاء كتلة جديدة بتلك الخصائص.

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

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

في نفس الوقت، يتم حفظ المعاملة في TransactionQueue، الجزء من العميل المسؤول عن إرسال جميع المعاملات إلى خوادم Notion حتى يتم حفظ بياناتك ومشاركتها مع المتعاونين. تخزن TransactionQueue المعاملات بأمان في IndexedDB أو SQLite (اعتماداً على النظام الأساسي) حتى يتم حفظها بواسطة الخادم أو رفضها.

حفظ التغييرات على الخادم

إليك كيف يتم حفظ كتلتك بأمان على الخادم، حتى يتمكن صديقك من رؤيتها.

عادةً، تكون TransactionQueue فارغة، لذا يتم إرسال المعاملة لإنشاء الكتلة إلى خادم Notion على الفور في طلب API. يتم تسلسل بيانات المعاملة إلى JSON ونشرها إلى نقطة نهاية API /saveTransactions.

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

بمجرد وصول الطلب إلى خادم API الخاص بـ Notion:

  1. نقوم بتحميل جميع الكتل والأبناء المعنيين في المعاملة. هذا يعطينا صورة "قبل" في الذاكرة. في هذا المثال، تذكر أننا نقوم بإنشاء كتلة. لذا نحتاج إلى تحميل كتلة الصفحة على الأقل حتى نتمكن من إدراج معرف الكتلة التي تم إنشاؤها حديثاً في مصفوفة محتوى الصفحة.

  2. نقوم بتكرار بيانات "قبل" التي تم تحميلها للتو في الذاكرة. ثم نقوم بتطبيق العمليات في المعاملة على النسخة الجديدة لإنشاء بيانات "بعد".

  3. ثم نستخدم كل من بيانات "قبل" و "بعد" للتحقق من التغييرات المتعلقة بالأذونات وتناسق البيانات. إذا كانت كل الأمور صحيحة (عادة ما تكون كذلك)، يتم حفظ جميع السجلات التي تم إنشاؤها أو تغييرها في قاعدة البيانات - مما يعني أن الكتلة الخاصة بك قد تم إنشاؤها رسمياً الآن.

  4. في هذه المرحلة، هناك استجابة HTTP "نجاح" لطلب API الأصلي الذي أرسله عميلك. هذا يؤكد أن عميلك يعرف أن المعاملة قد تم حفظها بنجاح وأنه يمكنه الانتقال إلى حفظ المعاملة التالية في TransactionQueue.

  5. في الخلفية، نقوم بجدولة عمل إضافي اعتماداً على نوع التغيير الذي تم إجراؤه على معاملتك. على سبيل المثال، نقوم بجدولة تاريخ النسخة لقطات وفهرسة نص الكتلة لـ البحث السريع. من المهم أيضاً أننا نبلغ MessageStore - خدمة التحديثات الفورية لـ Notion - بالتغييرات التي أجريتها.

سنتناول كيفية وصول البيانات إلى شاشة صديقك في القسم التالي.

التحديثات الفورية

لقد ضغطت على إدخال، وأنشأت كتلة جديدة، والآن تظهر كتلتك على شاشة صديقك. كيف يعمل ذلك؟

كل عميل لديه اتصال WebSocket طويل الأمد مع MessageStore، خدمة التحديثات الفورية لـ Notion. عندما يقوم عميل Notion بعرض كتلة (أو صفحة، أو أي نوع آخر من السجلات)، يشترك العميل في تغييرات تلك السجل من MessageStore باستخدام اتصال WebSocket هذا. عندما يفتح صديقك نفس الصفحة التي تفتحها، يكونون مشتركين في تغييرات جميع تلك الكتل.

بعد أن مرت تغييراتك عبر عملية saveTransactions، أبلغت API MessageStore بالإصدارات الجديدة المسجلة. يجد MessageStore اتصالات العملاء المشتركين في تلك السجلات المتغيرة، وينقل النسخة الجديدة إليهم عبر اتصال WebSocket الخاص بهم.

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

كتل القراءة

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

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

طريقة واجهة برمجة التطبيقات لتحميل البيانات لصفحة تُسمى loadPageChunk-تنحدر من نقطة بداية (من المحتمل أن تكون معرف كتلة صفحة) إلى أسفل شجرة المحتوى، وتعيد الكتل في شجرة المحتوى بالإضافة إلى أي سجلات معتمدة مطلوبة لعرض تلك الكتل بشكل صحيح. نستخدم عدة طبقات من التخزين المؤقت لـ loadPageChunk، ولكن في أسوأ الأحوال، قد تحتاج هذه الواجهة إلى العديد من الرحلات إلى قاعدة البيانات حيث تتجول بشكل متكرر في الشجرة لتتبع الكتل واعتماديات سجلاتها.

جميع البيانات المحملة بواسطة loadPageChunk يتم وضعها في الذاكرة (وتُحفظ في RecordCache إذا كنت تستخدم التطبيق). بمجرد أن تكون البيانات في الذاكرة، نقوم بتخطيط الصفحة وعرضها باستخدام React.

بناء كتل لما هو قادم

الكتل هي المكون الأكثر أساسية في مهمة Notion لتمكين أي شخص أو عمل من تخصيص البرمجيات لمشاكلهم. تحدد هذه البنية مسار مستقبل Notion-أنواع كتل جديدة، وأتمتة (مثل واجهة برمجة التطبيقات)، وتدفقات العمل، والوظائف التي تمكنك من إنشاء أدوات أكثر قوة.

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

نحن نبحث عن المساعدة لبناء مستقبل Notion. هل أنت ذلك الشخص؟

مشاركة هذا المنشور


جرِّب الآن

ابدأ العمل على الويب أو سطح المكتب

لدينا أيضاً تطبيقات متوافقة مع Mac وWindows.

لدينا أيضاً تطبيقات متوافقة مع iOS وAndroid.

تطبيق الويب

تطبيق سطح المكتب