Зручныя архітэктурныя патэрны

Прывітанне, Хабр!

У святле бягучых падзей з-за каранавіруса шэраг інтэрнет-сэрвісаў стаў атрымліваць павялічаную нагрузку. Напрыклад, адна з гандлёвых сетак у Вялікабрытаніі проста спыніла сайт з анлайн-замовамі, бо не хапіла магутнасцяў. І далёка не заўсёды можна паскорыць сервер, проста дадаўшы больш магутнае абсталяванне, аднак запыты кліентаў апрацоўваць трэба (ці яны сыдуць да канкурэнтаў).

У гэтым артыкуле я коратка раскажу аб папулярных практыках, якія дазволяць зрабіць хуткі і адмоваўстойлівы сэрвіс. Аднак з магчымых схем распрацоўкі я адабраў толькі тыя, якімі зараз лёгка скарыстацца. Для кожнага пункта ў вас ці ёсць ужо гатовыя бібліятэкі, ці ёсць магчымасць вырашыць задачу з дапамогай хмарнай платформы.

Гарызантальнае маштабаванне

Самы просты і ўсім вядомы пункт. Умоўна, найбольш часта сустракаюцца дзве схемы размеркавання нагрузкі - гарызантальнае і вертыкальнае маштабавання. У першым выпадку вы дазваляеце сэрвісам працаваць раўналежна, тым самым размяркоўваючы нагрузку паміж імі. У другім вы заказваеце больш магутныя серверы ці аптымізуеце код.

Для прыкладу я вазьму абстрактнае хмарнае сховішча файлаў, гэта значыць некаторы аналог OwnCloud, OneDrive і гэтак далей.

Стандартная карцінка падобнай схемы ніжэй, аднак яна толькі дэманструе складанасць сістэмы. Бо нам трэба неяк сінхранізаваць сэрвісы. Што будзе, калі з планшэта карыстач захаваў файл, а потым жадае яго паглядзець з тэлефона?

Зручныя архітэктурныя патэрны
Розніца паміж падыходамі: у вертыкальным маштабаванні мы гатовыя нарошчваць магутнасць вузлоў, а ў гарызантальным - дадаваць новыя вузлы, каб размеркаваць нагрузку.

CQRS

Command Query Responsibility Segregation даволі важны патэрн, бо ён дазваляе розным кліентам не толькі падлучацца да розных сэрвісаў, але і таксама атрымліваць аднолькавыя струмені падзей. Яго бонусы не гэтак відавочныя для простага прыкладання, аднак ён вельмі важны (і просты) для нагружанага сэрвісу. Яго сутнасць: уваходныя і выходныя струмені дадзеных не павінны перасякацца. Гэта значыць, вы не можаце адправіць запыт і чакаць адказ, замест гэтага вы адпраўляеце запыт у сэрвіс A, аднак атрымліваеце адказ у сэрвісе Б.

Першы бонус гэтага падыходу - магчымасць разрыву злучэння (у шырокім сэнсу гэтага слова) у працэсе выканання доўгага запыту. Для прыкладу возьмем больш-менш стандартную паслядоўнасць:

  1. Кліент адправіў запыт на сервер.
  2. Сервер запусціў доўгую апрацоўку.
  3. Сервер адказаў кліенту вынікам.

Уявім, што ў пункце 2 адбыўся абрыў сувязі (ці сетка перападключылася, ці карыстач перайшоў на іншую старонку, абарваўшы злучэнне). У гэтым выпадку серверу будзе складана адправіць адказ карыстачу з інфармацыяй, што менавіта апрацавалася. Ужываючы CQRS, паслядоўнасць будзе крыху іншай:

  1. Кліент падпісаўся на абнаўленні.
  2. Кліент адправіў запыт на сервер.
  3. Сервер адказаў "запыт прыняты".
  4. Сервер адказаў вынікам праз канал з пункта "1".

Зручныя архітэктурныя патэрны

Як бачна, схема крыху больш складаная. Больш за тое, інтуітыўны падыход request-response тут адсутнічае. Аднак як бачна, абрыў сувязі ў працэсе апрацоўкі запыту не прывядзе да памылкі. Больш таго, калі насамрэч карыстач падлучаны да сэрвісу з некалькіх прылад (напрыклад, з мабільнага тэлефона і з планшэта), можна зрабіць так, каб адказ прыходзіў на абедзве прылады.

