mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-04-25 16:36:21 +02:00
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.
965 lines
35 KiB
Markdown
965 lines
35 KiB
Markdown
---
|
||
layout: default
|
||
title: "Pub/Sub Altyapısı"
|
||
parent: "Turkish (Beta)"
|
||
---
|
||
|
||
# Pub/Sub Altyapısı
|
||
|
||
> **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.
|
||
|
||
## Genel Bakış
|
||
|
||
Bu belge, TrustGraph kod tabanı ile pub/sub altyapısı arasındaki tüm bağlantıları listeler. Şu anda sistem, Apache Pulsar'ı kullanmak üzere sabit kodlanmıştır. Bu analiz, yapılandırılabilir bir pub/sub soyutlamasına yönelik gelecekteki yeniden düzenlemeleri bilgilendirmek için tüm entegrasyon noktalarını belirler.
|
||
|
||
## Mevcut Durum: Pulsar Entegrasyon Noktaları
|
||
|
||
### 1. Doğrudan Pulsar İstemci Kullanımı
|
||
|
||
**Konum:** `trustgraph-flow/trustgraph/gateway/service.py`
|
||
|
||
API ağ geçidi, doğrudan Pulsar istemcisini içe aktarır ve örnekler:
|
||
|
||
**Satır 20:** `import pulsar`
|
||
**Satırlar 54-61:** `pulsar.Client()`'ın doğrudan örneklenmesi, isteğe bağlı `pulsar.AuthenticationToken()` ile birlikte
|
||
**Satırlar 33-35:** Ortam değişkenlerinden varsayılan Pulsar ana bilgisayarı yapılandırması
|
||
**Satırlar 178-192:** `--pulsar-host`, `--pulsar-api-key` ve `--pulsar-listener` için CLI argümanları
|
||
**Satırlar 78, 124:** `pulsar_client`'ı `ConfigReceiver` ve `DispatcherManager`'ye geçirir
|
||
|
||
Bu, bir soyutlama katmanı dışında bir Pulsar istemcisinin doğrudan örneklenmesini yapan tek konumdur.
|
||
|
||
### 2. Temel İşlemci Çerçevesi
|
||
|
||
**Konum:** `trustgraph-base/trustgraph/base/async_processor.py`
|
||
|
||
Tüm işlemciler için temel sınıf, Pulsar bağlantısı sağlar:
|
||
|
||
**Satır 9:** `import _pulsar` (istisna işleme için)
|
||
**Satır 18:** `from . pubsub import PulsarClient`
|
||
**Satır 38:** `pulsar_client_object = PulsarClient(**params)` oluşturur
|
||
**Satırlar 104-108:** `pulsar_host` ve `pulsar_client`'i ortaya koyan özellikler
|
||
**Satır 250:** Statik yöntem `add_args()`, CLI argümanları için `PulsarClient.add_args(parser)`'i çağırır
|
||
**Satırlar 223-225:** `_pulsar.Interrupted` için istisna işleme
|
||
|
||
Tüm işlemciler `AsyncProcessor`'dan türetildiği için, bu merkezi bir entegrasyon noktasıdır.
|
||
|
||
### 3. Tüketici Soyutlaması
|
||
|
||
**Konum:** `trustgraph-base/trustgraph/base/consumer.py`
|
||
|
||
Kuyruklardan mesajlar tüketir ve işleyici fonksiyonlarını çağırır:
|
||
|
||
**Pulsar içe aktarmaları:**
|
||
**Satır 12:** `from pulsar.schema import JsonSchema`
|
||
**Satır 13:** `import pulsar`
|
||
**Satır 14:** `import _pulsar`
|
||
|
||
**Pulsar'a özgü kullanım:**
|
||
**Satırlar 100, 102:** `pulsar.InitialPosition.Earliest` / `pulsar.InitialPosition.Latest`
|
||
**Satır 108:** `JsonSchema(self.schema)` sarmalayıcısı
|
||
**Satır 110:** `pulsar.ConsumerType.Shared`
|
||
**Satırlar 104-111:** Pulsar'a özgü parametrelerle `self.client.subscribe()`
|
||
**Satırlar 143, 150, 65:** `consumer.unsubscribe()` ve `consumer.close()` yöntemleri
|
||
**Satır 162:** `_pulsar.Timeout` istisnası
|
||
**Satırlar 182, 205, 232:** `consumer.acknowledge()` / `consumer.negative_acknowledge()`
|
||
|
||
**Belge dosyası:** `trustgraph-base/trustgraph/base/consumer_spec.py`
|
||
**Satır 22:** `processor.pulsar_client`'a referans
|
||
|
||
### 4. Yayıncı Soyutlaması
|
||
|
||
**Konum:** `trustgraph-base/trustgraph/base/producer.py`
|
||
|
||
Kuyruklara mesaj gönderir:
|
||
|
||
**Pulsar içe aktarmaları:**
|
||
**Satır 2:** `from pulsar.schema import JsonSchema`
|
||
|
||
**Pulsar'a özgü kullanım:**
|
||
**Satır 49:** `JsonSchema(self.schema)` sarmalayıcısı
|
||
**Satırlar 47-51:** Pulsar'a özgü parametrelerle (konu, şema, chunking_enabled) `self.client.create_producer()`
|
||
**Satırlar 31, 76:** `producer.close()` yöntemi
|
||
**Satırlar 64-65:** Mesaj ve özelliklerle `producer.send()`
|
||
|
||
**Belge dosyası:** `trustgraph-base/trustgraph/base/producer_spec.py`
|
||
**Satır 18:** `processor.pulsar_client`'a referans
|
||
|
||
### 5. Yayıncı Soyutlaması
|
||
|
||
**Konum:** `trustgraph-base/trustgraph/base/publisher.py`
|
||
|
||
Kuyruklu tamponlama ile asenkron mesaj yayınlama:
|
||
|
||
**Pulsar içe aktarmaları:**
|
||
**Satır 2:** `from pulsar.schema import JsonSchema`
|
||
**Satır 6:** `import pulsar`
|
||
|
||
**Pulsar'a özgü kullanım:**
|
||
**Satır 52:** `JsonSchema(self.schema)` sarmalayıcısı
|
||
**Satırlar 50-54:** Pulsar'a özgü parametrelerle `self.client.create_producer()`
|
||
**Satırlar 101, 103:** Mesaj ve isteğe bağlı özelliklerle `producer.send()`
|
||
**Satırlar 106-107:** `producer.flush()` ve `producer.close()` yöntemleri
|
||
|
||
### 6. Abonelik Soyutlaması
|
||
|
||
**Konum:** `trustgraph-base/trustgraph/base/subscriber.py`
|
||
|
||
Kuyruklardan çoklu alıcıya mesaj dağıtımı sağlar:
|
||
|
||
**Pulsar importları:**
|
||
**6. Satır:** `from pulsar.schema import JsonSchema`
|
||
**8. Satır:** `import _pulsar`
|
||
|
||
**Pulsar'a özgü kullanım:**
|
||
**55. Satır:** `JsonSchema(self.schema)` wrapper
|
||
**57. Satır:** `self.client.subscribe(**subscribe_args)`
|
||
**101, 136, 160, 167-172. Satırlar:** Pulsar istisnaları: `_pulsar.Timeout`, `_pulsar.InvalidConfiguration`, `_pulsar.AlreadyClosed`
|
||
**159, 166, 170. Satırlar:** Tüketici metotları: `negative_acknowledge()`, `unsubscribe()`, `close()`
|
||
**247, 251. Satırlar:** Mesaj onayı: `acknowledge()`, `negative_acknowledge()`
|
||
|
||
**Spec dosyası:** `trustgraph-base/trustgraph/base/subscriber_spec.py`
|
||
**19. Satır:** `processor.pulsar_client`'a referans
|
||
|
||
### 7. Şema Sistemi (Heart of Darkness)
|
||
|
||
**Konum:** `trustgraph-base/trustgraph/schema/`
|
||
|
||
Sistemdeki her mesaj şeması, Pulsar'ın şema çerçevesi kullanılarak tanımlanır.
|
||
|
||
**Temel öğeler:** `schema/core/primitives.py`
|
||
**2. Satır:** `from pulsar.schema import Record, String, Boolean, Array, Integer`
|
||
Tüm şemalar, Pulsar'ın `Record` temel sınıfından türetilir.
|
||
Tüm alan türleri Pulsar türleridir: `String()`, `Integer()`, `Boolean()`, `Array()`, `Map()`, `Double()`
|
||
|
||
**Örnek şemalar:**
|
||
`schema/services/llm.py` (2. Satır): `from pulsar.schema import Record, String, Array, Double, Integer, Boolean`
|
||
`schema/services/config.py` (2. Satır): `from pulsar.schema import Record, Bytes, String, Boolean, Array, Map, Integer`
|
||
|
||
**Konu adlandırması:** `schema/core/topic.py`
|
||
**2-3. Satırlar:** Konu formatı: `{kind}://{tenant}/{namespace}/{topic}`
|
||
Bu URI yapısı Pulsar'a özgüdür (örneğin, `persistent://tg/flow/config`)
|
||
|
||
**Etki:**
|
||
Kod tabanındaki tüm istek/yanıt mesaj tanımları, Pulsar şemalarını kullanır.
|
||
Bu, aşağıdaki hizmetler için geçerlidir: yapılandırma, akış, LLM, istem, sorgu, depolama, aracı, koleksiyon, tanı, kütüphane, arama, NLP sorgusu, nesne sorgusu, alma, yapılandırılmış sorgu.
|
||
Şema tanımları, tüm işlemciler ve hizmetler genelinde kapsamlı bir şekilde içe aktarılır ve kullanılır.
|
||
|
||
## Özet
|
||
|
||
### Kategoriye Göre Pulsar Bağımlılıkları
|
||
|
||
1. **İstem oluşturma:**
|
||
Doğrudan: `gateway/service.py`
|
||
Soyutlanmış: `async_processor.py` → `pubsub.py` (PulsarClient)
|
||
|
||
2. **Mesaj taşıma:**
|
||
Tüketici: `consumer.py`, `consumer_spec.py`
|
||
Üretici: `producer.py`, `producer_spec.py`
|
||
Yayıncı: `publisher.py`
|
||
Abonelik: `subscriber.py`, `subscriber_spec.py`
|
||
|
||
3. **Şema sistemi:**
|
||
Temel türler: `schema/core/primitives.py`
|
||
Tüm hizmet şemaları: `schema/services/*.py`
|
||
Konu adlandırması: `schema/core/topic.py`
|
||
|
||
4. **Gereken Pulsar'a özgü kavramlar:**
|
||
Konuya dayalı mesajlaşma
|
||
Şema sistemi (Kayıt, alan türleri)
|
||
Paylaşımlı abonelikler
|
||
Mesaj onayı (olumlu/olumsuz)
|
||
Tüketici konumlandırması (en erken/en son)
|
||
Mesaj özellikleri
|
||
Başlangıç konumları ve tüketici türleri
|
||
Parçalama desteği
|
||
Kalıcı ve kalıcı olmayan konular
|
||
|
||
### Yeniden Düzenleme Zorlukları
|
||
|
||
İyi haber: Soyutlama katmanı (Tüketici, Üretici, Yayıncı, Abonelik), çoğu Pulsar etkileşimini temiz bir şekilde kapsar.
|
||
|
||
Zorluklar:
|
||
1. **Şema sisteminin yaygınlığı:** Her mesaj tanımı `pulsar.schema.Record` ve Pulsar alan türlerini kullanır.
|
||
2. **Pulsar'a özgü numaralandırmalar:** `InitialPosition`, `ConsumerType`
|
||
3. **Pulsar istisnaları:** `_pulsar.Timeout`, `_pulsar.Interrupted`, `_pulsar.InvalidConfiguration`, `_pulsar.AlreadyClosed`
|
||
4. **Metot imzaları:** `acknowledge()`, `negative_acknowledge()`, `subscribe()`, `create_producer()`, vb.
|
||
5. **Konu URI formatı:** Pulsar'ın `kind://tenant/namespace/topic` yapısı
|
||
|
||
### Sonraki Adımlar
|
||
|
||
Yayın/abone altyapısını yapılandırılabilir hale getirmek için şunları yapmamız gerekiyor:
|
||
|
||
1. İstem/şema sistemi için bir soyutlama arayüzü oluşturun.
|
||
2. Pulsar'a özgü numaralandırmaları ve istisnaları soyutlayın.
|
||
3. Şema sargıları veya alternatif şema tanımları oluşturun.
|
||
4. Arayüzü hem Pulsar hem de alternatif sistemler (Kafka, RabbitMQ, Redis Streams, vb.) için uygulayın.
|
||
5. `pubsub.py`'ı yapılandırılabilir hale getirin ve çoklu arka uçları destekleyin.
|
||
6. Mevcut dağıtımlar için bir geçiş yolu sağlayın.
|
||
|
||
## Yaklaşım Taslağı 1: Şema Çeviri Katmanıyla Adaptör Kalıbı
|
||
|
||
### Temel Bilgi
|
||
**Şema sistemi**, en derin entegrasyon noktasıdır - her şey ondan kaynaklanır. Önce bunu çözmemiz gerekiyor, aksi takdirde tüm kodu yeniden yazmamız gerekecek.
|
||
|
||
### Strateji: Minimum Bozulma ile Adaptörler
|
||
|
||
**1. Pulsar şemalarını, dahili gösterim olarak koruyun**
|
||
Tüm şema tanımlarını yeniden yazmayın
|
||
Şemalar, dahili olarak `pulsar.schema.Record` olarak kalır
|
||
Adaptörleri, kendi kodumuz ile yayın/abonelik arka ucu arasındaki sınırdaki çevirileri yapmak için kullanın
|
||
|
||
**2. Bir yayın/abonelik soyutlama katmanı oluşturun:**
|
||
|
||
```
|
||
┌─────────────────────────────────────┐
|
||
│ Existing Code (unchanged) │
|
||
│ - Uses Pulsar schemas internally │
|
||
│ - Consumer/Producer/Publisher │
|
||
└──────────────┬──────────────────────┘
|
||
│
|
||
┌──────────────┴──────────────────────┐
|
||
│ PubSubFactory (configurable) │
|
||
│ - Creates backend-specific client │
|
||
└──────────────┬──────────────────────┘
|
||
│
|
||
┌──────┴──────┐
|
||
│ │
|
||
┌───────▼─────┐ ┌────▼─────────┐
|
||
│ PulsarAdapter│ │ KafkaAdapter │ etc...
|
||
│ (passthrough)│ │ (translates) │
|
||
└──────────────┘ └──────────────┘
|
||
```
|
||
|
||
**3. Soyut arayüzleri tanımlayın:**
|
||
`PubSubClient` - istemci bağlantısı
|
||
`PubSubProducer` - mesaj gönderme
|
||
`PubSubConsumer` - mesaj alma
|
||
`SchemaAdapter` - Pulsar şemalarını JSON'a veya arka uç özel formatlarına dönüştürme/dönüştürme
|
||
|
||
**4. Uygulama detayları:**
|
||
|
||
**Pulsar adaptörü için:** Neredeyse doğrudan geçiş, minimum çeviri
|
||
|
||
**Diğer arka uçlar için** (Kafka, RabbitMQ, vb.):
|
||
Pulsar Kayıt nesnelerini JSON/byte'a serileştirin
|
||
Aşağıdaki kavramları eşleyin:
|
||
`InitialPosition.Earliest/Latest` → Kafka'nın auto.offset.reset'i
|
||
`acknowledge()` → Kafka'nın commit'i
|
||
`negative_acknowledge()` → Yeniden kuyruklama veya DLQ deseni
|
||
Konu URI'leri → Arka uç özel konu adları
|
||
|
||
### Analiz
|
||
|
||
**Artıları:**
|
||
✅ Mevcut hizmetlerde minimum kod değişikliği
|
||
✅ Şemalar olduğu gibi kalır (büyük bir yeniden yazım olmaz)
|
||
✅ Aşamalı geçiş yolu
|
||
✅ Pulsar kullanıcıları hiçbir fark görmez
|
||
✅ Yeni arka uçlar adaptörler aracılığıyla eklenir
|
||
|
||
**Eksileri:**
|
||
⚠️ Hala Pulsar bağımlılığı taşır (şema tanımları için)
|
||
⚠️ Kavramları çevirirken bazı uyumsuzluklar olabilir
|
||
|
||
### Alternatif Düşünce
|
||
|
||
Bir **TrustGraph şema sistemi** oluşturun; bu sistem, pub/sub'dan bağımsızdır (dataclass'lar veya Pydantic kullanarak). Ardından, bu sistemden Pulsar/Kafka/vb. şemalarını oluşturun. Bu, her şema dosyasının yeniden yazılmasını gerektirir ve potansiyel olarak uyumsuzluklara neden olabilir.
|
||
|
||
### Taslak 1 için Öneri
|
||
|
||
**Adaptör yaklaşımıyla** başlayın çünkü:
|
||
1. Pratik bir yaklaşımdır - mevcut kodla çalışır
|
||
2. Minimum riskle kavramı kanıtlar
|
||
3. Gerekirse daha sonra yerel bir şema sistemine dönüştürülebilir
|
||
4. Yapılandırma odaklıdır: tek bir ortam değişkeni arka uçları değiştirir
|
||
|
||
## Yaklaşım Taslağı 2: Dataclass'larla Arka Uçtan Bağımsız Şema Sistemi
|
||
|
||
### Temel Kavram
|
||
|
||
Python **dataclass'ları** tarafsız şema tanımı formatı olarak kullanın. Her pub/sub arka ucu, dataclass'lar için kendi serileştirme/deserileştirme işlemlerini sağlar; bu, Pulsar şemalarının kod tabanında kalma ihtiyacını ortadan kaldırır.
|
||
|
||
### Fabrika Seviyesinde Şema Çok Biçimliliği
|
||
|
||
Pulsar şemalarını çevirmek yerine, **her arka uç, standart Python dataclass'larıyla çalışan kendi şema işleme yöntemini sağlar**.
|
||
|
||
### Yayıncı Akışı
|
||
|
||
```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
|
||
```
|
||
|
||
### Tüketici Akışı
|
||
|
||
```python
|
||
# 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
|
||
```
|
||
|
||
### Sahne Arkasındaki Olaylar
|
||
|
||
**Pulsar arka ucu için:**
|
||
`create_producer()` → JSON şeması veya dinamik olarak oluşturulmuş bir kayıtla Pulsar üreticisi oluşturur.
|
||
`send(request)` → veri sınıfını JSON/Pulsar formatına serileştirir ve Pulsar'a gönderir.
|
||
`receive()` → Pulsar mesajını alır, veri sınıfına geri serileştirir.
|
||
|
||
**MQTT arka ucu için:**
|
||
`create_producer()` → MQTT aracısına bağlanır, şema kaydı gerektirmez.
|
||
`send(request)` → veri sınıfını JSON'a dönüştürür ve MQTT konusuna yayınlar.
|
||
`receive()` → MQTT konusuna abone olur, JSON'ı veri sınıfına geri serileştirir.
|
||
|
||
**Kafka arka ucu için:**
|
||
`create_producer()` → Kafka üreticisini oluşturur, gerekirse Avro şemasını kaydeder.
|
||
`send(request)` → veri sınıfını Avro formatına serileştirir ve Kafka'ya gönderir.
|
||
`receive()` → Kafka mesajını alır, Avro'yu veri sınıfına geri serileştirir.
|
||
|
||
### Temel Tasarım Noktaları
|
||
|
||
1. **Şema nesnesi oluşturma**: Veri sınıfı örneği (`TextCompletionRequest(...)`), arka uçtan bağımsız olarak aynıdır.
|
||
2. **Arka uç, kodlamayı yönetir**: Her arka uç, veri sınıfını kablo formatına nasıl serileştireceğini bilir.
|
||
3. **Oluşturma sırasında şema tanımı**: Üretici/tüketici oluştururken, şema türünü belirtirsiniz.
|
||
4. **Tür güvenliği korunur**: Doğru bir `TextCompletionRequest` nesnesi alırsınız, bir sözlük değil.
|
||
5. **Arka uç sızıntısı yok**: Uygulama kodu, arka uçla ilgili özel kütüphaneleri asla içe aktarmaz.
|
||
|
||
### Örnek Dönüşüm
|
||
|
||
**Mevcut (Pulsar'a özel):**
|
||
```python
|
||
# schema/services/llm.py
|
||
from pulsar.schema import Record, String, Boolean, Integer
|
||
|
||
class TextCompletionRequest(Record):
|
||
system = String()
|
||
prompt = String()
|
||
streaming = Boolean()
|
||
```
|
||
|
||
**Yeni (Arka uçtan bağımsız):**
|
||
```python
|
||
# schema/services/llm.py
|
||
from dataclasses import dataclass
|
||
|
||
@dataclass
|
||
class TextCompletionRequest:
|
||
system: str
|
||
prompt: str
|
||
streaming: bool = False
|
||
```
|
||
|
||
### Arka Uç Entegrasyonu
|
||
|
||
Her arka uç, veri sınıflarının serileştirme/deserileştirme işlemlerini gerçekleştirir:
|
||
|
||
**Pulsar arka ucu:**
|
||
Veri sınıflarından dinamik olarak `pulsar.schema.Record` sınıfları oluşturun
|
||
Veya veri sınıflarını JSON'a serileştirin ve Pulsar'ın JSON şemasını kullanın
|
||
Mevcut Pulsar kurulumlarıyla uyumluluğu korur
|
||
|
||
**MQTT/Redis arka ucu:**
|
||
Veri sınıfı örneklerinin doğrudan JSON serileştirilmesi
|
||
`dataclasses.asdict()` / `from_dict()` kullanın
|
||
Hafif, şema kayıt defterine gerek yok
|
||
|
||
**Kafka arka ucu:**
|
||
Veri sınıfı tanımlarından Avro şemaları oluşturun
|
||
Confluent'ın şema kayıt defterini kullanın
|
||
Şema evrimi desteğiyle tür güvenli serileştirme
|
||
|
||
### Mimari
|
||
|
||
```
|
||
┌─────────────────────────────────────┐
|
||
│ 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 │ │ │
|
||
└─────────────────┘ └───────────────────┘
|
||
```
|
||
|
||
### Uygulama Detayları
|
||
|
||
**1. Şema tanımları:** Basit dataclass'lar ve tür ipuçları
|
||
`str`, `int`, `bool`, `float` temel veri tipleri için
|
||
`list[T]` diziler için
|
||
`dict[str, T]` haritalar için
|
||
Karmaşık tipler için iç içe dataclass'lar
|
||
|
||
**2. Her arka uç şunları sağlar:**
|
||
Serileştirici: `dataclass → bytes/wire format`
|
||
Seri dışa aktarıcı: `bytes/wire format → dataclass`
|
||
Şema kaydı (gerekirse, örneğin Pulsar/Kafka)
|
||
|
||
**3. Tüketici/Üretici soyutlaması:**
|
||
Zaten mevcut (consumer.py, producer.py)
|
||
Arka uç tarafından sağlanan seri dışa aktarmayı kullanmak için güncellenmeli
|
||
Doğrudan Pulsar içe aktarmalarını kaldırmalı
|
||
|
||
**4. Tür eşlemeleri:**
|
||
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`
|
||
|
||
### Geçiş Yolu
|
||
|
||
1. `trustgraph/schema/` içindeki tüm şemaların **dataclass versiyonlarını oluşturun**
|
||
2. Arka uç tarafından sağlanan seri dışa aktarmayı kullanmak için **arka uç sınıflarını (Tüketici, Üretici, Yayıncı, Abonelik) güncelleyin**
|
||
3. JSON şemasını veya dinamik Kayıt oluşturmayı kullanarak **PulsarBackend'i uygulayın**
|
||
4. **Mevcut dağıtımlarla geriye dönük uyumluluğu sağlamak için Pulsar ile test edin**
|
||
5. Gerekirse **yeni arka uçlar ekleyin (MQTT, Kafka, Redis, vb.)**
|
||
6. Şema dosyalarından **Pulsar içe aktarmalarını kaldırın**
|
||
|
||
### Avantajlar
|
||
|
||
✅ **Şema tanımlarında herhangi bir yayın/abonelik bağımlılığı yok**
|
||
✅ **Standart Python** - anlaşılması, tür denetimi yapılması ve belgelenmesi kolay
|
||
✅ **Modern araçlar** - mypy, IDE otomatik tamamlama, lint araçlarıyla çalışır
|
||
✅ **Arka uç odaklı** - her arka uç yerel seri dışa aktarmayı kullanır
|
||
✅ **Çeviri ek yükü yok** - doğrudan seri dışa aktarma, adaptörler yok
|
||
✅ **Tür güvenliği** - uygun türlere sahip gerçek nesneler
|
||
✅ **Kolay doğrulama** - gerekirse Pydantic kullanılabilir
|
||
|
||
### Zorluklar & Çözümler
|
||
|
||
**Zorluk:** Pulsar'ın `Record`'ı çalışma zamanı alan doğrulamasına sahiptir
|
||
**Çözüm:** Gerekirse doğrulama için Pydantic dataclass'larını kullanın veya `__post_init__` ile Python 3.10+ dataclass özelliklerini kullanın
|
||
|
||
**Zorluk:** Bazı Pulsar'a özgü özellikler (örneğin `Bytes` türü)
|
||
**Çözüm:** Bu türü dataclass'ta `bytes` türüne eşleyin, arka uç uygun şekilde kodlamayı işler
|
||
|
||
**Zorluk:** Konu adlandırması (`persistent://tenant/namespace/topic`)
|
||
**Çözüm:** Şema tanımlarında konu adlarını soyutlayın, arka uç uygun formata dönüştürür
|
||
|
||
**Zorluk:** Şema evrimi ve sürüm oluşturma
|
||
**Çözüm:** Her arka uç, yeteneklerine göre bunu işler (Pulsar şema sürümleri, Kafka şema kaydı, vb.)
|
||
|
||
**Zorluk:** İç içe karmaşık türler
|
||
**Çözüm:** İç içe dataclass'ları kullanın, arka uçlar yinelemeli olarak seri dışa aktarır/serileştirir
|
||
|
||
### Tasarım Kararları
|
||
|
||
1. **Basit dataclass'lar mı yoksa Pydantic mi?**
|
||
✅ **Karar: Basit Python dataclass'ları kullanın**
|
||
Daha basit, ek bağımlılık yok
|
||
Uygulamada doğrulama gerekli değil
|
||
Anlaşılması ve bakımı daha kolay
|
||
|
||
2. **Şema evrimi:**
|
||
✅ **Karar: Herhangi bir sürüm oluşturma mekanizmasına gerek yok**
|
||
Şemalar kararlı ve uzun ömürlü
|
||
Güncellemeler genellikle yeni alanlar ekler (geriye dönük uyumlu)
|
||
Arka uçlar, yeteneklerine göre şema evrimini işler
|
||
|
||
3. **Geriye dönük uyumluluk:**
|
||
✅ **Karar: Ana sürüm değişikliği, geriye dönük uyumluluk gerekli değil**
|
||
Bu, bir kopma değişikliği olacak ve geçiş talimatları sağlanacak
|
||
Temiz bir kopma, daha iyi bir tasarım sağlar
|
||
Mevcut dağıtımlar için bir geçiş kılavuzu sağlanacaktır
|
||
|
||
4. **İç içe türler ve karmaşık yapılar:**
|
||
✅ **Karar: İç içe dataclass'ları doğal olarak kullanın**
|
||
Python dataclass'ları iç içe geçmeyi mükemmel şekilde işler
|
||
Diziler için `list[T]`, haritalar için `dict[K, V]`
|
||
Arka uçlar yinelemeli olarak seri dışa aktarır/serileştirir
|
||
Örnek:
|
||
```python
|
||
@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. **Varsayılan değerler ve isteğe bağlı alanlar:**
|
||
✅ **Karar: Gerekli, varsayılan ve isteğe bağlı alanların birleşimi**
|
||
Gerekli alanlar: Herhangi bir varsayılan değer yok
|
||
Varsayılan değerlere sahip alanlar: Her zaman mevcut, anlamlı varsayılan değerlere sahip
|
||
Tamamen isteğe bağlı alanlar: `T | None = None`, `None` olduğunda serileştirmeden çıkarılır
|
||
Örnek:
|
||
```python
|
||
@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
|
||
```
|
||
|
||
**Önemli serileştirme kuralları:**
|
||
|
||
`metadata = None` olduğunda:
|
||
```json
|
||
{
|
||
"system": "...",
|
||
"prompt": "...",
|
||
"streaming": false
|
||
// metadata field NOT PRESENT
|
||
}
|
||
```
|
||
|
||
`metadata = {}` (açıkça boş) olduğunda:
|
||
```json
|
||
{
|
||
"system": "...",
|
||
"prompt": "...",
|
||
"streaming": false,
|
||
"metadata": {} // Field PRESENT but empty
|
||
}
|
||
```
|
||
|
||
**Temel ayrım:**
|
||
`None` → JSON'da bulunmayan alan (serileştirilmiyor)
|
||
Boş değer (`{}`, `[]`, `""`) → alan, boş bir değerle mevcut
|
||
Bu, anlamsal olarak önemlidir: "sağlanmadı" ile "açıkça boş"
|
||
Serileştirme arka uçları, `None` alanlarını atlamalı, bunları `null` olarak kodlamamalıdır.
|
||
|
||
## Yaklaşım Taslağı 3: Uygulama Detayları
|
||
|
||
### Genel Kuyruk Adlandırma Formatı
|
||
|
||
Arka uçlara özgü kuyruk adlarını, arka uçların uygun şekilde eşleyebileceği genel bir formata dönüştürün.
|
||
|
||
**Format:** `{qos}/{tenant}/{namespace}/{queue-name}`
|
||
|
||
Nerede:
|
||
`qos`: Hizmet kalitesi seviyesi
|
||
`q0` = en iyi çaba (ateşle ve unut, onay yok)
|
||
`q1` = en az bir kez (onay gerektirir)
|
||
`q2` = tam olarak bir kez (iki aşamalı onay)
|
||
`tenant`: Çok kiracılılık için mantıksal gruplandırma
|
||
`namespace`: Kiracı içindeki alt gruplandırma
|
||
`queue-name`: Gerçek kuyruk/konu adı
|
||
|
||
**Örnekler:**
|
||
```
|
||
q1/tg/flow/text-completion-requests
|
||
q2/tg/config/config-push
|
||
q0/tg/metrics/stats
|
||
```
|
||
|
||
### Arka Uç Konu Eşlemesi
|
||
|
||
Her arka uç, genel formatı kendi yerel formatına eşler:
|
||
|
||
**Pulsar Arka Ucu:**
|
||
```python
|
||
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 Altyapısı:**
|
||
```python
|
||
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
|
||
```
|
||
|
||
### Güncellenmiş Konu Yardımcı Fonksiyonu
|
||
|
||
```python
|
||
# 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}"
|
||
```
|
||
|
||
### Yapılandırma ve Başlatma
|
||
|
||
**Komut Satırı Argümanları + Ortam Değişkenleri:**
|
||
|
||
```python
|
||
# 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)'
|
||
)
|
||
```
|
||
|
||
**Fabrika Fonksiyonu:**
|
||
|
||
```python
|
||
# 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'da Kullanım:**
|
||
|
||
```python
|
||
# 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...
|
||
```
|
||
|
||
### Arka Uç Arayüzü
|
||
|
||
```python
|
||
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."""
|
||
...
|
||
```
|
||
|
||
```python
|
||
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."""
|
||
...
|
||
```
|
||
|
||
```python
|
||
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."""
|
||
...
|
||
```
|
||
|
||
```python
|
||
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."""
|
||
...
|
||
```
|
||
|
||
### Mevcut Sınıfların Yeniden Düzenlenmesi
|
||
|
||
Mevcut `Consumer`, `Producer`, `Publisher`, `Subscriber` sınıfları büyük ölçüde aynı kalacaktır:
|
||
|
||
**Mevcut sorumluluklar (saklanacak):**
|
||
Asenkron iş parçacığı modeli ve görev grupları
|
||
Yeniden bağlantı mantığı ve tekrar deneme işleme
|
||
Ölçüm toplama
|
||
Hız sınırlama
|
||
Eşzamanlılık yönetimi
|
||
|
||
**Gerekli değişiklikler:**
|
||
Doğrudan Pulsar içe aktarmalarını kaldırın (`pulsar.schema`, `pulsar.InitialPosition`, vb.)
|
||
`BackendProducer`/`BackendConsumer`'i Pulsar istemcisi yerine kullanın
|
||
Gerçek yayın/abonelik işlemlerini arka uç örneklerine devredin
|
||
Genel kavramları arka uç çağrılarına eşleyin
|
||
|
||
**Örnek yeniden düzenleme:**
|
||
|
||
```python
|
||
# 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)
|
||
```
|
||
|
||
### Arka Uç Özel Davranışlar
|
||
|
||
**Pulsar Arka Uç:**
|
||
`q0`'ı `non-persistent://`'e, `q1`/`q2`'ü `persistent://`'e eşler.
|
||
Tüm tüketici türlerini (paylaşımlı, özel, yedekli) destekler.
|
||
Başlangıç konumunu (en erken/en son) destekler.
|
||
Yerel mesaj onayı.
|
||
Şema kayıt defteri desteği.
|
||
|
||
**MQTT Arka Uç:**
|
||
`q0`/`q1`/`q2`'yi MQTT QoS seviyeleri 0/1/2'ye eşler.
|
||
Adlandırma için konu yoluna kiracı/isim alanı ekler.
|
||
Abonelik adlarından otomatik olarak istemci kimlikleri oluşturur.
|
||
Başlangıç konumunu yoksayar (temel MQTT'de mesaj geçmişi yoktur).
|
||
Tüketici türünü yoksayar (MQTT, tüketici grupları yerine istemci kimlikleri kullanır).
|
||
Basit yayın/abone modeli.
|
||
|
||
### Tasarım Kararları Özeti
|
||
|
||
1. ✅ **Genel kuyruk adlandırması**: `qos/tenant/namespace/queue-name` formatı
|
||
2. ✅ **Kuyruk kimliğindeki QoS**: Kuyruk tanımı tarafından belirlenir, yapılandırma ile değil.
|
||
3. ✅ **Yeniden bağlanma**: Tüketici/Üretici sınıfları tarafından işlenir, arka uçlar tarafından değil.
|
||
4. ✅ **MQTT konuları**: Doğru adlandırma için kiracı/isim alanı içerir.
|
||
5. ✅ **Mesaj geçmişi**: MQTT, `initial_position` parametresini yoksayar (gelecek geliştirmeler).
|
||
6. ✅ **İstemci kimlikleri**: MQTT arka ucu, abonelik adından otomatik olarak oluşturur.
|
||
|
||
### Gelecek Geliştirmeler
|
||
|
||
**MQTT mesaj geçmişi:**
|
||
İsteğe bağlı bir kalıcılık katmanı eklenebilir (örneğin, saklanan mesajlar, harici depolama).
|
||
`initial_position='earliest'`'ı desteklemeyi mümkün kılacaktır.
|
||
Başlangıç uygulamasında gerekli değildir.
|