मैं tRPC का बहुत बड़ा प्रशंसक हूं। सर्वर से प्रकार निर्यात करने और उन्हें क्लाइंट में आयात करने का विचार, दोनों के बीच एक प्रकार-सुरक्षित अनुबंध के लिए, यहां तक कि संकलन-समय चरण के बिना, बस शानदार है। टीआरपीसी में शामिल सभी लोगों के लिए, आप अद्भुत काम कर रहे हैं।
उस ने कहा, जब मैं tRPC और ग्राफकलाइन के बीच तुलना देख रहा हूं, ऐसा लगता है जैसे हम सेब और संतरे की तुलना कर रहे हैं।
यह विशेष रूप से स्पष्ट हो जाता है जब आप ग्राफक्यूएल और टीआरपीसी के आसपास सार्वजनिक प्रवचन को देखते हैं। उदाहरण के लिए थियो द्वारा इस आरेख को देखें:
थियो ने इस आरेख को गहराई से समझाया और पहली नज़र में, यह बहुत मायने रखता है। tRPC को संकलन-समय चरण की आवश्यकता नहीं है, डेवलपर अनुभव अविश्वसनीय है, और यह ग्राफ़िकल की तुलना में बहुत सरल है।
लेकिन क्या वास्तव में पूरी तस्वीर यही है, या यह सादगी किसी और चीज की कीमत पर हासिल की गई है? आइए tRPC और GraphQL दोनों के साथ एक सरल ऐप बनाकर पता करें।
आइए समाचार फ़ीड के लिए एक पृष्ठ, फ़ीड सूची के लिए एक घटक और फ़ीड आइटम के लिए एक घटक के साथ एक फ़ाइल ट्री की कल्पना करें।
src/pages/news-feed ├── NewsFeed.tsx ├── NewsFeedList.tsx └── NewsFeedItem.tsx
फ़ीड पेज के सबसे ऊपर, हमें उपयोगकर्ता के बारे में कुछ जानकारी, सूचनाएं, अपठित संदेश आदि की आवश्यकता होती है।
फ़ीड सूची को प्रस्तुत करते समय, हमें फ़ीड आइटम की संख्या जानने की आवश्यकता होती है, यदि कोई अन्य पृष्ठ है, और उसे कैसे लाया जाए।
फ़ीड आइटम के लिए, हमें लेखक, सामग्री, पसंद की संख्या, और यदि उपयोगकर्ता ने इसे पसंद किया है, जानने की आवश्यकता है।
यदि हम tRPC का उपयोग करते हैं, तो हम इस सभी डेटा को एक बार में लोड करने के लिए एक "प्रक्रिया" बनाएंगे। हम इस प्रक्रिया को पृष्ठ के शीर्ष पर कॉल करेंगे और फिर डेटा को घटकों तक प्रचारित करेंगे।
हमारा फ़ीड घटक कुछ ऐसा दिखाई देगा:
import { trpc } from '../utils/trpc' export function NewsFeed() { const feed = trpc.newsFeed.useQuery() return ( <div> <Avatar>{feed.user}</Avatar> <UnreadMessages> {feed.unreadMessages} unread messages </UnreadMessages> <Notifications> {feed.notifications} notifications </Notifications> <NewsFeedList feed={feed} /> </div> ) }
अगला, आइए फ़ीड सूची घटक देखें:
export function NewsFeedList({ feed }) { return ( <div> <h1>News Feed</h1> <p>There are {feed.items.length} items</p> {feed.items.map((item) => ( <NewsFeedItem item={item} /> ))} {feed.hasNextPage && ( <button onClick={feed.fetchNextPage}>Load more</button> )} </div> ) }
और अंत में, फ़ीड आइटम घटक:
export function NewsFeedItem({ item }) { return ( <div> <h2>{item.author.name}</h2> <p>{item.content}</p> <button onClick={item.like}>Like</button> </div> ) }
ध्यान रखें, हम अभी भी एक ही टीम हैं, यह सभी टाइपस्क्रिप्ट, एक ही कोडबेस है, और हम अभी भी tRPC का उपयोग कर रहे हैं।
आइए जानें कि पेज को रेंडर करने के लिए हमें वास्तव में किस डेटा की आवश्यकता है। हमें उपयोगकर्ता, अपठित संदेश, सूचनाएं, फ़ीड आइटम, फ़ीड आइटम की संख्या, अगला पृष्ठ, लेखक, सामग्री, पसंद की संख्या और यदि उपयोगकर्ता ने इसे पसंद किया है, की आवश्यकता है।
इन सब के बारे में हमें विस्तृत जानकारी कहाँ से मिल सकती है? अवतार के लिए डेटा आवश्यकताओं को समझने के लिए, हमें Avatar
घटक को देखने की जरूरत है। अपठित संदेशों और सूचनाओं के लिए घटक होते हैं, इसलिए हमें उन पर भी ध्यान देने की आवश्यकता है। फ़ीड सूची घटक को आइटमों की संख्या, अगला पृष्ठ और फ़ीड आइटम की आवश्यकता होती है। फ़ीड आइटम घटक में प्रत्येक सूची आइटम के लिए आवश्यकताएँ होती हैं।
कुल मिलाकर, यदि हम इस पृष्ठ के लिए डेटा आवश्यकताओं को समझना चाहते हैं, तो हमें 6 अलग-अलग घटकों को देखने की आवश्यकता है। उसी समय, हम वास्तव में नहीं जानते कि वास्तव में प्रत्येक घटक के लिए कौन से डेटा की आवश्यकता है। प्रत्येक घटक के लिए यह घोषित करने का कोई तरीका नहीं है कि उसे किस डेटा की आवश्यकता है क्योंकि tRPC के पास ऐसी कोई अवधारणा नहीं है।
ध्यान रखें कि यह केवल एक पृष्ठ है। क्या होता है यदि हम समान लेकिन थोड़े भिन्न पृष्ठ जोड़ते हैं?
मान लीजिए कि हम समाचार फ़ीड का एक प्रकार बना रहे हैं, लेकिन नवीनतम पोस्ट दिखाने के बजाय, हम सबसे लोकप्रिय पोस्ट दिखा रहे हैं।
हम कमोबेश उन्हीं घटकों का उपयोग कर सकते हैं, केवल कुछ परिवर्तनों के साथ। मान लीजिए कि लोकप्रिय पोस्ट में विशेष बैज होते हैं जिनके लिए अतिरिक्त डेटा की आवश्यकता होती है।
क्या हमें इसके लिए कोई नई प्रक्रिया बनानी चाहिए? या शायद हम मौजूदा प्रक्रिया में कुछ और क्षेत्र जोड़ सकते हैं?
यदि हम अधिक से अधिक पृष्ठ जोड़ रहे हैं तो क्या यह दृष्टिकोण अच्छा है? क्या यह उस समस्या की तरह नहीं है जो हमें REST API के साथ हुई है? हमें इन समस्याओं के लिए प्रसिद्ध नाम भी मिले हैं, जैसे ओवरफेटिंग और अंडरफेटिंग, और हम उस बिंदु तक भी नहीं पहुंचे हैं जहां हम एन+1 समस्या के बारे में बात कर रहे हैं।
किसी बिंदु पर हम प्रक्रिया को एक मूल प्रक्रिया और कई उप-प्रक्रियाओं में विभाजित करने का निर्णय ले सकते हैं। क्या होगा यदि हम रूट स्तर पर एक सरणी प्राप्त कर रहे हैं, और फिर सरणी में प्रत्येक आइटम के लिए, हमें अधिक डेटा प्राप्त करने के लिए एक और प्रक्रिया को कॉल करना होगा?
एक और खुलापन हमारी प्रक्रिया के प्रारंभिक संस्करण के लिए तर्क प्रस्तुत करना हो सकता है, उदाहरण के लिए trpc.newsFeed.useQuery({withPopularBadges: true})
।
यह काम करेगा, लेकिन ऐसा लगता है कि हम ग्राफक्यूएल की सुविधाओं का फिर से आविष्कार करना शुरू कर रहे हैं।
अब, इसकी तुलना ग्राफक्यूएल से करते हैं। ग्राफ़िकल में फ़्रैगमेंट की अवधारणा है, जो हमें प्रत्येक घटक के लिए डेटा आवश्यकताओं की घोषणा करने की अनुमति देती है। रिले जैसे क्लाइंट आपको पृष्ठ के शीर्ष पर एक एकल ग्राफ़क्यूएल क्वेरी घोषित करने की अनुमति देते हैं, और बाल घटकों से अंशों को क्वेरी में शामिल करते हैं।
इस तरह, हम अभी भी पृष्ठ के शीर्ष पर एक एकल प्राप्त कर रहे हैं, लेकिन रूपरेखा वास्तव में प्रत्येक घटक के लिए डेटा आवश्यकताओं को घोषित करने और एकत्र करने में हमारा समर्थन करती है।
आइए इसी उदाहरण को ग्राफक्यूएल, फ्रैगमेंट्स और रिले का उपयोग करते हुए देखें। आलस्य कारणों से, कोड 100% सही नहीं है क्योंकि मैं इसे लिखने के लिए कोपिलॉट का उपयोग कर रहा हूं, लेकिन यह वास्तविक ऐप में दिखने के बहुत करीब होना चाहिए।
import { graphql } from 'react-relay' export function NewsFeed() { const feed = useQuery(graphql` query NewsFeedQuery { user { ...Avatar_user } unreadMessages { ...UnreadMessages_unreadMessages } notifications { ...Notifications_notifications } ...NewsFeedList_feed } `) return ( <div> <Avatar user={feed.user} /> <UnreadMessages unreadMessages={feed.unreadMessages} /> <Notifications notifications={feed.notifications} /> <NewsFeedList feed={feed} /> </div> ) }
अगला, आइए फ़ीड सूची घटक देखें। फ़ीड सूची घटक अपने लिए एक फ़्रैगमेंट घोषित करता है, और फ़ीड आइटम घटक के लिए फ़्रैगमेंट शामिल करता है।
import { graphql } from 'react-relay' export function NewsFeedList({ feed }) { const list = useFragment( graphql` fragment NewsFeedList_feed on NewsFeed { items { ...NewsFeedItem_item } hasNextPage } `, feed ) return ( <div> <h1>News Feed</h1> <p>There are {feed.items.length} items</p> {feed.items.map((item) => ( <NewsFeedItem item={item} /> ))} {feed.hasNextPage && ( <button onClick={feed.fetchNextPage}>Load more</button> )} </div> ) }
और अंत में, फ़ीड आइटम घटक:
import { graphql } from 'react-relay' export function NewsFeedItem({ item }) { const item = useFragment( graphql` fragment NewsFeedItem_item on NewsFeedItem { author { name } content likes hasLiked } `, item ) return ( <div> <h2>{item.author.name}</h2> <p>{item.content}</p> <button onClick={item.like}>Like</button> </div> ) }
इसके बाद, फ़ीड आइटम पर लोकप्रिय बैज के साथ समाचार फ़ीड का एक रूपांतर बनाते हैं। हम समान घटकों का पुन: उपयोग कर सकते हैं, क्योंकि हम लोकप्रिय बैज खंड को सशर्त रूप से शामिल करने के लिए @include
निर्देश का उपयोग करने में सक्षम हैं।
import { graphql } from 'react-relay' export function PopularNewsFeed() { const feed = useQuery(graphql` query PopularNewsFeedQuery($withPopularBadges: Boolean!) { user { ...Avatar_user } unreadMessages { ...UnreadMessages_unreadMessages } notifications { ...Notifications_notifications } ...NewsFeedList_feed } `) return ( <div> <Avatar user={feed.user} /> <UnreadMessages unreadMessages={feed.unreadMessages} /> <Notifications notifications={feed.notifications} /> <NewsFeedList feed={feed} /> </div> ) }
इसके बाद, देखते हैं कि अपडेट की गई फ़ीड सूची आइटम कैसा दिख सकता है:
import { graphql } from 'react-relay' export function NewsFeedItem({ item }) { const item = useFragment( graphql` fragment NewsFeedItem_item on NewsFeedItem { author { name } content likes hasLiked ...PopularBadge_item @include(if: $withPopularBadges) } `, item ) return ( <div> <h2>{item.author.name}</h2> <p>{item.content}</p> <button onClick={item.like}>Like</button> {item.popularBadge && <PopularBadge badge={item.popularBadge} />} </div> ) }
जैसा कि आप देख सकते हैं, ग्राफक्यूएल काफी लचीला है और हमें बहुत अधिक कोड डुप्लिकेट किए बिना एक ही पृष्ठ की विविधताओं सहित जटिल वेब एप्लिकेशन बनाने की अनुमति देता है।
इसके अलावा, ग्राफक्यूएल फ़्रैगमेंट हमें प्रत्येक घटक के लिए डेटा आवश्यकताओं को स्पष्ट रूप से घोषित करने की अनुमति देता है, जो तब पृष्ठ के शीर्ष तक फहराया जाता है, और फिर एक ही अनुरोध में प्राप्त किया जाता है।
tRPC का महान डेवलपर अनुभव दो अलग-अलग चिंताओं को एक अवधारणा, एपीआई कार्यान्वयन और डेटा खपत में विलय करके प्राप्त किया जाता है।
यह समझना महत्वपूर्ण है कि यह एक व्यापार-बंद है। कोई मुफ्त लंच नहीं है। टीआरपीसी की सरलता लचीलेपन की कीमत पर आती है।
ग्राफक्यूएल के साथ, आपको स्कीमा डिज़ाइन में बहुत अधिक निवेश करना होगा, लेकिन यह निवेश उस क्षण का भुगतान करता है जब आपको अपने आवेदन को कई लेकिन संबंधित पृष्ठों पर स्केल करना होता है।
एपीआई कार्यान्वयन को डेटा लाने से अलग करके अलग-अलग उपयोग के मामलों के लिए एक ही एपीआई कार्यान्वयन का पुन: उपयोग करना बहुत आसान हो जाता है।
एपीआई बनाते समय विचार करने के लिए एक और महत्वपूर्ण पहलू है। आप एक आंतरिक एपीआई के साथ शुरुआत कर सकते हैं जो विशेष रूप से आपके स्वयं के फ्रंटएंड द्वारा उपयोग किया जाता है, और tRPC इस उपयोग के मामले के लिए बहुत उपयुक्त हो सकता है।
लेकिन आपके प्रयास के भविष्य के बारे में क्या? क्या संभावना है कि आप अपनी टीम बढ़ा रहे होंगे? क्या यह संभव है कि अन्य दल, या यहाँ तक कि तृतीय पक्ष भी आपके API का उपभोग करना चाहेंगे?
REST और GraphQL दोनों को सहयोग को ध्यान में रखकर बनाया गया है। सभी टीमें टाइपस्क्रिप्ट का उपयोग नहीं करेंगी, और यदि आप कंपनी की सीमाओं को पार कर रहे हैं, तो आप एपीआई को इस तरह से उजागर करना चाहेंगे जो समझने और उपभोग करने में आसान हो।
REST और GraphQL API को एक्सपोज़ करने और दस्तावेज़ करने के लिए बहुत सारे टूल हैं, जबकि tRPC स्पष्ट रूप से इस उपयोग के मामले के लिए डिज़ाइन नहीं किया गया है।
इसलिए, जबकि tRPC के साथ शुरुआत करना बहुत अच्छा है, आप इसे किसी बिंदु पर आगे बढ़ने की संभावना रखते हैं, जो मुझे लगता है कि थियो ने अपने एक वीडियो में भी उल्लेख किया है।
tRPC API से एक OpenAPI विनिर्देश उत्पन्न करना निश्चित रूप से संभव है, टूलिंग मौजूद है, लेकिन यदि आप एक ऐसा व्यवसाय बना रहे हैं जो अंततः API को तृतीय पक्षों के सामने लाने पर निर्भर करेगा, तो आपके RPC अच्छी तरह से डिज़ाइन किए गए REST और के विरुद्ध प्रतिस्पर्धा करने में सक्षम नहीं होंगे। ग्राफक्यूएल एपीआई।
जैसा कि शुरुआत में कहा गया था, मैं tRPC के पीछे के विचारों का बहुत बड़ा प्रशंसक हूं। यह सही दिशा में उठाया गया एक अच्छा कदम है, जिससे डेटा प्राप्त करना सरल और अधिक डेवलपर के अनुकूल हो जाता है।
दूसरी ओर ग्राफक्यूएल, फ्रैगमेंट और रिले शक्तिशाली उपकरण हैं जो आपको जटिल वेब एप्लिकेशन बनाने में मदद करते हैं। उसी समय, सेटअप काफी जटिल है और सीखने के लिए कई अवधारणाएँ हैं जब तक आप इसे लटका नहीं लेते।
जबकि tRPC आपको जल्दी से शुरू कर देता है, यह बहुत संभावना है कि आप किसी बिंदु पर इसकी वास्तुकला को पार कर लेंगे। यदि आप आज ग्राफ़कॉल या tRPC पर दांव लगाने का निर्णय ले रहे हैं, तो आपको इस बात पर ध्यान देना चाहिए कि आप अपनी परियोजना को कहाँ तक जाते हुए देखते हैं। भविष्य। डेटा लाने की आवश्यकताएं कितनी जटिल होंगी? क्या आपके एपीआई का उपभोग करने वाली कई टीमें होंगी? क्या आप अपने एपीआई को तीसरे पक्ष के सामने उजागर करेंगे?
उस सब के साथ, क्या होगा अगर हम दोनों दुनिया के सर्वश्रेष्ठ को जोड़ सकें? एक एपीआई क्लाइंट कैसा दिखेगा जो टीआरपीसी की सादगी को ग्राफक्यूएल की शक्ति के साथ जोड़ता है? क्या हम शुद्ध टाइपस्क्रिप्ट एपीआई क्लाइंट का निर्माण कर सकते हैं जो हमें टीआरपीसी की सादगी के साथ संयुक्त रूप से फ़्रैगमेंट और रिले की शक्ति प्रदान करता है?
कल्पना कीजिए कि हम tRPC के विचारों को लेते हैं और उन्हें उस चीज़ के साथ जोड़ते हैं जो हमने ग्राफक्यूएल और रिले से सीखा है।
यहाँ एक छोटा सा पूर्वावलोकन है:
// src/pages/index.tsx import { useQuery } from '../../.wundergraph/generated/client' import { Avatar_user } from '../components/Avatar' import { UnreadMessages_unreadMessages } from '../components/UnreadMessages' import { Notifications_notifications } from '../components/Notifications' import { NewsFeedList_feed } from '../components/NewsFeedList' export function NewsFeed() { const feed = useQuery({ operationName: 'NewsFeed', query: (q) => ({ user: q.user({ ...Avatar_user.fragment, }), unreadMessages: q.unreadMessages({ ...UnreadMessages_unreadMessages.fragment, }), notifications: q.notifications({ ...Notifications_notifications.fragment, }), ...NewsFeedList_feed.fragment, }), }) return ( <div> <Avatar /> <UnreadMessages /> <Notifications /> <NewsFeedList /> </div> ) } // src/components/Avatar.tsx import { useFragment, Fragment } from '../../.wundergraph/generated/client' export const Avatar_user = Fragment({ on: 'User', fragment: ({ name, avatar }) => ({ name, avatar, }), }) export function Avatar() { const data = useFragment(Avatar_user) return ( <div> <h1>{data.name}</h1> <img src={data.avatar} /> </div> ) } // src/components/NewsFeedList.tsx import { useFragment, Fragment } from '../../.wundergraph/generated/client' import { NewsFeedItem_item } from './NewsFeedItem' export const NewsFeedList_feed = Fragment({ on: 'NewsFeed', fragment: ({ items }) => ({ items: items({ ...NewsFeedItem_item.fragment, }), }), }) export function NewsFeedList() { const data = useFragment(NewsFeedList_feed) return ( <div> {data.items.map((item) => ( <NewsFeedItem item={item} /> ))} </div> ) } // src/components/NewsFeedItem.tsx import { useFragment, Fragment } from '../../.wundergraph/generated/client' export const NewsFeedItem_item = Fragment({ on: 'NewsFeedItem', fragment: ({ id, author, content }) => ({ id, author, content, }), }) export function NewsFeedItem() { const data = useFragment(NewsFeedItem_item) return ( <div> <h1>{data.title}</h1> <p>{data.content}</p> </div> ) }
तुम क्या सोचते हो? क्या आप ऐसा कुछ प्रयोग करेंगे? क्या आप घटक स्तर पर डेटा निर्भरताओं को परिभाषित करने में मूल्य देखते हैं, या क्या आप पृष्ठ स्तर पर दूरस्थ प्रक्रियाओं को परिभाषित करने के साथ रहना पसंद करते हैं? मुझे आपके विचार सुनना अच्छा लगेगा...
हम वर्तमान में रिएक्ट, नेक्स्टजेएस और अन्य सभी रूपरेखाओं के लिए सबसे अच्छा डेटा लाने का अनुभव बनाने के लिए डिजाइन चरण में हैं। यदि आप इस विषय में रुचि रखते हैं, तो अद्यतित रहने के लिए मुझे ट्विटर पर फॉलो करें।
यदि आप चर्चा में शामिल होना चाहते हैं और हमारे साथ RFC पर चर्चा करना चाहते हैं, तो बेझिझक हमारे डिस्कॉर्ड सर्वर से जुड़ें।
अगला