Што цікава, код апрацоўкі ўваходных паведамленняў становіцца аднолькавым (не на 100%) як для падзей, на якія паўплываў сам кліент, так і для астатніх падзей, у тым ліку ад іншых кліентаў.

Аднак у рэальнасці мы атрымліваем дадатковыя бонусы ў сувязі з тым, што аднанакіраваны паток можна апрацоўваць у функцыянальным стылі (выкарыстоўваючы RX і аналагі). І вось гэта ўжо сур'ёзны плюс, бо ў сутнасці прыкладанне можна зрабіць цалкам рэактыўным, прычым яшчэ з ужываннем функцыянальнага падыходу. Для тоўстых праграм гэта можа істотна зэканоміць рэсурсы на распрацоўку і падтрымку.

Калі аб'яднаць гэты падыход з гарызантальным маштабаваннем, то бонусам мы атрымліваем магчымасць дасылаць запыты на адзін сервер, а атрымліваць адказы ад іншага. Такім чынам, кліент можа сам выбіраць зручны для яго сэрвіс, а сістэма ўсярэдзіне ўсё роўна зможа карэктнай выявай апрацаваць падзеі.

Пошук падзей

Як вы ведаеце, адной з асноўных асаблівасцяў размеркаванай сістэмы з'яўляецца адсутнасць агульнага часу, агульнай крытычнай секцыі. Для аднаго працэсу вы можаце зрабіць сінхранізацыю (на тых жа м'ютэксах), усярэдзіне якой вы ўпэўненыя, што ніхто больш не выконвае гэты код. Аднак для размеркаванай сістэмы падобнае небяспечна, бо на гэта спатрэбяцца накладныя выдаткі, а таксама заб'ецца ўсё хараство маштабавання - усё роўна ўсе кампаненты будуць чакаць аднаго.

Адгэтуль атрымліваем важны факт - хуткую размеркаваную сістэму нельга сінхранізаваць, бо тады мы паменшым прадукцыйнасць. З іншага боку, часцяком нам неабходна вызначаная ўзгодненасць кампанентаў. І для гэтага можна выкарыстоўваць падыход з канчатковая кансістэнцыя, дзе гарантуецца, што пры адсутнасці зменаў дадзеных праз нейкі прамежак часу пасля апошняга абнаўлення («у канчатковым рахунку») усе запыты будуць вяртаць апошняе абноўленае значэнне.

Важна разумець, што для класічных баз дадзеных даволі часта прымяняецца. строгая ўзгодненасць, дзе кожны вузел валодае аднолькавай інфармацыяй (падобнае часта дасягаецца ў выпадку, калі транзакцыя лічыцца ўсталяванай толькі пасля адказу другога сервера). Тут ёсць некаторыя паслабленні з-за ўзроўняў ізаляцыі, аднак агульная іста застаецца той жа - вы можаце жыць у цалкам узгодненым свеце.

Аднак вернемся да першапачатковай задачы. Калі частка сістэмы можа быць пабудавана з канчатковая кансістэнцыя, то можна пабудаваць наступную схему.

Зручныя архітэктурныя патэрны

Важныя асаблівасці гэтага падыходу:

  • Кожны ўваходны запыт змяшчаецца ў адну чаргу.
  • У працэсе апрацоўкі запыту сэрвіс можа таксама змяшчаць задачы ў іншыя чэргі.
  • У кожнай уваходнай падзеі ёсць ідэнтыфікатар (які неабходны для дэдуплікацыі).
  • Чарга ідэалагічна працуе па схеме "append only". З яе нельга выдаляць элементы ці перастаўляць іх.
  • Чарга працуе па схеме FIFO (прабачце за таўталогію). Калі неабходна зрабіць паралельнае выкананне, то трэба ў адным з этапаў перакладаць аб'екты ў розныя чэргі.

Нагадаю, што мы разглядаем выпадак анлайн файлавага сховішча. У гэтым выпадку сістэма будзе выглядаць, прыкладна, так:

Зручныя архітэктурныя патэрны

Важна, што сервісы на дыяграме не абавязкова азначаюць асобны сервер. Нават працэс можа быць адзін і той жа. Важна іншае: ідэалагічна гэтыя рэчы падзелены такім чынам, каб можна было лёгка прымяніць гарызантальнае маштабаванне.

А для двух карыстачоў схема будзе выглядаць так (сэрвісы, прызначаныя розным карыстачам, пазначаныя розным колерам):

