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: "Mifumo ya Uwasilishaji na Ufuatiliaji (Pub/Sub)"
|
|
parent: "Swahili (Beta)"
|
|
---
|
|
|
|
# Mifumo ya Uwasilishaji na Ufuatiliaji (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.
|
|
|
|
## Muhtasari
|
|
|
|
Hati hii inaorodhesha miunganisho yote kati ya mfumo wa TrustGraph na miundomino ya uwasilishaji na ufuatiliaji. Kwa sasa, mfumo huu umewekwa ili kutumia Apache Pulsar. Uchunguzi huu unaeleza maeneo yote ya kuunganisha ili kutoa taarifa kwa urekebishaji wa baadaye kuelekea uainishaji wa uwasilishaji na ufuatiliaji unaoweza kusanidiwa.
|
|
|
|
## Hali ya Sasa: Maeneo ya Kuunganisha ya Pulsar
|
|
|
|
### 1. Matumizi ya Moja kwa Moja ya Mteja wa Pulsar
|
|
|
|
**Mahali:** `trustgraph-flow/trustgraph/gateway/service.py`
|
|
|
|
Lango la API huleta na kuunda mteja wa Pulsar moja kwa moja:
|
|
|
|
**Laini ya 20:** `import pulsar`
|
|
**Laini za 54-61:** Uundaji wa moja kwa moja wa `pulsar.Client()` pamoja na `pulsar.AuthenticationToken()` inayohitajika.
|
|
**Laini za 33-35:** Usanidi chaguo-msingi wa hosti wa Pulsar kutoka kwa vigezo vya mazingira.
|
|
**Laini za 178-192:** Vigezo vya CLI kwa `--pulsar-host`, `--pulsar-api-key`, na `--pulsar-listener`.
|
|
**Laini za 78, 124:** Hupitisha `pulsar_client` kwa `ConfigReceiver` na `DispatcherManager`.
|
|
|
|
Hii ndio eneo pekee ambalo huunda mteja wa Pulsar moja kwa moja nje ya safu ya uainishaji.
|
|
|
|
### 2. Muundo wa Msingi wa Mchakato
|
|
|
|
**Mahali:** `trustgraph-base/trustgraph/base/async_processor.py`
|
|
|
|
Darasa la msingi kwa mchakato wote hutoa uwezo wa kuunganisha na Pulsar:
|
|
|
|
**Laini ya 9:** `import _pulsar` (kwa usimamizi wa makosa)
|
|
**Laini ya 18:** `from . pubsub import PulsarClient`
|
|
**Laini ya 38:** Huunda `pulsar_client_object = PulsarClient(**params)`
|
|
**Laini za 104-108:** Vipengele ambavyo huonyesha `pulsar_host` na `pulsar_client`
|
|
**Laini ya 250:** Njia ya tuli `add_args()` huita `PulsarClient.add_args(parser)` kwa vigezo vya CLI
|
|
**Laini za 223-225:** Usimamizi wa makosa kwa `_pulsar.Interrupted`
|
|
|
|
Mchakato wote hurithi kutoka kwa `AsyncProcessor`, na hivyo kuwa eneo kuu la kuunganisha.
|
|
|
|
### 3. Uainishaji wa Mtumiaji
|
|
|
|
**Mahali:** `trustgraph-base/trustgraph/base/consumer.py`
|
|
|
|
Huchukua meseji kutoka kwa folyo na kutoa kazi za utendaji:
|
|
|
|
**Uingizaji wa Pulsar:**
|
|
**Laini ya 12:** `from pulsar.schema import JsonSchema`
|
|
**Laini ya 13:** `import pulsar`
|
|
**Laini ya 14:** `import _pulsar`
|
|
|
|
**Matumizi maalum ya Pulsar:**
|
|
**Laini za 100, 102:** `pulsar.InitialPosition.Earliest` / `pulsar.InitialPosition.Latest`
|
|
**Laini ya 108:** `JsonSchema(self.schema)` wrapper
|
|
**Laini ya 110:** `pulsar.ConsumerType.Shared`
|
|
**Laini za 104-111:** `self.client.subscribe()` pamoja na vigezo maalum ya Pulsar
|
|
**Laini za 143, 150, 65:** `consumer.unsubscribe()` na `consumer.close()` methods
|
|
**Laini ya 162:** `_pulsar.Timeout` exception
|
|
**Laini za 182, 205, 232:** `consumer.acknowledge()` / `consumer.negative_acknowledge()`
|
|
|
|
**Faili ya spec:** `trustgraph-base/trustgraph/base/consumer_spec.py`
|
|
**Laini ya 22:** Inarejelea `processor.pulsar_client`
|
|
|
|
### 4. Uainishaji wa Mtume
|
|
|
|
**Mahali:** `trustgraph-base/trustgraph/base/producer.py`
|
|
|
|
Hutuma meseji kwa folyo:
|
|
|
|
**Uingizaji wa Pulsar:**
|
|
**Laini ya 2:** `from pulsar.schema import JsonSchema`
|
|
|
|
**Matumizi maalum ya Pulsar:**
|
|
**Laini ya 49:** `JsonSchema(self.schema)` wrapper
|
|
**Laini za 47-51:** `self.client.create_producer()` pamoja na vigezo maalum ya Pulsar (mada, schema, chunking_enabled)
|
|
**Laini za 31, 76:** `producer.close()` method
|
|
**Laini za 64-65:** `producer.send()` pamoja na meseji na vipengele
|
|
|
|
**Faili ya spec:** `trustgraph-base/trustgraph/base/producer_spec.py`
|
|
**Laini ya 18:** Inarejelea `processor.pulsar_client`
|
|
|
|
### 5. Uainishaji wa Mchapishaji
|
|
|
|
**Mahali:** `trustgraph-base/trustgraph/base/publisher.py`
|
|
|
|
Uchapishaji wa meseji usiohusisha na uwekaji wa folyo:
|
|
|
|
**Uingizaji wa Pulsar:**
|
|
**Laini ya 2:** `from pulsar.schema import JsonSchema`
|
|
**Laini ya 6:** `import pulsar`
|
|
|
|
**Matumizi maalum ya Pulsar:**
|
|
**Laini ya 52:** `JsonSchema(self.schema)` wrapper
|
|
**Laini za 50-54:** `self.client.create_producer()` pamoja na vigezo maalum ya Pulsar
|
|
**Laini za 101, 103:** `producer.send()` pamoja na meseji na vipengele vya hiari
|
|
**Laini za 106-107:** `producer.flush()` na `producer.close()` methods
|
|
|
|
### 6. Uainishaji wa Mlisani
|
|
|
|
**Mahali:** `trustgraph-base/trustgraph/base/subscriber.py`
|
|
|
|
Inatoa usambazaji wa ujumbe kwa wapokeaji wengi kutoka kwa folyo:
|
|
|
|
**Uingizaji kutoka Pulsar:**
|
|
**Laini ya 6:** `from pulsar.schema import JsonSchema`
|
|
**Laini ya 8:** `import _pulsar`
|
|
|
|
**Matumizi maalum ya Pulsar:**
|
|
**Laini ya 55:** `JsonSchema(self.schema)` wrapper
|
|
**Laini ya 57:** `self.client.subscribe(**subscribe_args)`
|
|
**Laini 101, 136, 160, 167-172:** Vizuizi vya Pulsar: `_pulsar.Timeout`, `_pulsar.InvalidConfiguration`, `_pulsar.AlreadyClosed`
|
|
**Laini 159, 166, 170:** Mbinu za mtumiaji: `negative_acknowledge()`, `unsubscribe()`, `close()`
|
|
**Laini 247, 251:** Utambuzi wa ujumbe: `acknowledge()`, `negative_acknowledge()`
|
|
|
|
**Faili ya spec:** `trustgraph-base/trustgraph/base/subscriber_spec.py`
|
|
**Laini ya 19:** Inarejelea `processor.pulsar_client`
|
|
|
|
### 7. Mfumo wa Schemas (Heart of Darkness)
|
|
|
|
**Mahali:** `trustgraph-base/trustgraph/schema/`
|
|
|
|
Schemas kila ujumbe katika mfumo huu imefafanuliwa kwa kutumia mfumo wa schemas wa Pulsar.
|
|
|
|
**Vipengele muhimu:** `schema/core/primitives.py`
|
|
**Laini ya 2:** `from pulsar.schema import Record, String, Boolean, Array, Integer`
|
|
Schemas zote hurithi kutoka kwa darasa la msingi la Pulsar `Record`
|
|
Aina zote za sehemu ni aina za Pulsar: `String()`, `Integer()`, `Boolean()`, `Array()`, `Map()`, `Double()`
|
|
|
|
**Sampuli za schemas:**
|
|
`schema/services/llm.py` (Laini ya 2): `from pulsar.schema import Record, String, Array, Double, Integer, Boolean`
|
|
`schema/services/config.py` (Laini ya 2): `from pulsar.schema import Record, Bytes, String, Boolean, Array, Map, Integer`
|
|
|
|
**Jina la mada:** `schema/core/topic.py`
|
|
**Laini 2-3:** Muundo wa mada: `{kind}://{tenant}/{namespace}/{topic}`
|
|
Muundo huu wa URI ni maalum kwa Pulsar (k.m.e., `persistent://tg/flow/config`)
|
|
|
|
**Athari:**
|
|
Ufafanuzi wote wa ujumbe wa ombi/jibu katika msimbo wote hutumia schemas za Pulsar
|
|
Hii inajumuisha huduma za: config, flow, llm, prompt, query, storage, agent, collection, diagnosis, library, lookup, nlp_query, objects_query, retrieval, structured_query
|
|
Ufafanuzi wa schemas huingizwa na kutumika kwa kina katika processors na huduma zote
|
|
|
|
## Muhtasari
|
|
|
|
### Utegemezi wa Pulsar kwa Kategoria
|
|
|
|
1. **Uundaji wa mteja:**
|
|
Moja kwa moja: `gateway/service.py`
|
|
Imefichwa: `async_processor.py` → `pubsub.py` (PulsarClient)
|
|
|
|
2. **Usafirishaji wa ujumbe:**
|
|
Mtumiaji: `consumer.py`, `consumer_spec.py`
|
|
Mtayarishaji: `producer.py`, `producer_spec.py`
|
|
Mchapishaji: `publisher.py`
|
|
Msubiri: `subscriber.py`, `subscriber_spec.py`
|
|
|
|
3. **Mfumo wa schemas:**
|
|
Aina za msingi: `schema/core/primitives.py`
|
|
Schemas zote za huduma: `schema/services/*.py`
|
|
Jina la mada: `schema/core/topic.py`
|
|
|
|
4. **Dhima maalum za Pulsar zinazohitajika:**
|
|
Ujumbe unaotegemea mada
|
|
Mfumo wa schemas (Rekodi, aina za sehemu)
|
|
Usajili uliogawanywa
|
|
Utambuzi wa ujumbe (chanya/hasi)
|
|
Nafasi ya mtumiaji (mapema/ya hivi karibuni)
|
|
Sifa za ujumbe
|
|
Nafasi ya awali na aina za mtumiaji
|
|
Usaidizi wa chunking
|
|
Mada za kudumu vs. zisizo za kudumu
|
|
|
|
### Changamoto za Urekebishaji
|
|
|
|
Habari njema: Safu ya uainishaji (Mtumiaji, Mtayarishaji, Mchapishaji, Msubiri) hutoa uainishaji safi wa mwingiliano mwingi wa Pulsar.
|
|
|
|
Changamoto:
|
|
1. **Ukuaji wa mfumo wa schemas:** Ufafanuzi kila ujumbe hutumia `pulsar.schema.Record` na aina za Pulsar
|
|
2. **Enums maalum za Pulsar:** `InitialPosition`, `ConsumerType`
|
|
3. **Vizuizi vya Pulsar:** `_pulsar.Timeout`, `_pulsar.Interrupted`, `_pulsar.InvalidConfiguration`, `_pulsar.AlreadyClosed`
|
|
4. **Mifumo ya mbinu:** `acknowledge()`, `negative_acknowledge()`, `subscribe()`, `create_producer()`, n.k.
|
|
5. **Muundo wa URI ya mada:** Muundo wa `kind://tenant/namespace/topic` wa Pulsar
|
|
|
|
### Hatua Zinazofuata
|
|
|
|
Ili kufanya miundombinu ya p/s kuwa configurable, tunahitaji:
|
|
|
|
1. Kuunda kiolesura cha uainishaji kwa mfumo wa mteja/schema
|
|
2. Kuainisha enums na vizuizi maalum za Pulsar
|
|
3. Kuunda wrappers za schemas au ufafanuzi mbadala wa schemas
|
|
4. Kutekeleza kiolesura kwa wateja na mifumo mingine (Kafka, RabbitMQ, Redis Streams, n.k.)
|
|
5. Kusasisha `pubsub.py` ili iwe configurable na iunge mkono mifumo mingi
|
|
6. Kutoa njia ya uhamishaji kwa usakinishaji uliopo
|
|
|
|
## Mfumo Mkuu wa 1: Mfumo wa Adapta na Safu ya Tafsiri ya Schemas
|
|
|
|
### Maarifa Muhimu
|
|
Mfumo wa schemas ndio msingi wa mfumo huu.
|
|
|
|
|
|
|
|
**1. Endelea kutumia muundo wa Pulsar kama uwakilishi wa ndani**
|
|
Usiandike upya maelezo yote ya muundo.
|
|
Muundo utabaki `pulsar.schema.Record` ndani.
|
|
Tumia adapta ili kutafsiri katika eneo kati ya programu yetu na mfumo wa utumaji/kupokea.
|
|
|
|
**2. Unda safu ya utengwa kwa utumaji/kupokea:**
|
|
|
|
```
|
|
┌─────────────────────────────────────┐
|
|
│ Existing Code (unchanged) │
|
|
│ - Uses Pulsar schemas internally │
|
|
│ - Consumer/Producer/Publisher │
|
|
└──────────────┬──────────────────────┘
|
|
│
|
|
┌──────────────┴──────────────────────┐
|
|
│ PubSubFactory (configurable) │
|
|
│ - Creates backend-specific client │
|
|
└──────────────┬──────────────────────┘
|
|
│
|
|
┌──────┴──────┐
|
|
│ │
|
|
┌───────▼─────┐ ┌────▼─────────┐
|
|
│ PulsarAdapter│ │ KafkaAdapter │ etc...
|
|
│ (passthrough)│ │ (translates) │
|
|
└──────────────┘ └──────────────┘
|
|
```
|
|
|
|
**3. Tafakikata viambishi vya dhahabu:**
|
|
`PubSubClient` - muunganisho wa mteja
|
|
`PubSubProducer` - kutuma ujumbe
|
|
`PubSubConsumer` - kupokea ujumbe
|
|
`SchemaAdapter` - kutafsiri muundo wa Pulsar kuwa/kutoka JSON au muundo maalum wa mfumo wa nyuma
|
|
|
|
**4. Maelezo ya utekelezaji:**
|
|
|
|
Kwa **adapta ya Pulsar**: Karibu kupita moja kwa moja, tafsiri ndogo.
|
|
|
|
Kwa **mfumo mwingine wa nyuma** (Kafka, RabbitMQ, n.k.):
|
|
Tafsiri vitu vya rekodi ya Pulsar kuwa JSON/bytes
|
|
Linganisha dhana kama:
|
|
`InitialPosition.Earliest/Latest` → auto.offset.reset ya Kafka
|
|
`acknowledge()` → kukubali kwa Kafka
|
|
`negative_acknowledge()` → mfumo wa kurudisha au DLQ
|
|
URI za mada → majina ya mada maalum ya mfumo wa nyuma
|
|
|
|
### Uchambuzi
|
|
|
|
**Faida:**
|
|
✅ Mabadiliko madogo ya msimbo kwa huduma zilizopo
|
|
✅ Muundo unaendelea kuwa kama ilivyo (hakuna marekebisho makubwa)
|
|
✅ Njia ya hatua kwa hatua ya uhamishaji
|
|
✅ Watumiaji wa Pulsar hawona tofauti
|
|
✅ Mifumo mipya ya nyuma inaongezwa kupitia adapta
|
|
|
|
**Hasara:**
|
|
⚠️ Bado ina utegemezi wa Pulsar (kwa maelezo ya muundo)
|
|
⚠️ Mizozo mingine inapotafsiri dhana
|
|
|
|
### Toleo Mbadala
|
|
|
|
Unda **mfumo wa muundo wa TrustGraph** ambao hautegemei mfumo wowote wa kutuma na kupokea (kwa kutumia madarasa ya data au Pydantic), kisha uzalisha muundo wa Pulsar/Kafka/n.k. kutoka humo. Hii inahitaji kuandikewa tena kila faili ya muundo na inaweza kusababisha mabadiliko.
|
|
|
|
### Mapendekezo kwa Rasimu ya 1
|
|
|
|
Anza na **mbinu ya adapta** kwa sababu:
|
|
1. Ni ya vitendo - inafanya kazi na msimbo uliopo
|
|
2. Inathibitisha dhana kwa hatari ndogo
|
|
3. Inaweza kubadilika kuwa mfumo wa asili wa muundo baadaye ikiwa inahitajika
|
|
4. Inadumishwa kupitia usanidi: variable moja ya mazingira inabadilisha mifumo ya nyuma
|
|
|
|
## Mbinu ya Rasimu ya 2: Mfumo wa Muundo Usio na Utendaji wa Nyuma na Madarasa ya Data
|
|
|
|
### Dhana Kuu
|
|
|
|
Tumia **madarasa ya data ya Python** kama muundo wa muundo wa kati. Kila mfumo wa nyuma wa kutuma na kupokea hutoa utafsiri wake mwenyewe wa kuandika/kusoma kwa madarasa ya data, na kuondoa hitaji kwamba muundo wa Pulsar uendelee kuwa katika msimbo.
|
|
|
|
### Ulinganifu wa Muundo katika Kiwango cha Kiwanda
|
|
|
|
Badala ya kutafsiri muundo wa Pulsar, **kila mfumo wa nyuma hutoa utunzaji wake mwenyewe wa muundo** ambao unafanya kazi na madarasa ya data ya Python ya kawaida.
|
|
|
|
### Mtiririko wa Mchapishaji
|
|
|
|
```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
|
|
```
|
|
|
|
### Mchakato wa Mtumiaji
|
|
|
|
```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
|
|
```
|
|
|
|
### Kile Kinachotokea Nyuma ya Kulabu
|
|
|
|
**Kwa mfumo wa nyuma (backend) wa Pulsar:**
|
|
`create_producer()` → huunda mtayarishaji (producer) wa Pulsar ukitumia schema ya JSON au rekodi iliyoundwa moja kwa moja.
|
|
`send(request)` → huhifadhi (hufanya serialization) darasa la data (dataclass) kuwa muundo wa JSON/Pulsar, na hutuma kwa Pulsar.
|
|
`receive()` → hupokea ujumbe wa Pulsar, na huhifadhi tena (hufanya deserialization) kurudi kuwa darasa la data.
|
|
|
|
**Kwa mfumo wa nyuma (backend) wa MQTT:**
|
|
`create_producer()` → huunganisha na programu (broker) ya MQTT, hakuna haja ya usajili wa schema.
|
|
`send(request)` → hubadilisha darasa la data kuwa JSON, na hutuma kwenye mada (topic) ya MQTT.
|
|
`receive()` → huhudhuria mada (topic) ya MQTT, na huhifadhi tena JSON kurudi kuwa darasa la data.
|
|
|
|
**Kwa mfumo wa nyuma (backend) wa Kafka:**
|
|
`create_producer()` → huunda mtayarishaji (producer) wa Kafka, na husajili schema ya Avro ikiwa inahitajika.
|
|
`send(request)` → huhifadhi darasa la data kuwa muundo wa Avro, na hutuma kwa Kafka.
|
|
`receive()` → hupokea ujumbe wa Kafka, na huhifadhi tena Avro kurudi kuwa darasa la data.
|
|
|
|
### Vipengele Muhimu vya Ubunifu
|
|
|
|
1. **Uundaji wa kitu (object) cha schema:** Kitu (object) cha darasa la data (dataclass) (`TextCompletionRequest(...)`) ni sawa bila kujali mfumo wa nyuma (backend).
|
|
2. **Mfumo wa nyuma (backend) hutunza uhifadhi:** Kila mfumo wa nyuma (backend) unajua jinsi ya kuhifadhi darasa lake la data kuwa muundo unaotumwa.
|
|
3. **Ufafanuzi wa schema wakati wa uundaji:** Unapounda mtayarishaji (producer)/mpokeaji (consumer), unataja aina ya schema.
|
|
4. **Usalama wa aina (type) unahifadhiwa:** Unapata kitu (object) sahihi cha `TextCompletionRequest`, sio kamusi (dict).
|
|
5. **Hakuna uvujaji wa mfumo wa nyuma (backend):** Msimbo wa programu kamwe hauingize maktaba maalum za mfumo wa nyuma (backend).
|
|
|
|
### Mfano wa Ubadilishaji
|
|
|
|
**Hali ya sasa (maalum kwa Pulsar):**
|
|
```python
|
|
# schema/services/llm.py
|
|
from pulsar.schema import Record, String, Boolean, Integer
|
|
|
|
class TextCompletionRequest(Record):
|
|
system = String()
|
|
prompt = String()
|
|
streaming = Boolean()
|
|
```
|
|
|
|
**Mpya (Sio tegemezi ya mfumo wa nyuma):**
|
|
```python
|
|
# schema/services/llm.py
|
|
from dataclasses import dataclass
|
|
|
|
@dataclass
|
|
class TextCompletionRequest:
|
|
system: str
|
|
prompt: str
|
|
streaming: bool = False
|
|
```
|
|
|
|
### Uunganisho wa Seva ya Nyuma (Backend)
|
|
|
|
Kila seva ya nyuma hushughulikia us serialization/deserialization wa madatakesi:
|
|
|
|
**Seva ya nyuma ya Pulsar:**
|
|
Huunda madatakesi `pulsar.schema.Record` moja kwa moja kutoka kwa madatakesi.
|
|
Au huserialize madatakesi hadi JSON na kutumia mfumo wa JSON wa Pulsar.
|
|
Inaendelea kudumisha utangamano na matumizi ya sasa ya Pulsar.
|
|
|
|
**Seva ya nyuma ya MQTT/Redis:**
|
|
Huserialize madatakesi ya aina ya JSON moja kwa moja.
|
|
Tumia `dataclasses.asdict()` / `from_dict()`.
|
|
Nyepesi, haihitaji usajili wa mfumo.
|
|
|
|
**Seva ya nyuma ya Kafka:**
|
|
Huunda mifumo ya Avro kutoka kwa maelezo ya madatakesi.
|
|
Tumia usajili wa mfumo wa Confluent.
|
|
Us serialization wa salama wa aina na udhamini wa mabadiliko ya mfumo.
|
|
|
|
### Muundo
|
|
|
|
```
|
|
┌─────────────────────────────────────┐
|
|
│ 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 │ │ │
|
|
└─────────────────┘ └───────────────────┘
|
|
```
|
|
|
|
### Maelezo ya Utendaji
|
|
|
|
**1. Ufafanuzi wa muundo:** Darasa za data za kawaida na maelezo ya aina
|
|
`str`, `int`, `bool`, `float` kwa vipengele vya msingi
|
|
`list[T]` kwa safu
|
|
`dict[str, T]` kwa ramani
|
|
Darasa za data zilizounganishwa kwa aina ngumu
|
|
|
|
**2. Kila mfumo hutoa:**
|
|
Mfumo wa ubadilishaji: `dataclass → bytes/wire format`
|
|
Mfumo wa kurejesha: `bytes/wire format → dataclass`
|
|
Usajili wa muundo (ikiwa inahitajika, kama Pulsar/Kafka)
|
|
|
|
**3. Dhana ya mtumiaji/mtayarishaji:**
|
|
Tayari ipo (consumer.py, producer.py)
|
|
Sasisha ili kutumia ubadilishaji wa mfumo
|
|
Ondoa uingizaji wa moja kwa moja wa Pulsar
|
|
|
|
**4. Ulinganisho wa aina:**
|
|
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`
|
|
|
|
### Njia ya Uhamishaji
|
|
|
|
1. **Tengeneza matoleo ya darasa za data** ya muundo wote katika `trustgraph/schema/`
|
|
2. **Sasisha madarasa ya mfumo** (Mtumiaji, Mtayarishaji, Mchapishaji, Mwasili) ili kutumia ubadilishaji unaotolewa na mfumo
|
|
3. **Teleza PulsarBackend** na muundo wa JSON au uzalishaji wa Rekodi wa moja kwa moja
|
|
4. **Jaribu na Pulsar** ili kuhakikisha utangamano wa nyuma na matumizi yaliyopo
|
|
5. **Ongeza mifumo mipya** (MQTT, Kafka, Redis, n.k.) kama inahitajika
|
|
6. **Ondoa uingizaji wa Pulsar** kutoka kwa faili za muundo
|
|
|
|
### Faida
|
|
|
|
✅ **Hakuna utegemezi wa pub/sub** katika ufafanuzi wa muundo
|
|
✅ **Python ya kawaida** - rahisi kuelewa, kuangalia aina, na kutoa maelezo
|
|
✅ **Zana za kisasa** - inafanya kazi na mypy, kukamilisha kiotomatiki kwa IDE, na vichujio
|
|
✅ **Imeboreshwa kwa mfumo** - kila mfumo hutumia ubadilishaji wa asili
|
|
✅ **Hakuna gharama ya tafsiri** - ubadilishaji wa moja kwa moja, hakuna adapta
|
|
✅ **Usalama wa aina** - vitu halisi na aina sahihi
|
|
✅ **Uthibitisho rahisi** - inaweza kutumia Pydantic ikiwa inahitajika
|
|
|
|
### Changamoto na Suluhisho
|
|
|
|
**Changamoto:** `Record` ya Pulsar ina uthibitisho wa uwanja wakati wa utekelezaji
|
|
**Suluhisho:** Tumia darasa za data za Pydantic kwa uthibitisho ikiwa inahitajika, au vipengele vya darasa za data ya Python 3.10+ na `__post_init__`
|
|
|
|
**Changamoto:** Vipengele vingine maalum vya Pulsar (kama aina ya `Bytes`)
|
|
**Suluhisho:** Linganisha na aina ya `bytes` katika darasa ya data, mfumo hutunza uandikaji ipasavyo
|
|
|
|
**Changamoto:** Majina ya mada (`persistent://tenant/namespace/topic`)
|
|
**Suluhisho:** Dhani majina ya mada katika ufafanuzi wa muundo, mfumo hubadilisha kuwa muundo sahihi
|
|
|
|
**Changamoto:** Maendeleo na toleo la muundo
|
|
**Suluhisho:** Kila mfumo hushughulikia hii kulingana na uwezo wake (matoleo ya muundo ya Pulsar, rejista ya muundo ya Kafka, n.k.)
|
|
|
|
**Changamoto:** Aina ngumu zilizounganishwa
|
|
**Suluhisho:** Tumia darasa za data zilizounganishwa, mifumo inabadilisha/kurejesha kwa uangalifu
|
|
|
|
### Maamuzi ya Ubunifu
|
|
|
|
1. **Darasa za data za kawaida au Pydantic?**
|
|
✅ **Maamuzi: Tumia darasa za data za Python za kawaida**
|
|
Rahisi, hakuna utegemezi wa ziada
|
|
Uthibitisho hauhitajiki kwa mazoea
|
|
Rahisi kuelewa na kudumisha
|
|
|
|
2. **Maendeleo ya muundo:**
|
|
✅ **Maamuzi: Hakuna utaratibu wa toleo unaohitajika**
|
|
Miondoko ni thabiti na ya muda mrefu
|
|
Marekebisho kawaida huongeza sehemu mpya (utangamano wa nyuma)
|
|
Mifumo inashughulikia maendeleo ya muundo kulingana na uwezo wake
|
|
|
|
3. **Ulingano wa nyuma:**
|
|
✅ **Maamuzi: Mabadiliko makubwa ya toleo, utangamano wa nyuma hauhitajiki**
|
|
Itakuwa mabadiliko ya kuvunja na maagizo ya uhamishaji
|
|
Kutoa mtego huruhusu muundo bora
|
|
Mwongozo wa uhamishaji utatolewa kwa matumizi yaliyopo
|
|
|
|
4. **Aina zilizounganishwa na miundo ngumu:**
|
|
✅ **Maamuzi: Tumia darasa za data zilizounganishwa kwa asili**
|
|
Darasa za data za Python zinashughulikia uunganishaji kikamilifu
|
|
`list[T]` kwa safu, `dict[K, V]` kwa ramani
|
|
Mifumo inabadilisha/kurejesha kwa uangalifu
|
|
Mfano:
|
|
```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. **Maelezo ya msingi na sehemu za hiari:**
|
|
✅ **Uamuzi: Mchanganyiko wa sehemu za lazima, maelezo ya msingi, na sehemu za hiari**
|
|
Sehemu za lazima: Hakuna maelezo ya msingi
|
|
Sehemu zilizo na maelezo ya msingi: Zipo kila wakati, zina maelezo ya msingi yanayofaa
|
|
Sehemu za hiari kabisa: `T | None = None`, huachwa kutoka kwenye serialization wakati `None`
|
|
Mfano:
|
|
```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
|
|
```
|
|
|
|
**Maana muhimu ya usanifu:**
|
|
|
|
Wakati `metadata = None`:
|
|
```json
|
|
{
|
|
"system": "...",
|
|
"prompt": "...",
|
|
"streaming": false
|
|
// metadata field NOT PRESENT
|
|
}
|
|
```
|
|
|
|
Wakati `metadata = {}` (tupu wazi):
|
|
```json
|
|
{
|
|
"system": "...",
|
|
"prompt": "...",
|
|
"streaming": false,
|
|
"metadata": {} // Field PRESENT but empty
|
|
}
|
|
```
|
|
|
|
**Tofauti kuu:**
|
|
`None` → sehemu ambayo haina katika JSON (hairekebishwi)
|
|
Thamani tupu (`{}`, `[]`, `""`) → sehemu inayoonekana na thamani tupu
|
|
Hii ina umuhimu wa maana: "haiyapatikani" dhidi ya "tupu kwa uwazi"
|
|
Mifumo ya kurekebisha data lazima zisipite sehemu za `None`, badala ya kuzirekebisha kama `null`
|
|
|
|
## Mfumo wa Awali wa 3: Maelezo ya Utendaji
|
|
|
|
### Muundo wa Jina la Kawaida la Kundi
|
|
|
|
Badilisha majina ya kundi maalum ya kila mfumo na muundo wa kawaida ambao mifumo inaweza kulinganisha ipasavyo.
|
|
|
|
**Muundo:** `{qos}/{tenant}/{namespace}/{queue-name}`
|
|
|
|
Ambako:
|
|
`qos`: Ngazi ya Huduma ya Ubora
|
|
`q0` = juhudi za kawaida (tuma na usisahau, hakuna utambuzi)
|
|
`q1` = angalau mara moja (inahitaji utambuzi)
|
|
`q2` = hasiwa mara moja (utambuzi wa awamu mbili)
|
|
`tenant`: Kikundi cha mantiki kwa ushirikaji wa wateja wengi
|
|
`namespace`: Kikundi kidogo ndani ya mteja
|
|
`queue-name`: Jina halisi la kundi/mada
|
|
|
|
**Mfano:**
|
|
```
|
|
q1/tg/flow/text-completion-requests
|
|
q2/tg/config/config-push
|
|
q0/tg/metrics/stats
|
|
```
|
|
|
|
### Ramani ya Mada za Seva ya Nyuma (Backend)
|
|
|
|
Kila seva ya nyuma (backend) inaunganisha muundo wa jumla na muundo wake wa asili:
|
|
|
|
**Seva ya Nyuma ya Pulsar:**
|
|
```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}"
|
|
```
|
|
|
|
**Umfumo wa Nyuma wa MQTT:**
|
|
```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
|
|
```
|
|
|
|
### Kazi ya Msaidizi ya Mada Iliyosasishwa
|
|
|
|
```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}"
|
|
```
|
|
|
|
### Usanidi na Uanzishaji
|
|
|
|
**Vigezo vya Amri na Vigezo vya Mazingira:**
|
|
|
|
```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)'
|
|
)
|
|
```
|
|
|
|
**Fungua Kazi:**
|
|
|
|
```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}")
|
|
```
|
|
|
|
**Matumizi katika AsyncProcessor:**
|
|
|
|
```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...
|
|
```
|
|
|
|
### Kiolesura cha Nyuma
|
|
|
|
```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."""
|
|
...
|
|
```
|
|
|
|
### Urekebishaji wa Darasa Zilizopo
|
|
|
|
Madarasa yaliyopo ya `Consumer`, `Producer`, `Publisher`, `Subscriber` yanabaki kwa kiasi kikubwa bila kubadilishwa:
|
|
|
|
**Jukumu la sasa (hakikisha):**
|
|
Mfumo wa uzi usio na usumbufu na vikundi vya kazi
|
|
Mantiki ya kuunganisha tena na udhibiti wa kujaribu tena
|
|
Ukusanyaji wa metriki
|
|
Udhibiti wa kiwango
|
|
Usimamizi wa ushindani
|
|
|
|
**Mabadiliko yanayohitajika:**
|
|
Ondoa uingizaji wa moja kwa moja wa Pulsar (`pulsar.schema`, `pulsar.InitialPosition`, n.k.)
|
|
Kubali `BackendProducer`/`BackendConsumer` badala ya mteja wa Pulsar
|
|
Agiza shughuli halisi za kutuma/kupokea kwa mifumo ya nyuma
|
|
Linganisha dhana za jumla na simu za mfumo wa nyuma
|
|
|
|
**Mfano wa urekebishaji:**
|
|
|
|
```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)
|
|
```
|
|
|
|
### Tabia Maalum za Seva (Backend)
|
|
|
|
**Seva ya Pulsar:**
|
|
Inahusisha `q0` → `non-persistent://`, `q1`/`q2` → `persistent://`
|
|
Inasaidia aina zote za watumiaji (walioshirikiana, wa kipekee, wa chechezi)
|
|
Inasaidia nafasi ya awali (ya kwanza/ya mwisho)
|
|
Utambuzi wa asili wa ujumbe
|
|
Inasaidia usajili wa schema
|
|
|
|
**Seva ya MQTT:**
|
|
Inahusisha `q0`/`q1`/`q2` → Viwango vya QoS vya MQTT 0/1/2
|
|
Inajumuisha mpangilio/nafasi katika njia ya mada kwa ajili ya utenganishaji
|
|
Inazalisha kiotomatiki vitambulisho vya wateja kutoka kwa majina ya usajili
|
|
Inapuuza nafasi ya awali (hakuna historia ya ujumbe katika MQTT ya msingi)
|
|
Inapuuza aina ya mtumiaji (MQTT hutumia vitambulisho vya wateja, sio vikundi vya watumiaji)
|
|
Mfumo rahisi wa kuchapisha/kusajili
|
|
|
|
### Muhtasari wa Maamuzi ya Ubunifu
|
|
|
|
1. ✅ **Jina la kawaida la folyo:** Muundo wa `qos/tenant/namespace/queue-name`
|
|
2. ✅ **QoS katika kitambulisho cha folyo:** Huamuliwa na ufafanuzi wa folyo, sio usanidi
|
|
3. ✅ **Uunganishaji upya:** Unashughulikiwa na madarasa ya Mtumiaji/Mzalishaji, sio seva
|
|
4. ✅ **Mada za MQTT:** Zijumuishie mpangilio/nafasi kwa ajili ya utenganishaji sahihi
|
|
5. ✅ **Historia ya ujumbe:** MQTT inapuuza parameter ya `initial_position` (ongezeko la baadaye)
|
|
6. ✅ **Vitambulisho vya wateja:** Seva ya MQTT inazalisha kiotomatiki kutoka kwa jina la usajili
|
|
|
|
### Ongezeko za Baadaye
|
|
|
|
**Historia ya ujumbe wa MQTT:**
|
|
Inaweza kuongeza safu ya hiari ya kudumu (k.m., ujumbe uliokaguliwa, duka la nje)
|
|
Itaruhusu kuunga mkono `initial_position='earliest'`
|
|
Haihitajiki kwa utekelezaji wa awali
|