يجب سحب الخطوات والمسافة والسعرات الحرارية ومعدل ضربات القلب والنوم من منصة المستخدم (Android Health Connect على Android، و Apple HealthKit على iOS) بواسطة تطبيق صحي وتغذوي - وتقديمها كملف يومي واحد نظيف ومزيل للتكرارات. توجد أوجه تشابه قليلة جدًا بين واجهتي الـ APIs: آليات قراءة منفصلة، ومخططات ترخيص مختلفة، وهياكل بيانات متباينة. لقد قمنا ببناء طبقة المزامنة المتنقلة التي تجعلها تبدو كواحدة.
التحدي
- اثنتان من واجهات الـ APIs الأصلية لا تتفقان على شيء. يعيد Apple HealthKit التجميعات اليومية عبر عمليات الاستدعاء (callbacks)؛ بينما يعيد Health Connect عينات أولية مقسمة على صفحات مع بيانات وصفية غنية للمصدر. نفس المفاهيم، أشكال مختلفة تمامًا — وكان على التطبيق توحيد كليهما في مخطط واحد (schema).
- العد المزدوج هو الافتراضي. يمكن للهاتف والساعة الذكية وتطبيق طرف ثالث الإبلاغ عن نفس الـ 8000 خطوة. يؤدي جمعها ببساطة إلى تضخيم كل مقياس. احتاج التطبيق إلى التعرف على المصادر المتداخلة وحساب كل نشاط في العالم الحقيقي مرة واحدة.
- البطارية والشبكة لا يمكنهما تحمل تكلفة التحديث المستمر. تتغير البيانات الصحية طوال اليوم، ولكن قراءة السجل الكامل مع كل مزامنة ستستنزف البطارية وتشبع اتصالات الهاتف المحمول. كان يجب أن يظل تردد المزامنة خفيفًا مع الحفاظ على شعور البيانات بأنها حديثة ومباشرة.
يمكن أن تكون الأذونات مربكة. عشرات من أنواع البيانات، ونموذجان للأذونات، وقيود القراءة في الخلفية، ومستخدمون يمنحون بعض النطاقات دون غيرها — كل ذلك يجب على التطبيق التعامل معه دون تعطيل سير العمل.
حلنا
لقد قمنا ببناء مزامنة ثلاثية الطبقات: طبقة أصلية رفيعة تقرأ الـ API لكل منصة، وطبقة توحيد على الجهاز تزيل التكرارات وتجمع البيانات في ملخصات يومية، ويقوم الجزء الخلفي (backend) بتخزينها وتوفيقها في دفتر حسابات موثوق به واحد. لا يقوم العميل أبدًا بشحن عينات خام — بل يشحن تجميعات يومية نظيفة وصحيحة حسب المنطقة الزمنية.

