Təsəvvür edin: title sahəsinin adını heading etdiniz — həm verilənlər bazasında, həm də serverdə. Server işləyir, backend testləri yaşıldır, amma istifadəçilərin tətbiqində qeydlərin başlıqları yoxa çıxıb: frontend hələ də note.title oxuyur. Xətanı kompilyator yox, istifadəçi tapdı. Bu dərsdə tRPC-nin belə xətaları «istifadəçi tapdı» vəziyyətindən «redaktorda qırmızı dalğalı xətt» vəziyyətinə necə çevirdiyini araşdıracağıq — həm də sxem faylı və ya kod generasiyası olmadan.
Problem: frontend ilə backend arasındakı sərhəd
Frontend və backend şəbəkə üzərindən ünsiyyət qurur, şəbəkədə isə sadəcə mətn — JSON — gedir. TypeScript bu JSON haqqında heç nə bilmir: kompilyasiya zamanı serveri görmür, server işləyəndə isə artıq heç bir tip qalmır. Ona görə adi REST tətbiqində cavabın tipini sadəcə özümüzə söz veririk:
// REST: the response type is only a promise
type Note = { id: string; title: string };
const res = await fetch('/api/notes');
const notes = (await res.json()) as Note[]; // nobody checks this
console.log(notes[0].title); // undefined, if the server renamed the fieldas Note[] yazısı yoxlama deyil, sözdür: «bilirəm ki, server məhz belə cavab göndərəcək». Server dəyişsə, TypeScript bunu görməyəcək, notes[0].title isə işləyərkən undefined olacaq.
Bu boşluğu bağlamaq üçün komandalar iki məşhur həll fikirləşib:
- OpenAPI və kod generasiyası (REST). API
openapi.yamlfaylında təsvir olunur, xüsusi proqram — generator — isə həmin fayla görə müştəri üçün tiplər yaradır. - GraphQL sxemi və kod generasiyası. Sxem — GraphQL modulundan tanıdığınız həmin müqavilədir; müştəri tipləri yenə ondan generasiya olunur.
Hər iki yanaşmanın qiyməti var: serverin koduna əlavə olaraq daha bir artefakt (spesifikasiya və ya sxem) yazıb saxlamaq və generatoru işə salmağı unutmamaq lazımdır. Bəs müqavilə serverin koduna çevrilsə?
RPC ideyası: başqa kompüterdəki funksiyanı çağırmaq
RPC (Remote Procedure Call, «uzaq prosedurun çağırılması») köhnə ideyadır: başqa kompüterdə yaşayan funksiyanı elə çağırmaq ki, sanki o, kodunuzda, yanınızdadır.
Bənzətmə: çayxanada siz aşpaza məktub yazıb çayı necə dəmləmək lazım olduğunu izah etmirsiniz. Ofisiantə «bir çay, zəhmət olmasa» deyirsiniz — və bir stəkan çay alırsınız. Ofisiant RPC kitabxanasıdır: o, sizin «funksiya çağırışınızı» mətbəxə (serverə) çatdırır və nəticəni geri gətirir.
REST isə başqa cür qurulub: o, resurslar və HTTP metodları ətrafında dayanır (GET /notes/n2, POST /notes), yəni isimlər ətrafında. RPC isə əməllər, yəni fellər ətrafında qurulur: note.byId, note.create. tRPC — TypeScript RPC-dir: serverin procedure-lərini adi funksiya kimi çağırırsınız, TypeScript isə bu çağırışları yoxlayır.
Burada sehr yoxdur: arxa planda tanış HTTP və JSON işləyir. Müştəri trpc.note.byId.query({ id: 'n2' }) çağıranda şəbəkədə əslində belə sorğu gedir:
GET /api/trpc/note.byId?batch=1&input={"0":{"id":"n2"}} HTTP/1.1
Host: localhost:3000Və serverin cavabı:
HTTP/1.1 200 OK
Content-Type: application/json