trustgraph/docs/tech-specs/pubsub.ar.md
Alex Jenkins 8954fa3ad7 Feat: TrustGraph i18n & Documentation Translation Updates (#781)
Native CLI i18n: The TrustGraph CLI has built-in translation support
that dynamically loads language strings. You can test and use
different languages by simply passing the --lang flag (e.g., --lang
es for Spanish, --lang ru for Russian) or by configuring your
environment's LANG variable.

Automated Docs Translations: This PR introduces autonomously
translated Markdown documentation into several target languages,
including Spanish, Swahili, Portuguese, Turkish, Hindi, Hebrew,
Arabic, Simplified Chinese, and Russian.
2026-04-14 12:08:32 +01:00

42 KiB

layout title parent
default البنية التحتية للنشر والاشتراك (Pub/Sub) Arabic (Beta)

البنية التحتية للنشر والاشتراك (Pub/Sub)

Beta Translation: This document was translated via Machine Learning and as such may not be 100% accurate. All non-English languages are currently classified as Beta.

نظرة عامة

يوثق هذا المستند جميع الاتصالات بين قاعدة بيانات TrustGraph والبنية التحتية للنشر والاشتراك. حاليًا، يتم ترميز النظام لاستخدام Apache Pulsar. تحدد هذه التحليلات جميع نقاط التكامل لإعلام عمليات إعادة الهيكلة المستقبلية نحو تجريد نشر واشتراك قابل للتكوين.

الحالة الحالية: نقاط تكامل Pulsar

1. استخدام عميل Pulsar المباشر

الموقع: trustgraph-flow/trustgraph/gateway/service.py

يقوم بوابة واجهة برمجة التطبيقات باستيراد وتثبيت عميل Pulsar مباشرةً:

السطر 20: import pulsar الأسطر 54-61: تثبيت مباشر لـ pulsar.Client() مع pulsar.AuthenticationToken() الاختياري الأسطر 33-35: تكوين مضيف Pulsar الافتراضي من متغيرات البيئة الأسطر 178-192: وسيطات سطر الأوامر لـ --pulsar-host، --pulsar-api-key، و --pulsar-listener الأسطر 78، 124: تمرير pulsar_client إلى ConfigReceiver و DispatcherManager

هذا هو الموقع الوحيد الذي يقوم بتثبيت عميل Pulsar مباشرةً خارج طبقة التجريد.

2. إطار عمل المعالج الأساسي

الموقع: trustgraph-base/trustgraph/base/async_processor.py

يوفر الفئة الأساسية لجميع المعالجات اتصال Pulsar:

السطر 9: import _pulsar (للتعامل مع الاستثناءات) السطر 18: from . pubsub import PulsarClient السطر 38: إنشاء pulsar_client_object = PulsarClient(**params) الأسطر 104-108: خصائص تعرض pulsar_host و pulsar_client السطر 250: تستدعي الطريقة الثابتة add_args() PulsarClient.add_args(parser) لوسيطات سطر الأوامر الأسطر 223-225: معالجة الاستثناءات لـ _pulsar.Interrupted

ترث جميع المعالجات من AsyncProcessor، مما يجعل هذا نقطة التكامل المركزية.

3. تجريد المستهلك

الموقع: trustgraph-base/trustgraph/base/consumer.py

يستهلك الرسائل من قوائم الانتظار ويستدعي وظائف المعالج:

استيرادات Pulsar: السطر 12: from pulsar.schema import JsonSchema السطر 13: import pulsar السطر 14: import _pulsar

الاستخدام المحدد لـ Pulsar: الأسطر 100، 102: pulsar.InitialPosition.Earliest / pulsar.InitialPosition.Latest السطر 108: غلاف JsonSchema(self.schema) السطر 110: pulsar.ConsumerType.Shared الأسطر 104-111: self.client.subscribe() مع معلمات خاصة بـ Pulsar الأسطر 143، 150، 65: طرق consumer.unsubscribe() و consumer.close() السطر 162: استثناء _pulsar.Timeout الأسطر 182، 205، 232: consumer.acknowledge() / consumer.negative_acknowledge()

ملف المواصفات: trustgraph-base/trustgraph/base/consumer_spec.py السطر 22: يشير إلى processor.pulsar_client

4. تجريد الناشر

الموقع: trustgraph-base/trustgraph/base/producer.py

يرسل الرسائل إلى قوائم الانتظار:

استيرادات Pulsar: السطر 2: from pulsar.schema import JsonSchema

الاستخدام المحدد لـ Pulsar: السطر 49: غلاف JsonSchema(self.schema) الأسطر 47-51: self.client.create_producer() مع معلمات خاصة بـ Pulsar (الموضوع، المخطط، تمكين التجزئة) الأسطر 31، 76: طريقة producer.close() الأسطر 64-65: producer.send() مع الرسالة والخصائص

ملف المواصفات: trustgraph-base/trustgraph/base/producer_spec.py السطر 18: يشير إلى processor.pulsar_client

5. تجريد الناشر

الموقع: trustgraph-base/trustgraph/base/publisher.py

نشر الرسائل بشكل غير متزامن مع تخزين الرسائل مؤقتًا في قائمة الانتظار:

استيرادات Pulsar: السطر 2: from pulsar.schema import JsonSchema السطر 6: import pulsar

الاستخدام المحدد لـ Pulsar: السطر 52: غلاف JsonSchema(self.schema) الأسطر 50-54: self.client.create_producer() مع معلمات خاصة بـ Pulsar الأسطر 101، 103: producer.send() مع الرسالة والخصائص الاختيارية الأسطر 106-107: طرق producer.flush() و producer.close()

6. تجريد المشترك

الموقع: trustgraph-base/trustgraph/base/subscriber.py

يوفر توزيع رسائل متعددة المستلمين من خلال قوائم الانتظار:

استيرادات Pulsar: السطر 6: from pulsar.schema import JsonSchema السطر 8: import _pulsar

الاستخدام الخاص بـ Pulsar: السطر 55: JsonSchema(self.schema) wrapper السطر 57: self.client.subscribe(**subscribe_args) الأسطر 101، 136، 160، 167-172: استثناءات Pulsar: _pulsar.Timeout, _pulsar.InvalidConfiguration, _pulsar.AlreadyClosed الأسطر 159، 166، 170: طرق المستهلك: negative_acknowledge(), unsubscribe(), close() الأسطر 247، 251: إقرار الرسالة: acknowledge(), negative_acknowledge()

ملف المواصفات: trustgraph-base/trustgraph/base/subscriber_spec.py السطر 19: يشير إلى processor.pulsar_client

7. نظام المخططات (قلب الظلام)

الموقع: trustgraph-base/trustgraph/schema/

يتم تعريف كل مخطط رسالة في النظام باستخدام إطار عمل المخططات الخاص بـ Pulsar.

المكونات الأساسية: schema/core/primitives.py السطر 2: from pulsar.schema import Record, String, Boolean, Array, Integer ترث جميع المخططات من الفئة الأساسية Record الخاصة بـ Pulsar جميع أنواع الحقول هي أنواع Pulsar: String(), Integer(), Boolean(), Array(), Map(), Double()

أمثلة على المخططات: schema/services/llm.py (السطر 2): from pulsar.schema import Record, String, Array, Double, Integer, Boolean schema/services/config.py (السطر 2): from pulsar.schema import Record, Bytes, String, Boolean, Array, Map, Integer

تسمية الموضوع: schema/core/topic.py الأسطر 2-3: تنسيق الموضوع: {kind}://{tenant}/{namespace}/{topic} هذه البنية URI خاصة بـ Pulsar (على سبيل المثال، persistent://tg/flow/config)

التأثير: تستخدم جميع تعريفات الرسائل/الردود في قاعدة التعليمات البرمجية مخططات Pulsar يتضمن ذلك الخدمات الخاصة بـ: config, flow, llm, prompt, query, storage, agent, collection, diagnosis, library, lookup, nlp_query, objects_query, retrieval, structured_query يتم استيراد تعريفات المخططات واستخدامها على نطاق واسع في جميع المعالجات والخدمات

ملخص

تبعيات Pulsar حسب الفئة

  1. إنشاء العميل: مباشر: gateway/service.py مجرد: async_processor.pypubsub.py (PulsarClient)

  2. نقل الرسائل: المستهلك: consumer.py, consumer_spec.py المنتج: producer.py, producer_spec.py الناشر: publisher.py المشترك: subscriber.py, subscriber_spec.py

  3. نظام المخططات: الأنواع الأساسية: schema/core/primitives.py جميع مخططات الخدمة: schema/services/*.py تسمية الموضوع: schema/core/topic.py

  4. مفاهيم Pulsar الخاصة المطلوبة: رسائل قائمة على الموضوع نظام المخططات (السجل، أنواع الحقول) اشتراكات مشتركة إقرار الرسالة (إيجابي/سلبي) موضع المستهلك (الأول/الأخير) خصائص الرسالة المواضع الأولية وأنواع المستهلك دعم التجزئة مواضيع مستمرة مقابل مواضيع غير مستمرة

تحديات إعادة الهيكلة

الخبر السار: توفر طبقة التجريد (المستهلك، المنتج، الناشر، المشترك) تغليفًا نظيفًا لمعظم تفاعلات Pulsar.

التحديات:

  1. انتشار نظام المخططات: يستخدم كل تعريف للرسالة pulsar.schema.Record وأنواع Pulsar للحقول
  2. قوائم Pulsar الخاصة: InitialPosition, ConsumerType
  3. استثناءات Pulsar: _pulsar.Timeout, _pulsar.Interrupted, _pulsar.InvalidConfiguration, _pulsar.AlreadyClosed
  4. توقيعات الطريقة: acknowledge(), negative_acknowledge(), subscribe(), create_producer()، إلخ.
  5. تنسيق URI للموضوع: هيكل Pulsar kind://tenant/namespace/topic

الخطوات التالية

لجعل البنية التحتية للنشر/الاشتراك قابلة للتكوين، نحتاج إلى:

  1. إنشاء واجهة تجريد لنظام العميل/المخططات
  2. تجريد قوائم Pulsar الخاصة والاستثناءات
  3. إنشاء أغلفة المخططات أو تعريفات مخططات بديلة
  4. تنفيذ الواجهة لكل من Pulsar وأنظمة بديلة (Kafka, RabbitMQ, Redis Streams, إلخ.)
  5. تحديث pubsub.py ليكون قابلاً للتكوين ودعم الخلفيات المتعددة
  6. توفير مسار ترحيل للنشر الحالي

مسودة النهج 1: نمط المحول مع طبقة ترجمة المخططات

الفكرة الرئيسية

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

الاستراتيجية: الحد الأدنى من الاضطرابات مع المحولات

1. الحفاظ على مخططات Pulsar كتمثيل داخلي لا تقم بإعادة كتابة جميع تعريفات المخططات. تبقى المخططات pulsar.schema.Record داخليًا. استخدم المحولات لترجمة البيانات عند الحد بين التعليمات البرمجية الخاصة بنا والخلفية الخاصة بالنشر والاشتراك.

2. إنشاء طبقة تجريدية للنشر والاشتراك:

┌─────────────────────────────────────┐
│   Existing Code (unchanged)         │
│   - Uses Pulsar schemas internally  │
│   - Consumer/Producer/Publisher     │
└──────────────┬──────────────────────┘
               │
┌──────────────┴──────────────────────┐
│   PubSubFactory (configurable)      │
│   - Creates backend-specific client │
└──────────────┬──────────────────────┘
               │
        ┌──────┴──────┐
        │             │
┌───────▼─────┐  ┌────▼─────────┐
│ PulsarAdapter│  │ KafkaAdapter │  etc...
│ (passthrough)│  │ (translates) │
└──────────────┘  └──────────────┘

3. تحديد الواجهات المجردة: PubSubClient - اتصال العميل PubSubProducer - إرسال الرسائل PubSubConsumer - استقبال الرسائل SchemaAdapter - ترجمة مخططات Pulsar إلى/من JSON أو التنسيقات الخاصة بالخلفية

4. تفاصيل التنفيذ:

بالنسبة إلى محول Pulsar: تمرير تقريبي، ترجمة محدودة

بالنسبة إلى الخلفيات الأخرى (Kafka، RabbitMQ، إلخ): تسلسل كائنات سجل Pulsar إلى JSON/بايت مطابقة المفاهيم مثل: InitialPosition.Earliest/Latest → auto.offset.reset في Kafka acknowledge() → commit في Kafka negative_acknowledge() → نمط إعادة الترتيب أو قائمة الانتظار الميتة (DLQ) عناوين الموضوع → أسماء الموضوعات الخاصة بالخلفية

التحليل

المزايا: تغييرات قليلة في التعليمات البرمجية للخدمات الحالية تبقى المخططات كما هي (بدون إعادة كتابة شاملة) مسار ترحيل تدريجي لا يلاحظ مستخدمو Pulsar أي فرق إضافة خلفيات جديدة عبر المحولات

العيوب: ⚠️ لا يزال يعتمد على Pulsar (لتحديد المخططات) ⚠️ بعض عدم التطابق في ترجمة المفاهيم

بديل يجب أخذه في الاعتبار

إنشاء نظام مخططات TrustGraph مستقل عن النشر والاشتراك (باستخدام فئات البيانات أو Pydantic)، ثم إنشاء مخططات Pulsar/Kafka/إلخ من ذلك. يتطلب هذا إعادة كتابة كل ملف مخطط وقد يؤدي إلى تغييرات غير متوقعة.

التوصية للإصدار الأول

ابدأ بـ نهج المحول لأنه:

  1. عملي - يعمل مع التعليمات البرمجية الحالية
  2. يثبت المفهوم بأقل قدر من المخاطر
  3. يمكن أن يتطور إلى نظام مخططات أصلي لاحقًا إذا لزم الأمر
  4. مدفوع بالتكوين: متغير بيئة واحد يقوم بتبديل الخلفيات

النهج المقترح للإصدار الثاني: نظام مخططات مستقل عن الخلفية باستخدام فئات البيانات

المفهوم الأساسي

استخدم فئات البيانات في Python كتنسيق تعريف مخطط محايد. توفر كل خلفية نشر واشتراك الخاصة بها تسلسلًا/إلغاء تسلسل لـ فئات البيانات، مما يلغي الحاجة إلى بقاء مخططات Pulsar في قاعدة التعليمات البرمجية.

تعدد الأشكال للمخططات على مستوى المصنع

بدلاً من ترجمة مخططات Pulsar، توفر كل خلفية معالجتها الخاصة للمخططات والتي تعمل مع فئات البيانات القياسية في Python.

تدفق الناشر

# 1. Get the configured backend from factory
pubsub = get_pubsub()  # Returns PulsarBackend, MQTTBackend, etc.

# 2. Get schema class from the backend
# (Can be imported directly - backend-agnostic)
from trustgraph.schema.services.llm import TextCompletionRequest

# 3. Create a producer/publisher for a specific topic
producer = pubsub.create_producer(
    topic="text-completion-requests",
    schema=TextCompletionRequest  # Tells backend what schema to use
)

# 4. Create message instances (same API regardless of backend)
request = TextCompletionRequest(
    system="You are helpful",
    prompt="Hello world",
    streaming=False
)

# 5. Send the message
producer.send(request)  # Backend serializes appropriately

تدفق المستهلك

# 1. Get the configured backend
pubsub = get_pubsub()

# 2. Create a consumer
consumer = pubsub.subscribe(
    topic="text-completion-requests",
    schema=TextCompletionRequest  # Tells backend how to deserialize
)

# 3. Receive and deserialize
msg = consumer.receive()
request = msg.value()  # Returns TextCompletionRequest dataclass instance

# 4. Use the data (type-safe access)
print(request.system)   # "You are helpful"
print(request.prompt)   # "Hello world"
print(request.streaming)  # False

ما الذي يحدث وراء الكواليس

للخلفية التقنية لـ Pulsar: create_producer() → ينشئ مُنتج Pulsar باستخدام مخطط JSON أو سجل تم إنشاؤه ديناميكيًا. send(request) → يقوم بتسلسل كائن البيانات (dataclass) إلى تنسيق JSON/Pulsar، ويرسله إلى Pulsar. receive() → يستقبل رسالة Pulsar، ويقوم بإلغاء تسلسلها مرة أخرى إلى كائن البيانات.

للخلفية التقنية لـ MQTT: create_producer() → يتصل بخادم MQTT، ولا يلزم تسجيل أي مخطط. send(request) → يحول كائن البيانات إلى JSON، وينشره في موضوع MQTT. receive() → يشترك في موضوع MQTT، ويقوم بإلغاء تسلسل JSON إلى كائن بيانات.

للخلفية التقنية لـ Kafka: create_producer() → ينشئ مُنتج Kafka، ويسجل مخطط Avro إذا لزم الأمر. send(request) → يقوم بتسلسل كائن البيانات إلى تنسيق Avro، ويرسله إلى Kafka. receive() → يستقبل رسالة Kafka، ويقوم بإلغاء تسلسل Avro مرة أخرى إلى كائن بيانات.

النقاط الرئيسية في التصميم

  1. إنشاء كائن المخطط: نسخة كائن البيانات (TextCompletionRequest(...)) متطابقة بغض النظر عن الخلفية التقنية.
  2. الخلفية التقنية تتعامل مع الترميز: كل خلفية تقنية تعرف كيفية تسلسل كائن البيانات الخاص بها إلى التنسيق المستخدم.
  3. تعريف المخطط عند الإنشاء: عند إنشاء المنتج/المستهلك، تحدد نوع المخطط.
  4. الحفاظ على سلامة النوع: تحصل على كائن TextCompletionRequest صحيح، وليس قاموسًا.
  5. لا يوجد تسرب للخلفية التقنية: لا يقوم كود التطبيق باستيراد مكتبات خاصة بالخلفية التقنية.

مثال للتحويل

الحالي (خاص بـ Pulsar):

# schema/services/llm.py
from pulsar.schema import Record, String, Boolean, Integer

class TextCompletionRequest(Record):
    system = String()
    prompt = String()
    streaming = Boolean()

جديد (لا يعتمد على الواجهة الخلفية):

# schema/services/llm.py
from dataclasses import dataclass

@dataclass
class TextCompletionRequest:
    system: str
    prompt: str
    streaming: bool = False

التكامل مع الواجهة الخلفية (Backend Integration)

كل واجهة خلفية تتعامل مع التسلسل/إلغاء التسلسل لفئات البيانات (dataclasses):

واجهة خلفية Pulsar: إنشاء فئات pulsar.schema.Record ديناميكيًا من فئات البيانات. أو تسلسل فئات البيانات إلى JSON واستخدام مخطط JSON الخاص بـ Pulsar. تحافظ على التوافق مع عمليات نشر Pulsar الحالية.

واجهة خلفية MQTT/Redis: تسلسل JSON مباشر لمثيل فئة البيانات. استخدم dataclasses.asdict() / from_dict(). خفيفة الوزن، لا تحتاج إلى سجل مخططات.

واجهة خلفية Kafka: إنشاء مخططات Avro من تعريفات فئات البيانات. استخدم سجل مخططات Confluent. تسلسل آمن من حيث النوع مع دعم تطور المخطط.

البنية (Architecture)

┌─────────────────────────────────────┐
│   Application Code                  │
│   - Uses dataclass schemas          │
│   - Backend-agnostic                │
└──────────────┬──────────────────────┘
               │
┌──────────────┴──────────────────────┐
│   PubSubFactory (configurable)      │
│   - get_pubsub() returns backend    │
└──────────────┬──────────────────────┘
               │
        ┌──────┴──────┐
        │             │
┌───────▼─────────┐  ┌────▼──────────────┐
│ PulsarBackend   │  │ MQTTBackend       │
│ - JSON schema   │  │ - JSON serialize  │
│ - or dynamic    │  │ - Simple queues   │
│   Record gen    │  │                   │
└─────────────────┘  └───────────────────┘

تفاصيل التنفيذ

1. تعريفات المخططات: فئات بيانات بسيطة مع تلميحات الأنواع str، int، bool، float للقيم الأولية list[T] للمصفوفات dict[str, T] للخرائط فئات بيانات متداخلة للأنواع المعقدة

2. يوفر كل خلفية: المُسلسل: dataclass → bytes/wire format المُفكك: bytes/wire format → dataclass تسجيل المخطط (إذا لزم الأمر، مثل Pulsar/Kafka)

3. تجريد المستهلك/المنتج: موجود بالفعل (consumer.py, producer.py) تحديث لاستخدام التسلسل الخاص بالخلفية إزالة استيرادات Pulsar المباشرة

4. تعيينات الأنواع: Pulsar String() → Python str Pulsar Integer() → Python int Pulsar Boolean() → Python bool Pulsar Array(T) → Python list[T] Pulsar Map(K, V) → Python dict[K, V] Pulsar Double() → Python float Pulsar Bytes() → Python bytes

مسار الترحيل

  1. إنشاء إصدارات من فئات البيانات لجميع المخططات في trustgraph/schema/
  2. تحديث الفئات الخلفية (المستهلك، المنتج، الناشر، المشترك) لاستخدام التسلسل المقدم من الخلفية
  3. تنفيذ PulsarBackend باستخدام مخطط JSON أو إنشاء سجل ديناميكي
  4. الاختبار مع Pulsar للتأكد من التوافق مع الإصدارات السابقة للنشر
  5. إضافة خلفيات جديدة (MQTT، Kafka، Redis، إلخ) حسب الحاجة
  6. إزالة استيرادات Pulsar من ملفات المخططات

الفوائد

لا يوجد اعتماد على النشر/الاشتراك في تعريفات المخططات Python قياسي - سهل الفهم، التحقق من النوع، التوثيق أدوات حديثة - تعمل مع mypy، إكمال التعليمات البرمجية لبيئات التطوير المتكاملة، المدققين محسّنة للخلفية - تستخدم كل خلفية التسلسل الأصلي لا توجد تكلفة ترجمة - تسلسل مباشر، بدون محولات أمان النوع - كائنات حقيقية مع أنواع مناسبة التحقق السهل - يمكن استخدام Pydantic إذا لزم الأمر

التحديات والحلول

التحدي: يحتوي Pulsar's Record على تحقق من صحة الحقول في وقت التشغيل الحل: استخدم فئات بيانات Pydantic للتحقق من الصحة إذا لزم الأمر، أو ميزات فئة البيانات Python 3.10+ مع __post_init__

التحدي: بعض ميزات Pulsar الخاصة (مثل نوع Bytes) الحل: قم بتعيين إلى نوع bytes في فئة البيانات، تتعامل الخلفية مع الترميز بشكل مناسب

التحدي: تسمية الموضوع (persistent://tenant/namespace/topic) الحل: قم بتجريد أسماء الموضوع في تعريفات المخططات، تقوم الخلفية بتحويلها إلى التنسيق المناسب

التحدي: تطور المخططات والتحكم في الإصدار الحل: تتعامل كل خلفية مع هذا وفقًا لقدراتها (إصدارات مخطط Pulsar، سجل مخطط Kafka، إلخ)

التحدي: الأنواع المعقدة المتداخلة الحل: استخدم فئات بيانات متداخلة، تقوم الخلفيات بالتسلسل/فك التسلسل بشكل متكرر

قرارات التصميم

  1. فئات بيانات عادية أو Pydantic؟ القرار: استخدم فئات بيانات Python عادية أبسط، بدون تبعيات إضافية التحقق من الصحة ليس مطلوبًا عمليًا أسهل في الفهم والصيانة

  2. تطور المخطط: القرار: لا يلزم آلية للتحكم في الإصدار المخططات مستقرة وطويلة الأمد عادةً ما تضيف التحديثات حقولًا جديدة (متوافقة مع الإصدارات السابقة) تتعامل الخلفيات مع تطور المخطط وفقًا لقدراتها

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

  4. الأنواع المتداخلة والهياكل المعقدة: القرار: استخدم فئات بيانات متداخلة بشكل طبيعي تتعامل فئات بيانات Python مع التداخل بشكل مثالي list[T] للمصفوفات، dict[K, V] للخرائط تقوم الخلفيات بالتسلسل/فك التسلسل بشكل متكرر مثال:

    @dataclass
    class Value:
        value: str
        is_uri: bool
    
    @dataclass
    class Triple:
        s: Value              # Nested dataclass
        p: Value
        o: Value
    
    @dataclass
    class GraphQuery:
        triples: list[Triple]  # Array of nested dataclasses
        metadata: dict[str, str]
    
  5. القيم الافتراضية والحقول الاختيارية: القرار: مزيج من الحقول المطلوبة، والقيم الافتراضية، والحقول الاختيارية الحقول المطلوبة: لا توجد قيمة افتراضية. الحقول التي تحتوي على قيم افتراضية: موجودة دائمًا، ولها قيمة افتراضية منطقية. الحقول الاختيارية حقًا: T | None = None، يتم حذفها من التسلسل عند None. مثال:

    @dataclass
    class TextCompletionRequest:
        system: str              # Required, no default
        prompt: str              # Required, no default
        streaming: bool = False  # Optional with default value
        metadata: dict | None = None  # Truly optional, can be absent
    

    المعاني الأساسية للتسلسل الهام:

    عندما يكون metadata = None:

    {
        "system": "...",
        "prompt": "...",
        "streaming": false
        // metadata field NOT PRESENT
    }
    

    عندما metadata = {} (فارغة بشكل صريح):

    {
        "system": "...",
        "prompt": "...",
        "streaming": false,
        "metadata": {}  // Field PRESENT but empty
    }
    

    الفرق الرئيسي: None → حقل غير موجود في JSON (غير مُسلسل) قيمة فارغة ({}، []، "") → حقل موجود بقيمة فارغة هذا مهم من الناحية الدلالية: "غير مقدم" مقابل "فارغ بشكل صريح" يجب على أنظمة التسلسل الخلفية تخطي حقول None، وليس ترميزها كـ null

مسودة الحل الثالث: تفاصيل التنفيذ

تنسيق اسم قائمة انتظار عام

استبدل أسماء قوائم الانتظار الخاصة بكل نظام خلفي بتنسيق عام يمكن للأنظمة الخلفية تعيينه بشكل مناسب.

التنسيق: {qos}/{tenant}/{namespace}/{queue-name}

حيث: qos: مستوى جودة الخدمة q0 = بذل جهد (إرسال وإهمال، بدون إقرار) q1 = مرة واحدة على الأقل (يتطلب إقرارًا) q2 = مرة واحدة بالضبط (إقرار من مرحلتين) tenant: تجميع منطقي للتعددية namespace: تجميع فرعي داخل المستأجر queue-name: اسم قائمة الانتظار/الموضوع الفعلي

أمثلة:

q1/tg/flow/text-completion-requests
q2/tg/config/config-push
q0/tg/metrics/stats

ربط الموضوعات في الواجهة الخلفية

تقوم كل واجهة خلفية بتحويل التنسيق العام إلى تنسيقها الأصلي:

الواجهة الخلفية Pulsar:

def map_topic(self, generic_topic: str) -> str:
    # Parse: q1/tg/flow/text-completion-requests
    qos, tenant, namespace, queue = generic_topic.split('/', 3)

    # Map QoS to persistence
    persistence = 'persistent' if qos in ['q1', 'q2'] else 'non-persistent'

    # Return Pulsar URI: persistent://tg/flow/text-completion-requests
    return f"{persistence}://{tenant}/{namespace}/{queue}"

الخلفية التقنية لـ MQTT:

def map_topic(self, generic_topic: str) -> tuple[str, int]:
    # Parse: q1/tg/flow/text-completion-requests
    qos, tenant, namespace, queue = generic_topic.split('/', 3)

    # Map QoS level
    qos_level = {'q0': 0, 'q1': 1, 'q2': 2}[qos]

    # Build MQTT topic including tenant/namespace for proper namespacing
    mqtt_topic = f"{tenant}/{namespace}/{queue}"

    return mqtt_topic, qos_level

الدالة المساعدة للموضوع المحدّثة

# schema/core/topic.py
def topic(queue_name, qos='q1', tenant='tg', namespace='flow'):
    """
    Create a generic topic identifier that can be mapped by backends.

    Args:
        queue_name: The queue/topic name
        qos: Quality of service
             - 'q0' = best-effort (no ack)
             - 'q1' = at-least-once (ack required)
             - 'q2' = exactly-once (two-phase ack)
        tenant: Tenant identifier for multi-tenancy
        namespace: Namespace within tenant

    Returns:
        Generic topic string: qos/tenant/namespace/queue_name

    Examples:
        topic('my-queue')  # q1/tg/flow/my-queue
        topic('config', qos='q2', namespace='config')  # q2/tg/config/config
    """
    return f"{qos}/{tenant}/{namespace}/{queue_name}"

التكوين والتهيئة

وسائط سطر الأوامر + متغيرات البيئة:

# In base/async_processor.py - add_args() method
@staticmethod
def add_args(parser):
    # Pub/sub backend selection
    parser.add_argument(
        '--pubsub-backend',
        default=os.getenv('PUBSUB_BACKEND', 'pulsar'),
        choices=['pulsar', 'mqtt'],
        help='Pub/sub backend (default: pulsar, env: PUBSUB_BACKEND)'
    )

    # Pulsar-specific configuration
    parser.add_argument(
        '--pulsar-host',
        default=os.getenv('PULSAR_HOST', 'pulsar://localhost:6650'),
        help='Pulsar host (default: pulsar://localhost:6650, env: PULSAR_HOST)'
    )

    parser.add_argument(
        '--pulsar-api-key',
        default=os.getenv('PULSAR_API_KEY', None),
        help='Pulsar API key (env: PULSAR_API_KEY)'
    )

    parser.add_argument(
        '--pulsar-listener',
        default=os.getenv('PULSAR_LISTENER', None),
        help='Pulsar listener name (env: PULSAR_LISTENER)'
    )

    # MQTT-specific configuration
    parser.add_argument(
        '--mqtt-host',
        default=os.getenv('MQTT_HOST', 'localhost'),
        help='MQTT broker host (default: localhost, env: MQTT_HOST)'
    )

    parser.add_argument(
        '--mqtt-port',
        type=int,
        default=int(os.getenv('MQTT_PORT', '1883')),
        help='MQTT broker port (default: 1883, env: MQTT_PORT)'
    )

    parser.add_argument(
        '--mqtt-username',
        default=os.getenv('MQTT_USERNAME', None),
        help='MQTT username (env: MQTT_USERNAME)'
    )

    parser.add_argument(
        '--mqtt-password',
        default=os.getenv('MQTT_PASSWORD', None),
        help='MQTT password (env: MQTT_PASSWORD)'
    )

دالة المصنع:

# In base/pubsub.py or base/pubsub_factory.py
def get_pubsub(**config) -> PubSubBackend:
    """
    Create and return a pub/sub backend based on configuration.

    Args:
        config: Configuration dict from command-line args
                Must include 'pubsub_backend' key

    Returns:
        Backend instance (PulsarBackend, MQTTBackend, etc.)
    """
    backend_type = config.get('pubsub_backend', 'pulsar')

    if backend_type == 'pulsar':
        return PulsarBackend(
            host=config.get('pulsar_host'),
            api_key=config.get('pulsar_api_key'),
            listener=config.get('pulsar_listener'),
        )
    elif backend_type == 'mqtt':
        return MQTTBackend(
            host=config.get('mqtt_host'),
            port=config.get('mqtt_port'),
            username=config.get('mqtt_username'),
            password=config.get('mqtt_password'),
        )
    else:
        raise ValueError(f"Unknown pub/sub backend: {backend_type}")

الاستخدام في AsyncProcessor:

# In async_processor.py
class AsyncProcessor:
    def __init__(self, **params):
        self.id = params.get("id")

        # Create backend from config (replaces PulsarClient)
        self.pubsub = get_pubsub(**params)

        # Rest of initialization...

الواجهة الخلفية

class PubSubBackend(Protocol):
    """Protocol defining the interface all pub/sub backends must implement."""

    def create_producer(self, topic: str, schema: type, **options) -> BackendProducer:
        """
        Create a producer for a topic.

        Args:
            topic: Generic topic format (qos/tenant/namespace/queue)
            schema: Dataclass type for messages
            options: Backend-specific options (e.g., chunking_enabled)

        Returns:
            Backend-specific producer instance
        """
        ...

    def create_consumer(
        self,
        topic: str,
        subscription: str,
        schema: type,
        initial_position: str = 'latest',
        consumer_type: str = 'shared',
        **options
    ) -> BackendConsumer:
        """
        Create a consumer for a topic.

        Args:
            topic: Generic topic format (qos/tenant/namespace/queue)
            subscription: Subscription/consumer group name
            schema: Dataclass type for messages
            initial_position: 'earliest' or 'latest' (MQTT may ignore)
            consumer_type: 'shared', 'exclusive', 'failover' (MQTT may ignore)
            options: Backend-specific options

        Returns:
            Backend-specific consumer instance
        """
        ...

    def close(self) -> None:
        """Close the backend connection."""
        ...
class BackendProducer(Protocol):
    """Protocol for backend-specific producer."""

    def send(self, message: Any, properties: dict = {}) -> None:
        """Send a message (dataclass instance) with optional properties."""
        ...

    def flush(self) -> None:
        """Flush any buffered messages."""
        ...

    def close(self) -> None:
        """Close the producer."""
        ...
class BackendConsumer(Protocol):
    """Protocol for backend-specific consumer."""

    def receive(self, timeout_millis: int = 2000) -> Message:
        """
        Receive a message from the topic.

        Raises:
            TimeoutError: If no message received within timeout
        """
        ...

    def acknowledge(self, message: Message) -> None:
        """Acknowledge successful processing of a message."""
        ...

    def negative_acknowledge(self, message: Message) -> None:
        """Negative acknowledge - triggers redelivery."""
        ...

    def unsubscribe(self) -> None:
        """Unsubscribe from the topic."""
        ...

    def close(self) -> None:
        """Close the consumer."""
        ...
class Message(Protocol):
    """Protocol for a received message."""

    def value(self) -> Any:
        """Get the deserialized message (dataclass instance)."""
        ...

    def properties(self) -> dict:
        """Get message properties/metadata."""
        ...

إعادة هيكلة الفئات الحالية

تظل الفئات الحالية Consumer، Producer، Publisher، Subscriber في الغالب دون تغيير:

المسؤوليات الحالية (يجب الحفاظ عليها): نموذج الخيوط غير المتزامنة ومجموعات المهام. منطق إعادة الاتصال ومعالجة إعادة المحاولة. جمع المقاييس. تحديد المعدل. إدارة التزامن.

التغييرات المطلوبة: إزالة استيرادات Pulsar المباشرة (pulsar.schema، pulsar.InitialPosition، إلخ). قبول BackendProducer/BackendConsumer بدلاً من عميل Pulsar. تفويض عمليات النشر/الاشتراك الفعلية إلى مثيلات الواجهة الخلفية. مطابقة المفاهيم العامة إلى استدعاءات الواجهة الخلفية.

مثال على إعادة الهيكلة:

# OLD - consumer.py
class Consumer:
    def __init__(self, client, topic, subscriber, schema, ...):
        self.client = client  # Direct Pulsar client
        # ...

    async def consumer_run(self):
        # Uses pulsar.InitialPosition, pulsar.ConsumerType
        self.consumer = self.client.subscribe(
            topic=self.topic,
            schema=JsonSchema(self.schema),
            initial_position=pulsar.InitialPosition.Earliest,
            consumer_type=pulsar.ConsumerType.Shared,
        )

# NEW - consumer.py
class Consumer:
    def __init__(self, backend_consumer, schema, ...):
        self.backend_consumer = backend_consumer  # Backend-specific consumer
        self.schema = schema
        # ...

    async def consumer_run(self):
        # Backend consumer already created with right settings
        # Just use it directly
        while self.running:
            msg = await asyncio.to_thread(
                self.backend_consumer.receive,
                timeout_millis=2000
            )
            await self.handle_message(msg)

السلوكيات الخاصة بالخادم الخلفي

الخادم الخلفي Pulsar: يربط q0non-persistent://، و q1/q2persistent:// يدعم جميع أنواع المستهلكين (مشترك، حصري، احتياطي) يدعم الموضع الأولي (الأول/الأخير) إقرار الرسائل الأصلي دعم سجل المخططات

الخادم الخلفي MQTT: يربط q0/q1/q2 بمستويات جودة الخدمة MQTT 0/1/2 يتضمن المستأجر/المساحة الاسمية في مسار الموضوع لعملية التمييز يقوم بإنشاء معرفات العملاء تلقائيًا من أسماء الاشتراكات يتجاهل الموضع الأولي (لا يوجد سجل رسائل في MQTT الأساسي) يتجاهل نوع المستهلك (يستخدم MQTT معرفات العملاء، وليس مجموعات المستهلكين) نموذج نشر/اشتراك بسيط

ملخص قرارات التصميم

  1. تسمية قائمة انتظار عامة: بتنسيق qos/tenant/namespace/queue-name
  2. جودة الخدمة في معرف قائمة الانتظار: يتم تحديدها بواسطة تعريف قائمة الانتظار، وليس التكوين
  3. إعادة الاتصال: تتم معالجتها بواسطة فئات المستهلك/المنتج، وليس الخوادم الخلفية
  4. مواضيع MQTT: تتضمن المستأجر/المساحة الاسمية لعملية التمييز المناسبة
  5. سجل الرسائل: يتجاهل MQTT معلمة initial_position (تحسين مستقبلي)
  6. معرفات العملاء: يقوم خادم MQTT بإنشائها تلقائيًا من اسم الاشتراك

التحسينات المستقبلية

سجل رسائل MQTT: يمكن إضافة طبقة تخزين اختيارية (مثل الرسائل المحفوظة، أو مخزن خارجي) سيسمح بدعم initial_position='earliest' ليس مطلوبًا للتنفيذ الأولي