Зручныя архітэктурныя патэрны

Бонусы ад падобнай камбінацыі:

  • Сэрвісы апрацоўкі інфармацыі падзелены. Чэргі таксама падзелены. Калі нам запатрабуецца павялічыць прапускную здольнасць сістэмы, тое трэба ўсяго толькі запусціць больш сэрвісаў на большай колькасці сервераў.
  • Калі мы атрымліваем інфармацыю ад карыстальніка, нам не абавязкова чакаць поўнага захавання дадзеных. Наадварот, нам дастаткова адказаць "ок", а потым паступова пачаць працу. Заадно чарга згладжвае пікі, бо даданне новага аб'екта адбываецца хутка, а поўнага праходу па ўсім цыкле карыстачу чакаць не абавязкова.
  • Для прыкладу я дадаў сервіс дэдуплікацыі, які спрабуе аб'ядноўваць аднолькавыя файлы. Калі ён доўга працуе ў 1% выпадкаў, кліент гэтага практычна не заўважыць (гл. вышэй), што з'яўляецца вялікім плюсам, бо ад нас ужо не патрабуецца стоадсоткавая хуткасць і надзейнасць.

Аднак адразу ж бачныя і мінусы:

  • У нашай сістэмы знікла строгая ўзгодненасць. Гэта значыць, што калі, напрыклад, падпісацца да розных сэрвісаў, то тэарэтычна можна атрымаць розны стан (бо адзін з сэрвісаў можа не паспець прыняць апавяшчэнне ад унутранай чаргі). Як яшчэ адно следства, у сістэмы зараз няма агульнага часу. Гэта значыць нельга, напрыклад, сартаваць усе падзеі проста па часе прыходу, бо гадзіннік паміж серверамі могуць не быць сінхроннымі (больш за тое, аднолькавы час на двух серверах - гэта ўтопія).
  • Ніякія падзеі нельга зараз проста адкаціць (як можна было б зрабіць з базай дадзеных). Замест гэтага неабходна дадаць новую падзею. compensation event, які будзе мяняць апошні стан на неабходнае. Як прыклад з падобнай вобласці: без перазапісу гісторыі (што дрэнна ў шэрагу выпадкаў) у git нельга адкаціць коміт, аднак можна зрабіць адмысловы rollback commit, які па сутнасці проста верне стары стан. Аднак у гісторыі захаваецца і памылковы коміт, і rollback.
  • Схема дадзеных можа мяняцца ад рэлізу да рэлізу, аднак старыя падзеі зараз не атрымаецца абнавіць на новы стандарт (бо падзеі ў прынцыпе нельга мяняць).

Як відаць, Event Sourcing выдатна ўжываецца з CQRS. Больш за тое, рэалізаваць сістэму з эфектыўнымі і зручнымі чэргамі, аднак без падзелу патокаў дадзеных, ужо само па сабе складана, бо давядзецца дадаваць кропкі сінхранізацыі, якія будуць нівеліраваць увесь станоўчы эфект ад чэргаў. Ужываючы абодва падыходы адразу, неабходна нязначна скарэктаваць код працы праграмы. У нашым выпадку, пры адпраўцы файла на сервер, у адказе прыходзіць толькі "ок", што значыць толькі, што "аперацыя дадання файла захавана". Фармальна, гэта не азначае, што дадзеныя ўжо даступныя на іншых прыладах (напрыклад, сэрвіс дэдуплікацыі можа перабудоўваць азначнік). Аднак праз некаторы час кліенту прыйдзе апавяшчэнне ў стылі "файл Х захаваны".

Як вынік:

  • Лік статутаў адпраўкі файлаў павялічваецца: замест класічнага "файл адпраўлены" мы атрымліваем два: "файл дададзены ў чаргу на серверы" і "файл захаваны ў сховішча". Апошняе азначае, што іншыя прылады ўжо могуць пачаць атрымліваць файл (з папраўкай на тое, што чэргі працуюць з рознай хуткасцю).
  • З-за таго, што інфармацыя аб адпраўцы зараз прыходзіць па розных каналах, нам неабходна прыдумляць рашэнні, каб атрымліваць статут апрацоўкі файла. Як следства з гэтага: у адрозненне ад класічнага request-response, кліент можа быць перазапушчаны падчас апрацоўкі файла, аднак статут гэтай самай апрацоўкі будзе карэктны. Прычым гэты пункт працуе, па сутнасці, са скрынкі. Як следства: мы зараз больш талерантныя да адмоваў.

