Проектаванне фронтэндаў, якія працуюць без падключэння да інтарнету, як копій: чакальныя лісты, павторныя спробы, канфлікты
Дазвольце дазнацься, чаму падход «адначасна робота без супаконнекту» ператвараяе браузер у копію дадзейнаў, і як местныя храненні, стойкія калеі змэн, ідэмпатныя праказы і правілы канфліктав пераадпрацоўваюцца разам.
Фронтэнд, які добраўся працавае на швайнасці і стабільным з’яўленні, мало чаго дазваляе сказаць. Цікавыя ситуацыі пачынаюцца тады, калі запит застрягае, сігнал Wi-Fi зникае, паўздзях уходзіць у тунель, ноутбук пераходзіць між сетямі, альбо тэлефон втрачае сігнал чырвоны момант пасля таго, як хтось нажмае Save. Традыцыйная архітектура фронтэнда спрыяе ўсё гэта як адступлення: вызваць API, чакаць, апдэйтаваць стан, продаваць далей.
Дыянаўскі падход да дизайну выходзіць з працоўнае прыметы: сетка можа не працаваць, але прыкладненне павінна застацца корыстным. Гэта здаецца простаю дапраўкай ў корыстніку, але на самае працоўнае ёсьць структурныя змены. Калі браузер можа чытаць данні, прымоўляць змены, яныя захаваюцца і пазней сінхронізуюцца, ён перестае быть «танкім кліентам» і стае адным з вузлаў распрастраненай системы. У гэтым кяліку паспакоўваецца, што гэта значыць на практыцы: дзе павінны знаходзіцца данні, як безпечна аранжаваць і прабаваць заношкі знову, як UI павінен аднаўляць статус сінхронізацыі, і чаму рашэнне па адрабатаванню канфліктов ёсьць, востатку, рашэннем па продукцыі.
Выкарыстоўванне сеткі не ў критычным маршруце
Уявіце сабе просты прыкладненне для зьведак. Кто-та ачынае зьведку, правяряе ўсё і захоўвае. У традыцыйным дизайне прабег справ працоўнае такі:
User → React → Server
↓
Data
Сервер знаходзіцца безпосередньа ў межах адносінаў з корыстнікам. Павольны сервер значыць павольную інтерфейсную сістэму; перыякшанне з’ўязку значыць абставаленне запісу дадзенаў; невыпанае запитанне змушвае прыстрой запрацаваць на месцы, выбіраючы, што робіць зі змянамі корыстніка.
Дизайн, які прыватыжае роботу поза інтернетам, пераносіць значную частку гэтай адпаведнасці ў браузер:
User → React → Local Store
↓
Sync Later
Сервер не стае незначным. Для большасці систем ён застаеўся месцам, дзе зберагаецца спільная інформацыя. Змінюецца тое, што яго больш не трэба ўможлівіць для кожнай адносіны з корыстнікам. Гэта раз’ядранне – між паўтарэнням дзеяння корыстніка і його перадачай на сервер – являецца аднаго з фундаментальных элементаў, на якіх будуе все іншае.
Браузер тепер зберагае важлівы стан
Якщо аплікацыя должна працаваць без супакоўкі, яй патрэбна надзеямаеся сховішча на прыстрое. Для чаго-небудзь большага, чым простыя настройкі, гэта зазвычай значыць использованне IndexedDB або базы дадзеных вышэйшага рангу, створанай на ўснове яго. Конкрэтная бібліята мае меншое значэнне, чым наследкі: прыгледальнік тепер контролюе стан, які вы не можетэсе сабе прабыць адзначыць.
Думаце пра запісаныя у кэш рэкорды і непадасланыя чернавіки, мутацыі, якія чакаюць выдазвання, сінхронныя метаданные, такія як часы і номеры версый, а таксама ведэнне пра тое, што ўжо было заванесена. Це прыводзіць да пытання, на якое аплікацыя з простым запитам-адказам ніколі не мусіць адпавядаць: што должна стацца, калі копія ў прыгледальніку і копія на серверы не збігаюцца? У аплікацыі, якая працуе пераважна без супакоўкі, гэта не є адзінаковы случай; гэта стандартна ситуацыя.
Кэшаванне чытанняў — гэта не тое ж самае, што працаванне пераважна без супакоўкі
Гэтыя разлікі лёгкая спутаць. Аплікацыя можа кэшаваць адказы API, пры тым застаючыся ўпоўну залежной ад сеті. Падазрóваймо, што яна запрашае спіс проектаў і зберагае рэзультат локальна, тады спіс з’яўляецца зноў без паўзы. Гэта корыстна, але гэта стосуецца толькі чытання.
Дизайн стае сапраўдзімым «offline-first», калі корыстувальнікі можаць зменяць даныя без паўзы: ствараць проект, рэдагаваць дакумент, пазначыць задачу як выпануюючуюся, налаштаваць параметры. Гэтыя дзеянні не можу проста зникнуць таму, што не было адправленыя HTTP-запрос. Аплікацыі патрэбна адмова, куды запісваць тое, што корыстувальнік хацеў зрабіць, каб гэта намер застаўся до таго часу, пакуль яго не будзе можна выдаты.
Кожная запись у режыме офлайн стае обязанностью синхронізацыі
Калі хтось рэдагуе дакумент паавтонамо, звычны спосаб — спачатку застосаваць змяну ў локальным сховішчы, а пасля задаць запіс аперацыі, якая ўсё ж трэба дасягнуць сервера. У спроставанай форме:
User Saves
↓
Save Locally
↓
Update UI
↓
Add To Queue
↓
Sync When Online
Зверніце увагу на порядак: локальная зберогаўка і апдэйт інтерфейса выконваюцца раніше, чым ўсё будзе пакладзенаў у чергу, і самэ гэта дазволяе інтерфейсу адразу рэагаваць. Чарга, якая з’яўляецца напрыканце, заслуговае на большую увагу, чым зазвычай. Це не проста купка запитоў, якія выдалі памылку, а надзеямы лог, які фіксуе роботу, яку корыстнік чакае, што система завершыць.
Этам ёсць конкрэтныя трэбаванні. Чарга павінна застацца пры перзагрузке сторанкі, пачатку роботы браузера, тымчасовых адказах сервера і змянам у сеті, што значыць, яна павінна зберагвацца ў стойкай базе дадзеных, а не ў масі JavaScript. Яна таксама павінна правільна працаваць, калі адна операцыя выканана два чы роўну разоў. Гэтае другое трэбаванне прыводзіць да аднаго з найскладнейшых спосабаў адказаў у всім дизайне.
Калі успех выглядае абоўсюды як адказ
Разглянем гэтую последовальнасць. Пользователь зберагае даны. Прыходзіць запит. Сервер успешна збірае даны. Потым, перш чым адказ дасягне кліента, з’єднанне перыходзіць. Кліент выводзіць, што збераганне не удалося; сервер жа ведае, што ён успэшны.
Client
|
| Save
v
Server
|
| Saved
X
Response Lost
|
v
Client Retries
Якщо кліент проста ўскладніць спробу, сервер можа застосаваць тую ж самую змяну два разы. Для ідэмпатныя операцыі „set title“ гэта можа не мець наследкав, але для операцый „add item“, „create order“ чы „increment counter“ гэта прыводзіць да стварэння дублікатаў.
Самэльга ідэмпатнасць ёсць ключоўым элементам систем, якія працуюць у режыме „офлайн-перш“. Кожная змяна патрэбуе стабільнага ідэнтыфікатора, каб сервер мог упэўніцца, што ўскладніць — це тая ж логічная операцыя, а не новая. Часта выкарыстоўваемы падход — гэта стварэнне ID операцыі на кліянце праз адначаснае запісванне змяны, яго зберагчэнне разам з операцыяй у черзі і адправка яго ў кожной спробе. Сервер стежыць за тым, якія ID ён вучынаў обробіць, і, пабачыўшы повтарэнне, падтверджае яго, але не застосоўвае знову.
Это не простае налаштаванне для пасібнасці. Цэ гэта, што не дазволяе ненадзейнай сети ператвараць звычныя праказы на дубліруючую работу. Практычны детал, які варта правільна адрабатваць: ID павинен быць створаны ўсёраз і зберагацца разам з элементам кялі, а не генеравацца занова за кожны запит, інакш кожны праказ будзе вважацца новым.
Оптымістычны інтерфейс як выказанне намерэння
Оптымістычныя апдэйты зазвычай прыводзяцца як спосаб скарбліваць прыстроі. У прыстроях, якія працуюць без сеті першымі, ў яных вони маюць большое значэнне. Якщо корыстувальнік завершае заданне без сеті, а інтерфейс чакае на адпаведзь сервера, прыстрой проста выглядае зламаным. Локальныя змены павінны з’являцца момантальна, а сінхронізацыя можа пазначыць даўняю частку ў актуальны стан пазней.
Гэта дае корыстны разделенне адпаведнасцей:
- Локальны стан адрабатвае тое, што зробіў корыстувальнік.
Этыя два пункты связаны, але не ідэнтычны, і якшае ўтратаць іх адна з другой дазволяе інтерфейсу даўаць более чыстую інформацію, чым адзін показчык «онлайн/офлайн». Напрыклад, элемент можа быць пазначаны як:
- зберажаны на гэтым прыстрое
- знаходзіцца у черге на сінхронізацыю
- сінхронізуецца зараз
- вже сінхронізаваны
- трыбуе увагі пользователя
Пользователь, які бачыць пазначку «знаходзіцца у черге на сінхронізацыю», розумее, што даныя ў безпеці, але яшчо не спадзелены, што ў дзiesяць разоў больш заспакойвае, чым неясны іконка або паведамленне пра адказку.
Дазвольце локальнаму сховішчу кераваць данымі, а React іх відобразваць
Звычным выклікам ненавольнай складнасці ёст тое, што даныя зберагаюцца ў IndexedDB, завантажуюцца у стан React, пасля чаго копіююцца ў окремы кліентскі сховішча, а потым усё ручна падтрымваецца сінхронізаваным. У такім разе прыкладнэць мае калькі репрэзентацый тых сабе записей, што прыводзіць да застарэлых значэнняў, ситуацый канкурэнціі, непатрэбных перерасчытанняў і бягоў сінхронізацыі, якія існуюць выключна ў фронтэндзе.
Чыстейшая модель перакладае адпаведальнасць за стойкія даныя на надзеючаеся локальнае сховішча, пры чым React выступае як відображэнне ўсьго гэтаго:
Local Store
↓
React UI
↓
User
↓
Local Store
Сама сутнасць — це цыкл: інтэрфейс чытае даны з локальнага сховішча, корыстувач выконвае дзеянне, а гэта дзеянне запісвае даны назад у локальнае сховішча, якое зноў керуе інтэрфейсам. React адпавядае за обработку взаімадзейнаў і прадставлення даных; база дадзенаў адпавядае за стан на боку кліента, які павінен застацца незмянным. У практыцы гэта зазвычай значыць, што компоненты підпішоцца на запиты да локальнага сховішча, а не копіююць рэзультаты ў стан компонентаў. Корыста зростае з розмерам прыкладнення, таму што існуе толькі адна точка, куды можна паглянуць, калі значэнне здаецца некоректным.
Калі два прыстроя абоўсюды правыя
Расляжэнне канфліктав яўляецца тым моментам, калі падход «перш за ўсё — афлайн» перестае быць пытаннем хранення дадзэнняў і стае пытаннем распрацоўкі даскладных систем. Падазрóў, калі хтось ачынае той самы документ на ноутбуку і тэлефоне, і аба прыключаюцца афлайн. На ноутбуку заглавье зменяецца на Frontend Architecture; на тэлефоне — на Offline Architecture. Кожны прыстрой прийняў легітымную змену, і ніхто не ведае пра дзеяння другога. Калі аба прыключаюцца знову да інтэрнету, у ўсіх выйшваецца канфлікт:
Laptop Phone
| |
v v
"Frontend App" "Offline App"
| |
+--------+----------+
|
v
Conflict
Не існуе універсальна правильнага спосабу расляжэння канфліктав. Чы хутчэй мае перамагаць пасляпянейшая змена? Чы мае прывілейвацца версія сервера? Чы можна з’едыніць поля? Чы мае выбіраць корыстувальнік? Чы разныя поля маюць паводлівацца рознымі правіламі?
Ключовая ідея закладаецца ў тым, што расляжэнне канфліктав яўляецца рашэнням, прыемлемым для продукту, якое выглядае як тэхнічнае рашэння. Прынцып „першы, хто запісаў, той і выграў“ можа быць абсалютна прыемным, напрыклад, для настаўок інтерфейсу. Аднак для саавтарскіх дакументаў, фінансовых дадзенняў, запісаў заляганняў або важлівых бізнес-дакументаў гэты прынцып можа быць непрыемным, адтаколькі таямнай выдаленню одной з сторон губяцца рэальныя працы або ствараюцься некоректныя падсумкі. Архітактура должна вказаць, што значыць „коректна“ для кожнага типу дадзенняў, і самэ гэта ўскладнюе справу власнікам продукту, якія павінны браты участь у гэтых дыскусіях, а не залишаць гэта на рашэнне тых, хто піша код синхронізацыі. Таксама трэба памятаць, што сам паняў „пасляльшага“ є нестабільным: гадзінкі прыстроёў пакрэслююцца, таму правілы, заснованыя на часовых падазначэннях кліента, можу выбраць некоректны варыянт, якщо толькі порядак не будзе вылучаны з чагось болей надзяйныя, напрыклад, з версій, прызначаных серверам.
Далёкія змены трэбуюць таго ж пункта входу
Падчынна праця не значыць, што сінхронізацыя вядзецца толькі тады, калі запускаецца прыстанок. Іншыя корыстувальнікі і іншыя прыстроі зменяюць тыя ж данні, а сервер продовжае прымляць змяны з іншых месцаў. Таму канал у рэальны час адкрывае другі напрамак перадачы даных:
Local Changes
↓
Sync
↓
Server
↓
New Changes
↓
Local Store
↓
UI
Гэта прыводзіць да важлівага правіла: змяны, якія прыходзяць з сервера, должны падаць у той самы локальны модэль даных, які вже чытае інтерфейс. Якщо локальныя змены праходзяць аднай дорагой, а удалённыя — іншай, компаненты React вынужданы викорыстоўваць складную логіку, каб супрацаваць з гэтымі двума варыянтамі. Якшчо локальны хранар стане ўніверсальным пунктом супрацоўкі, рэндарынг застанецца простым: інтерфейсу не мае значэння, чыя зменаўы выйшлі з гэтага прыстрою чы з іншага.
Праяжджайце, што сам браузер можа зламацца
Існуе ўзьміцельны спосаб зламу, які лёгка працязнаць: браузер можа зникнуць раней, чым сетка знову будзе доступна. Карточка закрыта, браузер зламваецца, апэратная система заварачвае процес, акумулятор ноутбука разрядзіцца, або процес зникае пад час сінхронізацыі. Усё незавершанае заведамства, якое існуе толькі ў памяці, таксама губіцца.
Самэй тым стан сінхронізацыі павінен быць стойкім. Серйозны прылад, які працюе афлайн-перш, павінен спрыяваць браузеру як ненадзеянаму вузлу, а його локальная база дадзеных павінна зберагчыць достатню колькасць інфармацыі, каб можна было восстанавіць незавершанае заведамства, калі прылад зноў запускаецца: якія операцыі застаюцься незавершанымі, якія былі ў процесе адбывання, і якім быў пасляпянны стан сервера. Корыстны тэст — заварачваць карточку пад час сінхронізацыі пад час разработкі і пераканацца, што нічога не губіцца і не дуплікуецца пад час наступнага запуску.
Service workers — гэта частка, а не всяя архітектура
Рабочыя процесы сервісу маюць великую цэннасць, ў чырвоныя для кешавання ресурсаў прыкладнення і перехоплення запитоў сеті; якщо вам трэба падтвердзіць своия знання ў гэтым аспекте, перагляньце як увімкнуць падтрымку роботы без інтернету за дапамогою рабочых процесаў сервісу. Але рэгістрацыя такога процесу не робіць прыкладненне прыоритэтным для роботы без інтернету:
- Зарэгістраваны рабочы процес нічога не робіць, каб запобiec втрате незявленых корыстнікам змян.
- Кешаваныя пакеты дазволяюць прыкладненню запускацца без інтернету, але не вказываюць, куды будуць зберагваны даны, створаныя без інтернету.
- Падача застарэлых адпаведзей API з кешу не є політікай разв’язвання конфліктаў.
Падчыннае працаванне ў режыме без з’ўязку — гэта атрыбут самай архітектуры, а не функцыя браузера, яку можна увімкнуць. Тое, што насправды дазволяе яй працаваць, — цэх локальны шар дадзеных, змены, якія застаюцца, процес синхронізацыі, частакаванне падчынных ситуацый, правілы рашэння канфліктав і інтерфейс, який розумее, што даўняя синхронізацыя можа займаць час.
Складнасць перашырваецца; яна не зникае
Некомфортная рэальнасць заключаецца у тым, што програмы, якія працуюць у режыме без з’ўязку, не є простэйшымі. Складнасць толькі пераносится. Звычайныя дапрыемнікі можу проста адправіць запит і чакаць на адпаведзь. Дапрыемнікі, які працуюць у режыме без з’ўязку, вынушаны разв’язваць шмат сложных задач:
- Што, якщо не вдасться з’явіцца да сервера?
- Што, якщо браузер закрыцца раней, чым завершыцца синхронізацыя?
- Што, якщо запит прыйшоў, але адпаведзь — ні?
- Што, якщо тая ж сама інформацыя зменіцца на іншам прыстрое?
- Што, якщо обе змены буду правамі?
Это класычныя проблемы распраўляючыхся систем, і наявнасць прыгледача як аднаго з учаснікаў не робіць іх простымі. Перш чым выбраць падход «перш за ўсё — афлайн», варта запытацца, чы рэальна продукту патрэбны афлайн-запісы. Для панелі керування, яюякую корыстувальнікі чытаюць толькі, кэшаваныя запісы плюс чыстая паведамленне пра роботу афлайн можа даць большую частку корысті за мінімальныя затраты.
Локальныя даны заслугваюць на адпаведную захоўнасць на рэвэрсе сервера
Даны, якія зберагліся на прыстрое, застаюцца данымі, з усіма наследкамі, якія гэта несе. Якщо чулыя даны знаходзяцца ў прыгледачы, трэба вучыцься, што будзе, якщо прыстрой будзе атакаваны, якшы ўжо кілька чалавек яго выкарыстоўваюць, або калі адна адзначка выйде з системы, а інша людзь яе вярне. Локальная перазберагачванасць должна адпавядаць той самай захоўнасці, як і храненне на бэкэндзе:
- Храніце толькі тое, каму кліент практычна патрэбуе.
- З’явіце правілы выканання тэрміну і тое, што будзе адбывацца пасля выходу з системы.
- Не спрыявайце храненню ў браузеры як даўерлівам сховішчу.
- Рашуча выберыце, якія данні могу існаваць на прыстрое ўзагалі.
Падход «перш за ўсё — афлайн» павышае доступнасць, але гэты прыбутак ніколі не должен купівацца цэнай адкрытых інформацыяў, якія з самага пачатку не трэбавалася храніць локальна.
Проектаванне з урахоўваннем адказаў уместо ідеальнага сцэнарыю
Яскравая асоблівасць падходу «перш за ўсё — афлайн» заключаецца у тым, што архітектура становіцца яснейя, калі перастаеце праектаваць пад ідеальны сцэнарыю. Змінююцца пытанні:
- Уместо «як мне запрашыць гэтыя данні?» спытайце, дзе должны знаходзіцца данні, калі сеть нямае.
- Уместо «як мне прабавіць зноў выконаць гэты запит?» спытайце, як кліент можа дазнацца, чы не быў ён вялікі серверам.
Адпаведныя адказы на другую версію кожнага запитання ствараюць моцнейшыя дизайны.
Мета — такой досвяд, якога корыстувальнікі практычна не зазначаюць
Корыстувальнікі не заінтэресаваныя ў механізмах сінхронізацыі, транзакціях базы дадзеных, політіках адкладэння, алгорытмах з’еднання чы реплікацыі. Яны хочуць працаваць далей. Яны спадзяваюцца, што чернавік застанется там пасля перыявлення з’язку, задача, выкананая ў поязде, застанется выкананай, а прыстрым не стане безпарацейным як толькі зменяецца іконка Wi-Fi. Механізмы пад спудам можуць быць складнымі; досвяд на верхнім слое не павінен такім быць.
Ключовыя выводы
Едыная ідея, яка з’еднаеўшы ўсё дзіянне дизайну, выглядае так: фронтэнд, які працюе пераважна без падключэння да інтарнету, — это распраўленыя система, у якой браузер храніць другую копію дадзеных. Якшчы паглядзець на рэчы з гэтай точкі зору, адзеленыя элементы стаюць яснейшымі за свою ролю:
- IndexedDB — это не проста сховішча браузера; гэта частка шару дадзеных прыемлівання.
- Очакванне на змяну — это не проста адказ на падзею; гэта надзейна запись намеру пользователя, і кожны элемент патрэбуе стабільнага ID для можлівасці повторных спроб.
- Оптымістычны інтерфейс — это не трюк для прышвартавання швайна; гэта спосаб зберагчы прыемліванне придатным па час, калі бэкенд трэбуе схваліцца.
- Раслучэнне конфліктав — это не адзін экстремальны кейс; гэта частка способу, якім прыемліванне визначае свою правильнасць.
- Сінхронізацыя — это не ўсё, што дадаецаўна даганяць напрыкінцы; гэта сама архітектура.
Сеті перыходзяць, серверы спаваюць, прыстроі на час не сагласныся, а браузеры зникаюць без паведамлення. Якісна фронтэнд-частка, розраблена з увагай да работы без сеті, гарантуе, што робота пользователя застанется незьменнай усупраць гэтам, адкладаючы свое завярэнне нават тады, калі з’ўязак немагчымы.