البنية
- يقرأ iOS بيانات Apple HealthKit عبر react-native-health؛ ويقرأ Android بيانات Health Connect عبر react-native-health-connect.
- تقوم طبقة التوحيد على الجهاز بتحويل مخرجات كلا المنصتين إلى مخطط واحد وإزالة التكرارات قبل أن يغادر أي شيء الهاتف.
- مشغلات المزامنة — 30 يومًا من السجل عند الإطلاق الأول، ثم قراءات تزايدية خفيفة ليوم واحد بفاصل 10 دقائق وعند كل استئناف للتطبيق.
- النقل — يتم إرسال التجميعات اليومية الموحدة (normalized daily rollups) عبر POST إلى /user-health/system-activity في الخادم الرئيسي، والذي بدوره يقوم بإعادة توجيهها (مع معرف المستخدم والمنطقة الزمنية) إلى الخدمة المصغرة الصحية (health microservice).
- الاستمرارية (Persistence) — يتم تخزين كل مصدر/يوم بشكل متكرر (idempotently) كـ HealthSystemAggregate؛ ثم يقوم تدفق التغيير (change stream) في MongoDB بتوفيقها في HealthLedger لكل يوم.
سير عمل مُدار باستخدام Expo / React Native مع وحدات HealthKit و Health Connect الأصلية.
الميزات الرئيسية
- عقد توحيد واحد لواجهتي برمجة تطبيقات (APIs). تغذي القارئات الخاصة بالمنصة خطوة formatHealthData واحدة تصدر نفس الشكل بغض النظر عن المصدر — بحيث لا يتفرع الجزء الخلفي وواجهة المستخدم أبدًا بناءً على iOS مقابل Android.
2. إزالة التكرارات على الجهاز بناءً على الحد الأقصى. ضمن كل مصدر، يتم جمع قراءات اليوم؛ ثم تكون القيمة اليومية هي Math.max() عبر المصادر (السعرات الحرارية مُفهرسة بواسطة source|deviceType)، لذا تتراكم القراءات المتعددة من جهاز واحد بينما لا تتضاعف القراءات من ساعة وهاتف يبلغان عن نفس اليوم:
// Sum readings within each source, then take the max ACROSS sources dateSourceMap[date][source] += entry.count; const totalSteps = Math.ceil(Math.max(...Object.values(dateSourceMap[date]))); |
3.استراتيجية القراءة الصحيحة لكل منصة. تتم قراءات Health Connect بتقسيمها على صفحات باستخدام حلقة pageToken (1,000 سجل/صفحة)؛ بينما يتم تكرار تجميعات HealthKit اليومية يومًا بيوم. كل خصوصية محتواة في قارئها الخاص، وغير مرئية لبقية التطبيق.
4. تردد مزامنة يحترم البطارية. يتم تشغيل عملية تعبئة البيانات التاريخية بالكامل لمدة 30 يومًا مرة واحدة فقط (محمية بعلامة استدعاء أولى)؛ بعد ذلك، تقوم كل مزامنة بسحب بيانات يوم واحد فقط — سريعة على شبكة الجوال، واقتصادية في استهلاك الطاقة.
5. تجميع يومي صحيح حسب المنطقة الزمنية. يتم تجميع العينات في مفاتيح YYYY-MM-DD في المنطقة الزمنية الخاصة بالمستخدم، بحيث يظهر التمرين الذي يتم في الساعة 11 مساءً في اليوم الصحيح بغض النظر عن موقع الخادم.
6. كتابات خلفية متكررة (Idempotent backend writes). يقوم كل تجميع بعملية upsert بناءً على (userId, deviceId, source, date) — إرسال نفس اليوم مرة أخرى لا يؤدي إلى أي عملية، مما يجعل عمليات إعادة المحاولة والمزامنات المتداخلة آمنة.
7. تدهور نزيه للأداء (Honest degradation). يتم التقاط أخطاء الأذونات المفقودة والقراءات الفارغة وأخطاء تجاوز الحد الأقصى لمعدل Health Connect وعرضها بوضوح — لا يؤدي اليوم الفارغ أبدًا إلى تعطل المزامنة، ويتلقى المستخدم إشعارًا واضحًا بدلاً من فشل صامت.
النتائج
- ملف صحي واحد ومتسق عبر iOS و Android — يصبح الاختلاف بين المنصتين غير مرئي لبقية التطبيق.
- تمت إزالة تكرار مصادر الجهاز/التطبيق المتداخلة، لذا تعكس الخطوات والسعرات الحرارية الواقع بدلاً من المجاميع المتضخمة.
- تحافظ المزامنات التزايدية الخفيفة على تحديث البيانات دون استنزاف البطارية أو استهلاك بيانات الهاتف المحمول.
الكتابات الصحيحة حسب المنطقة الزمنية والمتكررة (idempotent) تعني أن كل مقياس يقع في اليوم الصحيح، وعمليات إعادة المحاولة لا تفسد السجل أبدًا.
مكدس التقنيات
React Native (Expo) · react-native-health (HealthKit) · react-native-health-connect · NestJS · MongoDB · MongoDB Change Streams · moment-timezone