Sharding

Як ужо апісвалася вышэй, у сістэмах з event sourcing адсутнічае строгая ўзгодненасць. А значыць, мы можам выкарыстоўваць некалькі сховішчаў без якой-небудзь сінхранізацыі паміж імі. Набліжаючыся да нашай задачы, мы можам:

  • Падзяляць файлы па тыпах. Напрыклад, карцінкі/відэа можна дэкадаваць і выбіраць больш эфектыўны фармат.
  • Падзяляць акаўнты па краінах. З-за шматлікіх законаў падобнае можа запатрабавацца, аднак гэтая схема архітэктуры дае падобную магчымасць аўтаматычна

Зручныя архітэктурныя патэрны

Калі вы хочаце перанесці дадзеныя з аднаго сховішча ў іншае, то тут ужо стандартнымі сродкамі не абысціся. Нажаль, у такім выпадку неабходна спыніць чаргу, зрабіць міграцыю, а потым запусціць яе. У агульным выпадку "на ляту" дадзеныя не перанесці, аднак, калі чарга падзей захоўваецца цалкам, і ў вас ёсць злепкі папярэдніх станаў сховішча, то мы можам перапрайграць падзеі наступным чынам:

  • У Event Source кожная падзея мае свой ідэнтыфікатар (у ідэале - не памяншаецца). А значыць у сховішча мы можам дадаць поле - id апошняга апрацаванага элемента.
  • Дублюем чаргу, каб усе падзеі маглі апрацоўвацца для некалькіх незалежных сховішчаў (першае – гэта тое, у якім ужо зараз захоўваюцца дадзеныя, а другое – новае, аднак пакуль пустое). Другая чарга, натуральна, пакуль не апрацоўваецца.
  • Запускаем другую чаргу (гэта значыць пачынаем перапрайграванне падзей).
  • Калі новая чарга будзе адносна пустая (г.зн. сярэдняя розніца ў часе паміж даданнем элемента і яго ж выманне будзе прымальная), можна пачынаць перамыкаць чытачоў на новае сховішча.

Як бачна, у нас у сістэме як не было, так і няма строгай узгодненасці. Ёсць толькі eventual constistency, гэта значыць гарантыя таго, што падзеі апрацоўваюцца ў аднолькавым парадку (аднак, магчыма, з рознай затрымкай). І, карыстаючыся гэтым, мы можам параўнальна лёгка перанесці дадзеныя без прыпынку сістэмы на іншы канец зямнога шара.

Такім чынам, працягваючы наш прыклад пра анлайн сховішча для файлаў, падобная архітэктура ўжо дае нам шэраг бонусаў:

  • Мы можам перамяшчаць аб'екты бліжэй да карыстальнікаў, прычым дынамічнай выявай. Тым самым можна павысіць якасць сэрвісу.
  • Мы можам захоўваць частку дадзеных унутры кампаній. Напрыклад, Enterprise карыстачы часцяком патрабуюць захоўваць іх дадзеныя ў падкантрольных датацэнтрах (у пазбяганне ўцечак дадзеных). За кошт sharding мы можам улегкую гэта падтрымаць. І задача яшчэ спрашчаецца, калі ў замоўца ёсць сумяшчальнае воблака (напрыклад, Azure self hosted).
  • А самае важнае - мы можам гэтага не рабіць. Бо для пачатку нас суцэль бы задаволіла адно сховішча для ўсіх акаўнтаў (каб барзджэй пачаць працаваць). І ключавая асаблівасць гэтай сістэмы - хоць яна і пашыраецца, на пачатковым этапе яна дастаткова простая. Проста не трэба адразу пісаць код, які працуе з мільёнам асобных незалежных чэргаў і т.д. Калі спатрэбіцца, тое гэта можна зрабіць у будучыні.

Static Content Hosting

Гэты пункт можа здацца зусім відавочным, аднак ён усё роўна неабходны для больш-менш стандартнага нагружанага дадатку. Яго сутнасць простая: увесь статычны кантэнт раздаецца не з таго ж сервера, дзе знаходзіцца дадатак, а са спецыяльных, выдзеленых менавіта пад гэтую справу. Як следства, гэтыя аперацыі выконваюцца хутчэй (умоўны nginx аддае файлы і аператыўней, і меней затратна, чым Java-сервер). Плюс архітэктура CDN (Сеткі дастаўкі кантэнту) дазваляе размяшчаць нашы файлы бліжэй да канчатковых карыстальнікаў, што станоўча адбіваецца на зручнасці працы з сэрвісам.

