1. Ümumiyyətlə, ChatGPT App arxitekturasında “axınlar” harada yaranır
SSE və HTTP-stream arasında hansı daha yaxşıdır deyə mübahisə etməzdən əvvəl, ümumiyyətlə stekimizdə harada axınlar mövcuddur bunu anlamaq faydalıdır.
Şərti olaraq üç səviyyəni ayırmaq olar.
Birincisi, ChatGPT və model səviyyəsi. Model artıq cavabı tokenlərlə striminq edir: cavab mətninin hərflərlə “yazıldığını” görürsünüz. Bu da bir axındır, amma tamamilə OpenAI tərəfindən idarə olunur və birbaşa sizin kodunuza aid deyil.
İkincisi, MCP səviyyəsi. ChatGPT MCP serverinizə qoşulduqda, adətən SSE bağlantısını saxlayır: server ona MCP-nin JSON‑RPC mesajlarını (cavablar və bildirişlər) push edir, ChatGPT isə cavabında ayrıca HTTP endpoint‑ə, məsələn /messages, sorğular göndərir. MCP terminlərində bu bazis nəqliyyatdır.
Üçüncüsü, Apps SDK və sizin backend səviyyəsi. Sizin React vidjetiniz GiftGenius ChatGPT sandbox‑da işləyir və backend/MCP‑gateway‑inizlə HTTP vasitəsilə ünsiyyət qurur: adi fetch ilə, fetch üzərindən axınla (ReadableStream) və ya SSE abunəliyi ilə (EventSource).
Bu səviyyələri bir-birinə qarışdırmamaq vacibdir. MCP hadisələri — ChatGPT ilə serverlər arasında olan “tel”dir; SSE/HTTP-stream isə vidjetlə HTTP backend‑iniz arasında — artıq sizin məsuliyyət sahənizdir.
Bunu sxemlə göstərmək olar.
flowchart TD
subgraph ChatGPT
UI[ChatGPT UI + model]
W[GiftGenius Widget]
end
subgraph YourInfra[İnkişaf etdiricinin infrastrukturu]
GW[MCP Gateway / Backend]
MCP[MCP Server]
end
UI -- "tool-call / cavablar\n(daxili token axını)" --> W
UI <-- "SSE üzərindən MCP\n(/sse + /messages)" --> MCP
W <-- "HTTP / fetch / SSE / stream" --> GW
GW <-- "JSON-RPC MCP" --> MCP
Bu gün diqqəti Widget ↔ Backend oxuna yönəldəcəyik və qismən MCP nəqliyyatının özü də SSE‑yə əsaslandığını xatırladacağıq.
Məhz bu hissədə — Widget ↔ Backend — necə ünsiyyət quracağımızı seçməli oluruq: sadə HTTP sorğuları, yoxsa axınlar. Növbəti bölmədə niyə “adi” HTTP-nin burada tezliklə kifayət etmədiyinə baxacağıq.
2. Niyə adi HTTP‑sorğu kifayət etmir
HTTP-nin standart modeli “sorğu → bir cavab”dır. Müştəri nəsə soruşur, server bir dəfə cavab verir, əlaqə bağlanır.
Bir çox tapşırıq üçün bu kifayətdir: job-un hazırkı statusunu almaq, istifadəçi ayarlarını saxlamaq, bazada artıq olan hazır hədiyyə siyahısını götürmək.
Amma uzunmüddətli əməliyyat edən kimi hər şey xışıltı ilə işləməyə başlayır.
Təsəvvür edin, GiftGenius:
- bir neçə mənbədən siqnallar toplayır (alış tarixçəsi, wishlist, sosial şəbəkələr),
- bunları bir neçə LLM sorğusundan keçirir,
- yüzlərlə namizəd arasından fərdi reytinq qurur.
Bütün bunlar onlarca saniyə çəkə bilər. Əgər adi HTTP sorğusunu 40 saniyə açıq saxlayıb susacaqsınızsa, UX köhnə brauzerlərdəki kimi olacaq: istifadəçi fırlanan spinnerə baxır və tətbiqin çöküb-çökmədiyini, yoxsa hələ də “düşündüyünü” təxmin edir.
UX‑dən əlavə texniki problemlər də var:
- ChatGPT, Vercel və proksilərdə taymautlar;
- proqres, qismən nəticələr (partial results) və s. göndərmək mümkün deyil;
- bağlantı qırılmasını düzgün idarə edib bərpa olunmur.
Buradan təbii həll gəlir: bir böyük cavabdan hazır olduqca göndərilən kiçik hissəciklər axınına keçmək.
Bu hissəciklər ola bilər:
- hadisələr (job.progress, job.completed) — bu, SSE üçündür;
- bir böyük yükün fraqmentləri (hesabat mətni, hədiyyələrlə NDJSON sətirləri) — bu, HTTP-stream üçündür.
3. SSE (Server‑Sent Events): hadisələrə abunə
SSE ilə başlayaq, çünki o, bir çox cəhətdən MCP‑yə “yaxındır”: MCP özü də serverdən müştəriyə hadisələri push etmək üçün HTTP üzərindən SSE bağlantısından istifadə edir.
SSE modelinin sadə izahı
SSE — adi HTTP üzərində işləyən protokoldur:
- müştəri GET sorğusunu elə bir endpoint‑ə açır ki, oradan cavabın Content-Type: text/event-stream olması göstərilir;
- server bağlantını bağlamır, vaxtaşırı ora aşağıdakı kimidə sətirlər yazır:
event: job.progress
data: {"jobId":"123","percent":40}
event: job.completed
data: {"jobId":"123","resultCount":12}
- brauzer tərəfində EventSource istifadə olunur, o:
- yenidən qoşulmaları özü izləyir;
- event: + data: + ikiqat sətirsonu formatını parse edir;
- onmessage / addEventListener("job.progress", ...) işləyicilərini çağırır.
Əsas məqam: kanal birtərəflidir. Yalnız server müştəriyə hadisələr göndərir. Müştəri bu bağlantı ilə heç nə göndərmir.
ChatGPT Apps üçün bu model, vidjet sadəcə jobId üzrə hadisələrə “abunə olmaq” və proqresə və tamamlanmaya reaksiya vermək istəyəndə əladır.
Next.js 16-da mini SSE endpoint nümunəsi
Güman edək ki, bizdə Job proqresi hadisələri üçün route handler var:
app/api/gift-jobs/[jobId]/events/route.ts
import { NextRequest } from "next/server";
export async function GET(req: NextRequest, { params }: { params: { jobId: string } }) {
const jobId = params.jobId;
const stream = new ReadableStream({
start(controller) {
// SSE hadisəsini göndərmək üçün utilit
const send = (event: string, data: unknown) => {
const payload = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
controller.enqueue(new TextEncoder().encode(payload));
};
send("job.started", { jobId });
let percent = 0;
const interval = setInterval(() => {
percent += 20;
if (percent >= 100) {
send("job.completed", { jobId, totalGifts: 10 });
clearInterval(interval);
controller.close();
} else {
send("job.progress", { jobId, percent });
}
}, 1000);
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream", // məhz burada SSE təyin edirik
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
}
Bu oyuncaq imitasiyadır: hər saniyə faiz artır və sonda job.completed gəlir. Sonra bu taymeri real worker/queue hadisələri ilə əvəz edəcəksiniz, amma sxem eyni qalacaq.
Klient: GiftGenius vidjetində SSE abunəliyi
React vidjetinin daxilində jobId olduqda bu axına abunə ola bilərik. Xatırladım ki, vidjet API‑si ChatGPT sandbox‑unda işləyir, amma EventSource orada da adi brauzerdəki kimi əlçatandır.
import { useEffect, useState } from "react";
export function GiftJobProgress({ jobId }: { jobId: string }) {
const [percent, setPercent] = useState(0);
useEffect(() => {
const url = `/api/gift-jobs/${jobId}/events`;
const es = new EventSource(url);
es.addEventListener("job.progress", (event) => {
const data = JSON.parse((event as MessageEvent).data);
setPercent(data.percent);
});
es.addEventListener("job.completed", () => {
setPercent(100);
es.close();
});
es.onerror = () => {
// burada "Bağlantı problemləri, yenidən qoşulmağa çalışırıq" mesajını göstərə bilərsiniz
};
return () => es.close();
}, [jobId]);
return <div>Hədiyyə seçiminin irəliləyişi: {percent}%</div>;
}
İndi bunu MCP aləti ilə əlaqələndirə bilərsiniz. start_gift_job aləti jobId qaytarır və vidjetinizin ToolOutput-unda sadəcə GiftJobProgress render edirsiniz.
Avtoyenidən qoşulma və Last‑Event‑ID
Standarta görə EventSource bağlantı kəsilərsə avtomatik yenidən qoşulmağa çalışır. Server hadisələrdə SSE‑nin standart id: sahəsindən, müştəri isə Last-Event-ID başlığından istifadə edərək, yenidən qoşulmadan sonra ötən hadisələri tutub çatdıra bilər.
Sadə GiftGenius üçün hələlik nə id:, nə də ayrıca hadisə identifikatorunu reallaşdırmaya bilərsiniz və reconnect zamanı proqresdə kiçik “boşluğa” icazə verə bilərsiniz. Amma production‑da, xüsusən də yüksək yüklənmədə sizə lazım olacaq:
- müştərinin yenidən qoşulmada Last-Event-ID ötürə bilməsi üçün hər SSE hadisəsinə standart id: sahəsi əlavə etmək;
- hadisə payload‑ında tətbiqi event_id daxil etmək və müştəri/backend tərəfində idempotent emalda ona arxalanmaq.
Bu, idempotentliklə birbaşa uyğunlaşır: eyni job.progress iki dəfə gəlsə belə, emal edən tərəf tanış event_id gördükdə yan təsirləri təkrar yerinə yetirməyəcək.
Nəticədə SSE bizə jobId ətrafında hadisələrə abunəlik, avtoyenidən qoşulma və hadisə identifikatorları vasitəsilə dublikatların idarəsini verir. İndi isə ikinci tip axınları nəzərdən keçirək — bir sorğu var, amma cavab çox böyükdür və onu hissə-hissə qaytarmaq istəyirik.
4. HTTP‑streaming: tək sorğuya mərhələli cavab veririk
Əgər SSE “müstəqil hadisələrə abunəlik”dirsə, HTTP‑striminq “bir sorğu, bir cavab, amma cavab zaman üzrə uzadılıb çanklarla gəlir” deməkdir.
Məhz bunu siz stream : true ilə OpenAI API istifadə edəndə görürsünüz: server JSON çankları (tez-tez SSE formatında, amma məntiq “bir sorğu ↔ qismən cavab axını”dır) göndərir, müştəri isə onları yekun mətnə yığır.
Öz API‑lərinizdə siz də bunu aşağıdakılar üçün edə bilərsiniz:
- uzun mətn hesabatları (məsələn, seçilmiş hədiyyələrin məntiqinin izahı),
- uzun hədiyyə siyahıları (onları hissə-hissə striminq etmək, istifadəçini gözlətməmək).
Next.js‑də ən sadə HTTP‑stream endpoint
Tutaq ki, seçim nəticəsinin “izahını” yaratmaq lazımdır, burada LLM uzun mətn yazır. Biz bunu generator yazdıqca vidjetə striminq etmək istəyirik.
app/api/gift-report/route.ts
import { NextRequest } from "next/server";
export async function POST(req: NextRequest) {
const stream = new ReadableStream({
async start(controller) {
const encoder = new TextEncoder();
controller.enqueue(encoder.encode("Təhlilə başlayırıq...\n"));
// Burada LLM-in həqiqi çank‑generasiyası ola bilərdi
for (const line of ["Seçimləri toplayırıq...\n", "Büdcəni hesablayırıq...\n", "Yekun tövsiyələr...\n"]) {
await new Promise((r) => setTimeout(r, 1000));
controller.enqueue(encoder.encode(line));
}
controller.close();
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
"Transfer-Encoding": "chunked", // burada bunun HTTP/stream olduğunu göstəririk
},
});
}
Texniki cəhətdən Next chunked kodlaşdırmanı özü idarə edir, sizin üçün əsas odur ki, ReadableStream qaytarasınız.
Vidjetdə fetch vasitəsilə HTTP‑strimin oxunması
Klientdə (vidjet daxilində) axını belə oxumaq olar:
async function fetchReport(setText: (s: string) => void) {
const res = await fetch("/api/gift-report", { method: "POST" });
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let acc = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
acc += decoder.decode(value, { stream: true });
setText(acc); // daxil olduqca UI-ni yeniləyirik
}
}
Və bükücü komponent:
import { useState } from "react";
export function GiftReport() {
const [text, setText] = useState("");
return (
<div>
<button onClick={() => fetchReport(setText)}>Hesabatı yarat</button>
<pre style={{ whiteSpace: "pre-wrap" }}>{text}</pre>
</div>
);
}
Bu klassik nümunədir: bir POST /api/gift-report sorğusu, cavab — tədricən göstərdiyiniz mətn axını.
JSON deyil, mətn striminq edirik
Çox vaxt sətirlər yox, JSON obyektlərini striminq etmək istəyəcəksiniz. Ən populyar format — NDJSON (Newline‑delimited JSON): hər hadisə — bir JSON sətri və \n simvolu ilə bitir.
Server tərəfi nümunəsi:
const stream = new ReadableStream({
async start(controller) {
const encoder = new TextEncoder();
for (let i = 0; i < 5; i++) {
const chunk = { type: "gift", index: i, name: `Hədiyyə #${i}` };
controller.enqueue(encoder.encode(JSON.stringify(chunk) + "\n"));
await new Promise((r) => setTimeout(r, 500));
}
controller.close();
},
});
Müştəri TextDecoder ilə oxuyur, \n üzrə bölür və ayrı‑ayrı JSON obyektlərini parse edir.
5. SSE vs HTTP‑stream: fərq nədir və necə seçməli
Bu məqama qədər mənzərə artıq intuitiv olmalıdır, amma gəlin kiçik bir cədvəldə bunu rəsmiləşdirək.
| Xüsusiyyət | SSE (Server‑Sent Events) | HTTP‑stream (chunked) |
|---|---|---|
| İnisiator | Müştəri GET edir və abunə olur | Müştəri sorğu edir (GET/POST), server cavabı striminq edir |
| İstiqamət | Yalnız server → müştəri | Serverin konkret sorğuya cavabı |
| Semantika | Hadisə axınına abunə (pub/sub) | Bir sorğuya qismən cavab |
| Daxili protokol | Var (event:, data:, id: və s.) | Yox, formatı özünüz müəyyənləşdirirsiniz (sətirlər, NDJSON, JSON) |
| Müştəri API-si | EventSource | fetch + ReadableStream / response.body |
| Yenidən qoşulma dəstəyi | Daxili (EventSource, Last-Event-ID) | Əl ilə reallaşdırılmalıdır |
| Tipik ssenarilər | Proqres, statuslar, jobId üzrə bildirişlər | Mətnin striminqi, böyük JSON cavabları, LLM çıxışı |
Sadələşdirsək (ehtiyatla, ifrata varmadan):
- sizdə job və onun ətrafında çox hadisə varsa → SSE;
- bir tool çağırışı böyük nəticə qaytarırsa və onu hissə‑hissə göstərmək istəyirsinizsə → HTTP‑stream.
GiftGenius üçün bu o deməkdir: SSE — canlı proqres barı və seçim statusları üçün; HTTP‑strim — uzun mətn xülasələri və ya uzun hədiyyə siyahısını tədricən yükləmək üçün.
6. Bu, MCP və GiftGenius ilə necə uzlaşır
Mühazirənin əvvəlindəki sxemi xatırlayaq: model ↔ MCP ↔ vidjet ↔ backend. Artıq vidjet ↔ backend səviyyəsində axınlara baxdıq, indi bir addım geri qayıdaq və bu hekayədə MCP‑nin harada, “sadə HTTP”nin isə harada olduğunu dəqiq ayıraq.
MCP müəyyən edir ki, ChatGPT (MCP müştərisi kimi) MCP serverinizlə necə ünsiyyət qurur. Bunun üçün nəqliyyat var, burada:
- ChatGPT /sse SSE bağlantısı açır və bu vasitə ilə MCP mesajlarını alır (cavablar, bildirişlər, hadisələr);
- ChatGPT MCP sorğularını (call_tool, list_tools və s.) adətən /messages ünvanına POST kimi JSON‑RPC ilə göndərir.
GiftGenius‑u ChatGPT‑yə qoşanda bu səviyyədən artıq keçmisiniz.
İndi asinxron tapşırıqlar və vidjetdə UX axınları əlavə etdikdə, iki arxitektura variantı yaranır.
Birinci variant — “saf MCP”: MCP serveri job.progress və job.completed hadisələrini özü yaradır; ChatGPT onları MCP‑SSE vasitəsilə alır; sonra model vidjetinizi yenilənmiş kontekstlə çağırır, vidjet isə backend ilə birbaşa danışmadan proqresi render edir. Bu, MCP‑events üçün maksimal “kanonik” yoldur.
İkinci variant — hibrid: MCP aləti start_gift_job tapşırıq yaradır və jobId qaytarır; vidjet jobId alır və sonra backend ilə HTTP üzrə özü danışır, /api/gift-jobs/{jobId}/events SSE endpoint‑inə abunə olur və lazım gəlsə HTTP‑strim hesabatı istəyir. MCP tərəfində bu zaman xüsusi heç nə baş vermir.
Kursda biz hibrid yolu seçirik: o, App Router/Next‑ə daha yaxşı oturur və lokal sazlama üçün daha sadədir. Sonra isə “saf MCP bildirişlərinə” keçə bilərsiniz.
7. Yenidən qoşulma, taym‑autlar və şəbəkə reallıqları
Hələlik hər şey ideal kimi səslənirdi: SSE və ya strimi açdıq, hər şey axır, hadisələr gəlir, UX parıldayır. Reallıqda şəbəkə ən gözlənilməz anda bağlantıları qırmağı sevir, infrastruktur isə taymautlar qoyur.
Nələr səhv gedə bilər
SSE və HTTP-stream ilə gec-tez aşağıdakıları görəcəksiniz:
- proksidə idle taymautlar: “əgər bağlantı üzrə N saniyə heç nə gəlmirsə — bağlayırıq”;
- backend‑inizin yenidən işə salınması (deploy, qəza);
- istifadəçi tərəfində qeyri‑stabil şəbəkə (xüsusilə mobil şəbəkələrdə).
Bu normaldır; əsas odur ki, buna hazır olasınız, “bizi keçər” ümidinə arxalanmayasınız.
SSE üçün strategiya
Bu zonada SSE‑nin bir çox üstünlüyü var:
- EventSource müəyyən gecikmə ilə özü yenidən qoşulur;
- sizin id: və Last-Event-ID vasitəniz var ki, hadisələri tutub çatdırasınız.
Minimal praktik dəst:
- Serverdə əlaqə tam idle sayılmasın deyə vaxtaşırı nəsə göndərmək, məsələn heartbeat. Bu ayrıca event: ping hadisəsi və ya sadəcə : keep-alive şərhi ola bilər.
- Müştəri tərəfində onerror-da istifadəçiyə belə bir status göstərmək: “Bağlantıda problemlər, yenidən qoşulmağa çalışırıq…”, vidjeti sındırmamaq.
- Yenidən qoşulmada, əgər id: istifadə edirsinizsə, serverdən həmin ID‑dən sonrakı yeni hadisələri vermək. GiftGenius üçün ilkin mərhələdə ümumiyyətlə id: olmadan başlamaq və son gələn job.progress/job.completed əsasında vəziyyəti “yenidən qurmaq” olar.
HTTP‑stream üçün strategiya
HTTP‑strim — bu tək bir sorğudur, ona görə də qırılma baş verərsə, əslində yenidən başlamaq lazım gəlir:
- əgər mətn hesabatı striminq edirsinizsə, istifadəçiyə “Tam hesabatı almaq mümkün olmadı, yenidən cəhd edin” demək və hər şeyi yenidən başlamaq olar;
- əgər strukturlaşdırılmış məlumatları (NDJSON) striminq edirsinizsə, resume mexanizmini düşünmək olar: məsələn, sorğuda davam etmək üçün offset və ya cursor ötürmək.
Əvvəlcə çətinləşdirməmək də olar: cavab strimi sonadək çatmasa — nə gəlibsə onu göstəririk və “Hesabatın yaradılmasını davam et” düyməsi ilə yeni sorğu göndəririk.
Əsas odur ki, istifadəçini “sonsuz gözləmə” vəziyyətində qoymayasınız.
8. GiftGenius‑a tətbiq edirik: ssenari başlanğıcdan sonadək
İndi SSE, HTTP‑stream və MCP ilə iki arxitektura variantı haqqında danışdıqlarımızı birləşdirək — GiftGenius üzrə canlı ssenaridə: istifadəçi sorğusundan hazır hesabatadək.
İstifadəçi ChatGPT‑də yazır: “Stolüstü oyunlar fanatı üçün hədiyyə seç, büdcə 100 dollaradək”. Model GiftGenius‑u çağırmağa qərar verir. Tətbiq/agent sizin MCP serverinizdə start_gift_job tool‑call edir. Server:
- job‑u BD‑yə yazır;
- onu daxili növbəyə göndərir (növbələr və worker‑lər — növbəti mühazirə, hələlik “kimsə” onu yerinə yetirir kimi qəbul edək);
- tool‑call cavabında sinxron olaraq jobId qaytarır.
GiftGenius vidjeti ToolOutput-da jobId alır və komponenti render edir:
function GiftGeniusRoot({ jobId }: { jobId: string }) {
return (
<div>
<h2>Mükəmməl hədiyyələri axtarırıq...</h2>
<GiftJobProgress jobId={jobId} />
<GiftReport />
</div>
);
}
GiftJobProgress komponenti /api/gift-jobs/{jobId}/events SSE‑sinə abunə olur və proqresi çəkir. Hər job.progress faizləri yeniləyir, job.completed isə 100% qoyur və, mümkündür ki, “Ətraflı hesabatı göstər” düyməsini aktivləşdirir.
GiftReport komponenti düyməyə kliklə POST /api/gift-report (ora jobId ötürərək) göndərir və server HTTP‑strimi qaytardığı müddətdə mətn hesabatını tədricən göstərir.
SSE bağlantısı qırılanda vidjet yumşaq xəbərdarlıq göstərir, EventSource isə yenidən qoşulmağa çalışır. Hesabat strimi ilə problemlərdə istifadəçi hesabatın bir hissəsini görür və “Yaratmanı davam et” və ya “Yenidən cəhd et” düyməsini görür.
ChatGPT və MCP baxımından:
- MCP start_gift_job tool çağırışını və, ola bilsin, sonra job statusları ilə bağlı bildirişləri görür;
- Axınlar ətrafında UX əsasən vidjet ilə backend arasında HTTP səviyyəsində reallaşdırılıb.
9. SSE və HTTP‑stream ilə işləyərkən tipik xətalar
Səhv №1: SSE və HTTP‑stream‑i “eyni şey” hesab etmək.
Bəli, dərinlikdə hər ikisi üçün HTTP və chunked cavablar var, amma semantika çox fərqlidir. SSE — müstəqil hadisələrə abunədir, onlar istənilən vaxt gələ bilər və müştəri bunu əvvəlcədən bilmir. HTTP‑strim — konkret bir cavabdır, sadəcə zaman üzrə yayılıb. Əgər çoxsaylı jobId-lərə bir HTTP‑strim vasitəsilə abunə olmağa çalışsanız, baytlar üzərində öz protokolunuzu icad etməli olacaqsınız, faktiki olaraq SSE‑nin yarısını yenidən quracaqsınız.
Səhv №2: SSE‑nin avtoyenidən qoşulmasını görməzdən gəlmək və idempotentlik barədə düşünməmək.
Çoxları “sadə” SSE serveri yazır: data: ... göndərirlər, amma nə standart id: ( Last-Event-ID üçün ), nə də hadisə daxilində tətbiqi event_id əlavə edirlər. Sonra, ilk bağlantı qırılması və yenidən qoşulmada hadisə dublikatları artır. Düşünülmüş event_id və “bu hadisəni artıq gördüm” məntiqi olmadan müştəri emalı iki dəfə vəziyyəti yeniləyə, eyni job.completed-i iki dəfə göstərə və ya daha pisi, iki dəfə pul silə/bonus yazdırmaqla nəticələnə bilər.
Səhv №3: worker‑in hər xırda dəyişikliklərini ayrı SSE hadisəsi kimi göndərmək.
Əgər tapşırığın proqresini hər millisaniyədə SSE ilə göndərirsinizsə, çox güman ki, şəbəkəni və müştərini yükləyəcəksiniz, istifadəçini isə sevindirməyəcəksiniz. Daha ağlabatanı yeniləmələri aqreqasiya etmək və proqresi, məsələn, 200–500 ms arayla və ya mərhələ dəyişəndə göndərməkdir. Throttling və backpressure mövzularını ayrıca müzakirə edəcəyik, amma bu mərhələdə belə hadisə tezliyi barədə düşünmək lazımdır.
Səhv №4: açıq format olmadan HTTP‑strim üzərində mürəkkəb protokollar qurmaq.
Tipik anti‑pattern: ayırıcılar olmadan JSON striminq etmək və bir obyektin bitib digərin başlayacağını “tahmin” etmək. Və ya eyni axında mətnlə JSON‑u qarışdırmaq. Ən yaxşı yol — sadə və anlaşılan format seçmək: sətir‑sətir mətn, və ya NDJSON (hər sətirdə bir JSON obyekt), və ya açıq ayırıcılar. Onda müştəri tərəfində parser ağlabatan qalacaq.
Səhv №5: taymautları unutmaq və “əbədi” strimlər etmək.
Bəzən inkişafçılar SSE endpoint‑ləri edirlər ki, 5–10 dəqiqə heç nə göndərmir, sonra isə istifadəçidən serverə yol boyu (balanslaşdırıcılar, API şlüzlər, korporativ proksilər) bağlantıların qırılmasına təəccüblənirlər. Müntəzəm heartbeat hadisələri və ya şərhlər bağlantını “canlı” saxlamağa və qırılmaları vaxtında aşkar etməyə imkan verir. HTTP‑strimlər isə sonsuz cavablara çevrilməməlidir — əbədi abunəliklər üçün SSE var.
Səhv №6: normal hadisələr əvəzinə HTTP‑strimlə mürəkkəb pub/sub qurmağa çalışmaq.
Bəzən belə bir cazibə yaranır: “Bir strim açarıq, onunla həm proqresi, həm qismən nəticələri, həm də təsadüfi logları göndərərik”. Nəticədə müştəri tərəfində hər çankı təhlil edən və onun hansı jobId-ə aid olduğunu qərar verən mürəkkəb multiplekser yaranır. Çox hallarda job.progress, job.completed tipli hadisələrlə və hər job üçün ayrı kanal ilə SSE istifadə etmək, HTTP‑strim üzərində özəl “nəhəng protokol” icad etməkdən daha sadə və etibarlıdır.
Səhv №7: UX‑i axının “heç vaxt düşməyəcəyinə” möhkəm bağlamaq.
Hər bir axın nə vaxtsa qırılacaq. Əgər vidjetiniz bu zaman sadəcə sonsuz animasiyalı proqres barı ilə qalıb heç bir hərəkət imkanı təqdim etmirsə — UX “sınmış” kimi qəbul olunur. Hətta sadəcə “Görünür, əlaqə kəsildi. Hədiyyə seçimini yenidən başladın” mesajı və “Təkrarla” düyməsi susqunluqdan qat‑qat yaxşıdır.
GO TO FULL VERSION