રીઅલ-ટાઇમ કોલેબોરેશન (Real-time collaboration) જ્યાં સુધી તમે પડદા પાછળની પ્રક્રિયા ન જુઓ ત્યાં સુધી ખૂબ જ સરળ લાગે છે. એક વ્યક્તિ ટાઇપ કરે છે. બીજી વ્યક્તિ ત્રણ ફકરા ઉપરની એક લાઇન ડિલીટ કરે છે. ત્રીજી વ્યક્તિ Stack Overflow માંથી એક સ્નિપેટ (snippet) પેસ્ટ કરે છે. કોઈક રીતે દસ્તાવેજ એક સિંગલ, સુસંગત સ્થિતિમાં સ્થિર થાય છે. WebSockets અથવા ડિસ્ટ્રિબ્યુટેડ સ્ટેટ (distributed state) માં કોઈ અગાઉના અનુભવ વગર, શૂન્યથી આ પ્રકારની સરળતા બનાવવી જોખમી લાગે છે. પરંતુ ખરેખર શીખવા માટે આ જ સાચી રીત લાગે છે.
આ પ્રોજેક્ટ શૂન્યથી શરૂ થાય છે. કોઈ ઉધાર લીધેલું બૉઇલરપ્લેટ (boilerplate) નથી. કોઈ ચમકતા YouTube વોકથ્રુ (walkthroughs) નથી જેમાં ૩૦ સેકન્ડના મોન્ટેજમાં અઘરા ભાગોને છોડી દેવામાં આવ્યા હોય. લક્ષ્ય એક કોલેબોરેટિવ કોડ એડિટર બનાવવાનું છે જ્યાં અનેક વપરાશકર્તાઓ એકસાથે એક જ ફાઇલ એડિટ કરી શકે, અને એકબીજાના ફેરફારો—અને એકબીજાના કર્સર્સ (cursors)—જેમ જેમ થાય તેમ જોઈ શકે. ત્યાં પહોંચવા માટે ટ્રાન્સપોર્ટ લેયર્સ (transport layers), કન્સિસ્ટન્સી મોડલ્સ (consistency models), અને દસ્તાવેજને બગાડ્યા વિના એકસાથે થતા એડિટ્સને મર્જ (merge) કરવાની અઘરી સમસ્યાનો ઉકેલ લાવવો પડશે.
"રીઅલ-ટાઇમ" (Real-Time) નો ખરેખર અર્થ શું છે
મોટાભાગની વેબ એપ્લિકેશન્સ રિક્વેસ્ટ-રિસ્પોન્સ સાયકલ (request-response cycles) સાથે કામ કરવામાં આરામદાયક હોય છે. તમે ફોર્મ સબમિટ કરો છો, સર્વર તેને સેવ કરે છે, અને તમે પેજ રિફ્રેશ કરો છો. રીઅલ-ટાઇમ કોલેબોરેશન આ કરારને સંપૂર્ણપણે તોડી નાખે છે. દરેક કીસ્ટ્રોક (keystroke) એક એવી ઇવેન્ટ છે જે અન્ય તમામ કનેક્ટેડ ક્લાયન્ટ્સ સુધી પહોંચવી જોઈએ, સામાન્ય રીતે મિલીસેકન્ડમાં, અને અર્થ જળવાઈ રહે તેવા ક્રમમાં આવવી જોઈએ.
અહીં WebSockets એ ટ્રાન્સપોર્ટ માટે સ્પષ્ટ પસંદગી છે કારણ કે તેઓ ક્લાયન્ટ અને સર્વર વચ્ચે સતત, ફૂલ-ડુપ્લેક્સ (full-duplex) કનેક્શન જાળવી રાખે છે. HTTP polling થી વિપરીત, જે દર થોડી સેકન્ડે "કંઈ નવું છે?" પૂછીને બેન્ડવિડ્થ વેડફે છે, એક WebSocket ખુલ્લું રહે છે. જ્યારે યુઝર A સેમીકોલન (semicolon) ટાઇપ કરે છે, ત્યારે તે કેરેક્ટર એક મેસેજ બની જાય છે જે સોકેટ દ્વારા સેન્ટ્રલ સર્વર પર જાય છે, અને પછી યુઝર B અને C સુધી પહોંચે છે. આ ભાગ પ્રમાણમાં સરળ છે.
અઘરો ભાગ એ છે કે જ્યારે B અને C બરાબર એક જ સમયે ટાઇપ કરે ત્યારે શું થાય. જો બંને ફેરફારો લગભગ એકસાથે સર્વર પર પહોંચે, તો કોણ જીતશે? જો તમે ફક્ત આગમનના ક્રમમાં મેસેજ બ્રોડકાસ્ટ કરો છો, તો કેરેક્ટર્સ ગુમાવવાનું અથવા લખાણ અસ્તવ્યસ્ત થવાનું જોખમ રહે છે. સાદી 'લાસ્ટ-રાઈટ-વિન્સ' (last-write-wins) વ્યૂહરચનાઓ નિષ્ફળ જાય છે કારણ કે તેઓ હેતુ (intent) ને અવગણે છે. જો હું લાઇન એકની શરૂઆતમાં "hello" ટાઇપ કરું અને તમે લાઇન એકની શરૂઆતમાં "world" ટાઇપ કરો, તો પરિણામ એવું ન હોવું જોઈએ કે આપણી વચ્ચે અથડામણ થાય અને આપણામાંથી એકનું લખાણ ભૂંસાઈ જાય. તે "helloworld" અથવા "worldhello" હોવું જોઈએ, જે નિશ્ચિત રીતે (deterministically) પસંદ કરવામાં આવ્યું હોય. તે પ્રાપ્ત કરવા માટે એવી સિંક્રનાઇઝેશન વ્યૂહરચનાની જરૂર છે જે દસ્તાવેજના માળખાને સમજી શકે.
શૂન્યથી શરૂઆત કરવી કેમ મહત્વની છે
આ જટિલતાને છુપાવતા ઉત્તમ ફ્રેમવર્ક ઉપલબ્ધ છે. Yjs, Automerge, અને Socket.IO આ મુશ્કેલીઓને દૂર કરી શકે છે અને એક બપોરના સમયમાં કાર્યરત પ્રોટોટાઇપ બનાવી શકે છે. પરંતુ તેની નીચેના મૂળભૂત સિદ્ધાંતો (primitives) ને સમજ્યા વિના તેનો ઉપયોગ કરવો એ સાધનો કેવી રીતે વાંચવા તે જાણ્યા વિના ઓટોપાયલટ પર વિમાન ઉડાડવા જેવું છે. જ્યારે ટર્બ્યુલન્સ (turbulence) આવે—અને ડિસ્ટ્રિબ્યુટેડ સિસ્ટમ્સમાં તે હંમેશા આવે જ છે—ત્યારે તમારે જાણવું જરૂરી છે કે સમસ્યા તમારા નેટવર્ક લેયર (network layer) માં છે, તમારા કોન્ફ્લિક્ટ રિઝોલ્યુશન (conflict resolution) માં છે, કે તમારા ડેટા મોડેલમાં છે.
અહીં પ્રતિબદ્ધતા લાઇબ્રેરીઓ પર આધાર રાખતા પહેલા ખ્યાલો શીખવાની છે. તેનો અર્થ એ છે કે નીચેની પરિસ્થિતિઓમાં શું થાય છે તે મેન્યુઅલી (manually) સમજવું:
- એક ક્લાયન્ટ કીસ્ટ્રોક દરમિયાન ડિસ્કનેક્ટ થઈ જાય અને દસ સેકન્ડ પછી ફરીથી કનેક્ટ થાય
- બે વપરાશકર્તાઓ એકસાથે એક જ કર્સર પોઝિશન પર ટેક્સ્ટ ઇન્સર્ટ કરે
- એક વપરાશકર્તા એ બ્લોક ડિલીટ કરે જેને બીજો વપરાશકર્તા સક્રિય રીતે એડિટ કરી રહ્યો હોય
- સર્વર ક્રેશ થાય અને નવા નોડ (node) એ દસ્તાવેજની સ્થિતિને શૂન્યથી ફરીથી બનાવવી પડે
Operational Transformation (OT) અને Conflict-free Replicated Data Types (CRDTs) એ આ સમસ્યાઓ માટેના બે મુખ્ય ઉકેલો છે. Google Docs એ તેના પ્રારંભિક આર્કિટેક્ચર માટે OT નો ઉપયોગ કર્યો હતો, જેમાં ઓપરેશન્સ લાગુ કરતા પહેલા તેને એકબીજા સામે રૂપાંતરિત કરવા માટે સેન્ટ્રલ સર્વરની જરૂર પડે છે. તેનાથી વિપરીત, CRDTs એવી રીતે ડિઝાઇન કરવામાં આવ્યા છે કે જેથી સહપ્રત્યક્ષ (concurrent) અપડેટ્સને કોઈ સંકલન (coordination) વગર સ્થાનિક રીતે મર્જ કરી શકાય, જે તેમને peer-to-peer અથવા edge-based સેટઅપ માટે આકર્ષક બનાવે છે. તેમની વચ્ચે—અથવા હાઇબ્રિડ અભિગમો વચ્ચે—પસંદગી કરવા માટે મેમરી વપરાશ, કન્વર્જન્સ ગેરંટી અને અમલીકરણની જટિલતામાં તેમના ફાયદા અને ગેરફાયદા (trade-offs) સમજવા જરૂરી છે. આ બાબતો વિશે વાંચવું પૂરતું નથી; યોજના એ બંને સાદી (naive) અને સુધારેલી (refined) આવૃત્તિઓને અમલમાં મૂકવાની છે જેથી જોઈ શકાય કે તેઓ ક્યાં નિષ્ફળ જાય છે.
રીબિલ્ડ્સ, ભૂલો અને નિષ્ફળ પ્રયાસો
અપેક્ષાઓ પ્રમાણિક રીતે સેટ કરવામાં આવી છે. એવા સમયગાળા આવશે જ્યારે કંઈ કામ નહીં કરે. પ્રથમ પ્રયાસમાં ટેક્સ્ટ ફેરફારો દર્શાવવા માટે સાદા JSON patches નો ઉપયોગ કરવામાં આવી શકે છે, પરંતુ પછી ખબર પડશે કે JSON માં "પેરાગ્રાફમાં ઇન્ડેક્સ 5" જેવી કોઈ વિભાવના નથી, તેથી એક જ ઇન્ડેક્સ પર કરવામાં આવેલા બે એકસાથે (concurrent) ઇન્સર્શન મર્જ થવાને બદલે એકબીજાને ઓવરરાઈટ કરી દેશે. બીજો પ્રયાસ કદાચ એક કસ્ટમ લિનિયર હિસ્ટ્રી લોગ બનાવશે, પરંતુ જ્યારે ડોક્યુમેન્ટ મોટું થશે ત્યારે તે લોગને ફરીથી રન (replay) કરવો એ Big O ના кошાળ સમાન હશે. ત્રીજો પ્રયાસ કદાચ સ્થાનિક રીતે (locally) WebSockets ને કાર્યરત કરી દેશે, પરંતુ વાસ્તવિક નેટવર્ક પર જ્યાં પેકેટ લોસ અને બદલાતી લેટન્સી (latency) નિયમો બદલી નાખે છે, ત્યાં તે નિષ્ફળ જશે.
તે ઘર્ષણ જ મુખ્ય હેતુ છે. કામ કરતા રિપોઝિટરીને કોપી કરવાથી એ તપાસવાનું રહી જશે કે ક્યુ (queue) તે ચોક્કસ ક્રમમાં કેમ ફ્લશ થાય છે, અથવા સર્વર વર્ઝન વેક્ટર (version vector) કેમ જાળવી રાખે છે. એક જ ઘટકને (component) ત્રણ વાર ફરીથી બનાવવું એ ધીમું છે, પરંતુ તે ફ્રેમવર્ક શું કરે છે અને તમારા પોતાના લોજિકે શું સંભાળવું જોઈએ તે વચ્ચેની સીમા સમજવા માટે મજબૂર કરે છે.
આ પ્રક્રિયાનું દસ્તાવેજીકરણ (documentation) માત્ર સફળતાની ગાથા નહીં હોય. તેમાં ખોટા રસ્તાઓનો પણ સમાવેશ થશે. ઉદાહરણ તરીકે, પ્રેઝન્સ અવેરનેસ (presence awareness) બનાવવી—કોણ ઓનલાઇન છે અને તેમનું કર્સર ક્યાં છે તે જાણવું—એ એક દેખાવટી ફીચર લાગે છે, જ્યાં સુધી તમને એ સમજાઈ ન જાય કે તે ટેક્સ્ટની જેમ જ સમાન કન્સિસ્ટન્સી મોડેલ (consistency model) પર આધારિત છે. જો યુઝર A, યુઝર B નું કર્સર કોલમ 10 પર જુએ છે, અને પછી યુઝર B ચાર અક્ષરો ઉમેરે છે, તો તે કર્સર ક્યાં ખસશે? ડોક્યુમેન્ટ ટોપોલોજી (document topology) ની સહિયારી સમજ વિના, પ્રેઝન્સ ડેટા વાસ્તવિકતાથી વિમુખ થઈ જાય છે. તેને ઉકેલવા માટે કર્સર પોઝિશનને માત્ર તેના ન્યુમેરિકલ ઇન્ડેક્સ સાથે જ નહીં, પરંતુ તેના અન્ડરલાઇંગ ડેટા સ્ટ્રક્ચરની ઓળખ (identity) સાથે જોડવી જરૂરી છે. આ એવા પ્રકારની વિગતો છે જેના પર ટ્યુટોરિયલ્સ માત્ર ઉપરછલ્લી નજર નાખે છે કારણ કે તે કંટાળાજનક છે, એટલા માટે નહીં કે તે અગત્યની નથી.
આગળ શું આવશે
તાત્કાલિક રોડમેપ જાણીજોઈને મર્યાદિત રાખવામાં આવ્યો છે. પ્રથમ સીમાચિહ્નો (milestones) આ મુજબ હશે:
- એક રો (raw) WebSocket સર્વર જે કેરેક્ટર ઇવેન્ટ્સને ઇકો (echo) કરે, જેથી લેટન્સી અને કનેક્શન લાઇફસાયકલનો પ્રત્યક્ષ અનુભવ કરી શકાય
- ક્લાયન્ટ પર એક સાદો સ્ટ્રિંગ બફર, જેથી સમજી શકાય કે કન્કરન્સી (concurrency) હેઠળ સાદું ઇન્સર્શન ઓર્ડરિંગ કેમ નિષ્ફળ જાય છે
- ઓર્ડર્ડ સિક્વન્સ માટે શૂન્યથી બનાવેલ (from-scratch) CRDT, ભલે તે અકાર્યક્ષમ હોય, પરંતુ કમ્યુટેટિવ પ્રોપર્ટી (commutative property) ને કાર્યરત જોવા માટે
- વાસ્તવિક કોડ એડિટર સપાટી સાથે ધીમે ધીમે ઇન્ટિગ્રેશન, કદાચ CodeMirror અથવા Monaco જેવું કંઈક, જેથી એડિટરના ઇમ્પેરેટિવ API અને ઓપરેશનલ હિસ્ટ્રીના ફંક્શનલ સ્વરૂપ વચ્ચેના તફાવતનો સામનો કરી શકાય
દરેક પગલા સાથે એક લેખિત તર્ક (rationale) હશે. આ અભિગમ કેમ અને બીજો કેમ નહીં? કઈ ધારણાઓ ખોટી સાબિત થઈ? કઈ એબ્સ્ટ્રેક્શન લીક (abstraction leaked) થઈ?
વાસ્તવિક બોધપાઠ
WebSockets અથવા CRDTs ના અનુભવ વગર આવો પ્રોજેક્ટ શરૂ કરવો ડરામણો લાગે છે, પરંતુ નિપુણતા ઘણીવાર માત્ર વધુ સારા લેબલ સાથેનું વારંવારનું મૂંઝવણ જ હોય છે. ઉદ્દેશ્ય ઝડપી અંત લાવવાનો નથી. તે એક એવી સિસ્ટમ બનાવવાનો છે જેનું વર્તન અનુમાનિત (predictable) હોય, કારણ કે દરેક લેયર આશા સાથે ઇમ્પોર્ટ કરવાને બદલે હેતુ સાથે બનાવવામાં આવ્યું છે.
જો તમે પહેલા ક્યારેય કોલેબોરેટિવ સોફ્ટવેર બનાવ્યું હોય—પછી તે ટેક્સ્ટ એડિટર હોય, ડિઝાઇન ટૂલ હોય અથવા ગેમ સ્ટેટ સિંક એન્જિન હોય—તો એવા ફેલ્યોર મોડ્સ (failure modes) શેર કરો જેણે તમને અચાનક ચોંકાવી દીધા હોય. જો તમે પણ આ સિસ્ટમ્સ શીખી રહ્યા હોવ, તો સાથે જોડાયેલા રહો. કોડ ધીમે ધીમે આવશે, અને તેને વારંવાર ફરીથી લખવામાં આવશે. ડે 0 (Day 0) અત્યારે જ શરૂ થાય છે.