Самы просты і стандартны прыклад статычнага кантэнту - гэта набор скрыптоў і карцінак для сайта. З імі ўсё проста – яны вядомыя загадзя, далей архіў выгружаецца на CDN серверы, адкуль раздаюцца канчатковым карыстачам.

Аднак на справе для статычнага кантэнту можна ўжыць падыход нечым падобны на лямбда архітэктуру. Вернемся да нашай задачы (анлайн сховішча файлаў), у якой нам неабходна раздаваць файлы карыстальнікам. Самае простае рашэнне ў лоб - зрабіць сэрвіс, які для кожнага запыту карыстальніка робіць усё неабходныя праверкі (аўтарызацыя і тд), а потым спампоўвае файл непасрэдна з нашага сховішча. Галоўны мінус такога падыходу – статычны кантэнт (а файл з вызначанай рэвізіяй, у сутнасці, з'яўляецца статычным кантэнтам) раздаецца тым жа серверам, што і ўтрымоўвае бізнэс-логіку. Замест гэтага, можна зрабіць наступную схему:

  • Сервер выдае URL для запампоўкі. Ён можа быць выгляду file_id + key, дзе key - міні-лічбавы подпіс, якая дае права на доступ да рэсурсу на бліжэйшыя суткі.
  • Раздачай файла займаецца просты nginx са наступнымі опцыямі:
    • Кэшаванне кантэнту. Так як гэты сэрвіс можа знаходзіцца на асобным серверы, мы пакінулі сабе задзел на будучыню з магчымасцю захоўваць усе апошнія запампаваныя файлы на дыску.
    • Праверка ключа ў момант стварэння злучэння
  • Апцыянальна: струменевая апрацоўка кантэнту. Напрыклад, калі мы сціскаем усе файлы ў сэрвісе, то можна зрабіць разархіваванне непасрэдна ў гэтым модулі. Як следства: IO аперацыі зроблены там, дзе ім самае месца. Архіватар на Java лёгка будзе вылучаць шмат лішняй памяці, аднак перапісваць сэрвіс з бізнэс-логікай на ўмоўныя Rust/C++ можа апынуцца таксама неэфектыўным. У нашым жа выпадку выкарыстоўваюцца розныя працэсы (ці нават сэрвісы), а таму можна дастаткова эфектыўна падзяліць бізнес логіку і IO аперацыі.

Зручныя архітэктурныя патэрны

Падобная схема не вельмі падобная на раздачу статычнага кантэнту (бо мы не выгружаем увесь пакет статыкі кудысьці), аднак у рэальнасці падобны падыход менавіта і займаецца раздачай нязменных дадзеных. Больш за тое, гэтую схему можна абагульняць і на іншыя выпадкі, калі кантэнт не проста статычны, а можа быць прадстаўлены ў выглядзе набору нязменных і невыдаляльных блокаў (хоць яны і могуць дадавацца).

Як яшчэ прыклад (для замацавання): калі вы працавалі з Jenkins/TeamCity, то ведаеце, што абодва рашэнні напісаны на Java. Абодва яны ўяўляюць сабой Java-працэс, які займаецца як аркестрацыяй білдаў, так і мэнэджментам кантэнту. У прыватнасці, у іх абодвух ёсць задачы выгляду "перадаць файл/тэчку з сервера". Як прыклад: выдача артэфактаў, перадача зыходнага кода (калі агент не спампоўвае код напрамую з рэпазітара, а за яго гэта робіць сервер), доступ да логаў. Усе гэтыя задачы адрозніваюцца нагрузкай на IO. Гэта значыць атрымліваецца, што сервер, які адказвае за складаную бізнэс-логіку, заадно павінен умець эфектыўна прапіхваць праз сябе вялікія струмені дадзеных. І што самае цікавае, падобную аперацыю можна здэлегаваць таму ж nginx'у па роўна той жа схеме (хіба што ў запыт варта дадаваць ключ дадзеных).

Аднак калі вярнуцца да нашай сістэмы, то атрымліваецца такая схема:

Зручныя архітэктурныя патэрны

Як бачна, сістэма радыкальна ўскладнілася. Цяпер гэта не проста міні-працэс, які захоўвае файлы лакальна. Цяпер патрабуецца ўжо не самая простая падтрымка, кантроль за версіямі API і т.д. Таму пасля таго, як усе дыяграмы намаляваны, лепш за ўсё дэталёва ацаніць, ці варта пашыральнасць падобных выдаткаў. Аднак калі вы хочаце мець магчымасць пашыраць сістэму (у тым ліку і для працы з яшчэ большай колькасцю карыстальнікаў), то давядзецца пайсці на падобныя рашэнні. Затое, як вынік, сістэма архітэктурна гатова да павелічэння нагрузкі (практычна кожны кампанент можна кланаваць для гарызантальнага маштабавання). Сістэму можна абнаўляць без яе прыпынку (проста некаторыя аперацыі нязначна затармозяцца).

Як я ўжо казаў у самым пачатку, зараз шэраг інтэрнэт-сэрвісаў стаў атрымліваць павялічаную нагрузку. І некаторыя з іх проста сталі спыняць правільна працаваць. Па сутнасці сістэмы адмовілі менавіта ў той момант, калі бізнэс якраз павінен рабіць грошы. Гэта значыць замест адкладзенай дастаўкі, замест прапановы кліентам "расплануйце пастаўку на бліжэйшыя месяцы", сістэма проста сказала "ідзіце да канкурэнтаў". Уласна, гэта і ёсць кошт нізкай прадукцыйнасці: страты адбудуцца менавіта тады, калі прыбытак быў бы найвышэйшым.

Заключэнне

Усе гэтыя падыходы былі вядомы і раней. Той жа VK даўно ўжо выкарыстоўвае ідэю Static Content Hosting для выдачы карцінак. Куча анлайн-гульняў выкарыстоўваюць Sharding схему для падзелу гульцоў па рэгіёнах ці ж для падзелу гульнявых лакацый (калі сам свет адзіны). Event Sourcing падыход актыўна выкарыстоўваецца ў электроннай пошце. Большасць прыкладанняў трэйдараў, дзе бесперапынна прыходзяць дадзеныя, на самой справе пабудаваны на CQRS падыходзе, каб мець магчымасць фільтраваць атрыманыя дадзеныя. Ну і гарызантальна маштабаванне досыць даўно ўжываецца ў шматлікіх сэрвісах.

Аднак, што найважнейшае, усе гэтыя патэрны стала вельмі лёгка ўжываць у сучасных прыкладаннях (калі яны да месца, вядома). Аблокі прапануюць Sharding і гарызантальнае маштабаванне адразу, што нашмат лягчэй, чым заказваць розныя вылучаныя серверы ў розных датацэнтрах самастойна. CQRS стаў нашмат лягчэй хаця б з-за развіцця бібліятэк, такіх як RX. Гадоў 10 таму рэдкі web сайт мог бы падтрымаць такое. Event Sourcing наладжваецца таксама неверагодна лёгка за рахунак ужо гатовых кантэйнераў з Apache Kafka. Гадоў 10 таму гэта было б інавацыяй, зараз гэта штодзённасць. Аналагічна і з Static Content Hosting: з-за зручнейшых тэхналогій (у тым ліку і таму, што ёсць падрабязная дакументацыя і вялікая база адказаў), падобны падыход стаў яшчэ прасцейшым.

Як вынік, рэалізацыя шэрагу даволі складаных архітэктурных патэрнаў зараз стала нашмат прасцей, а значыць да яе лепш загадзя прыгледзецца. Калі ў дзесяцігадовым дадатку адмовіліся ад аднаго з рашэнняў вышэй па прычыне высокага кошту ўкаранення і эксплуатацыі, то зараз, у новым дадатку, ці ж пасля рэфактарынгу, можна зрабіць сэрвіс, які архітэктурна ўжо будзе як пашыраемым (з пункту гледжання прадукцыйнасці), так і гатовым да новых запытаў ад кліентаў (напрыклад, для лакалізацыі персанальных даных).

І самае важнае: не выкарыстоўвайце, калі ласка, гэтыя падыходы, калі ў вас простае дадатак. Так, яны прыгожыя і цікавыя, аднак для сайта з пікавым наведваннем у 100 чалавек часцяком можна абыйсціся класічным маналітам (прынамсі звонку, усярэдзіне ўсё можна разбіць на модулі і г.д.).

Крыніца: habr.com

Дадаць каментар