mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-04-26 00:46:22 +02:00
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.
|