Чи завжди потрібні Docker, мікросервіси та реактивне програмування?

6 лютого
Денис Циплаков, Solution Architect, DataArt
Чи завжди потрібні Docker, мікросервіси та реактивне програмування?
У DataArt я працюю за двома напрямками. У першому допомагаю людям лагодити системи, зламані тим чи іншим чином і з різних причин. У другому допомагаю проектувати нові системи так, щоб у майбутньому вони не ламались або, якщо говорити реалістичніше, щоб зламати їх було складніше.

Якщо ви не робите щось принципово нове, наприклад, перший у світі інтернет-пошуковик або штучний інтелект для управління запуском ядерних ракет, створити дизайн гарної системи досить просто. Досить врахувати всі вимоги, подивитися на дизайн схожих систем і зробити приблизно так само, не припустившись при цьому грубих помилок. Звучить як надмірне спрощення питання, але давайте згадаємо, що на дворі 2019 рік, і “типові рецепти” дизайну систем існують практично для всього. Бізнес може підкидати складні технічні завдання — скажімо, обробити мільйон різнорідних PDF-файлів і вийняти з них таблиці з даними про витрати — але архітектура систем рідко відрізняється великою оригінальністю. Головне тут — не помилитися з визначенням того, яку саме систему ми будуємо, і не промахнутися з вибором технологій.

В останньому пункті регулярно виникають типові помилки, про деякі з них я розповім у статті.

У чому складність вибору технічного стеку? Додавання будь-якої технології у проект робить його більш складним і приносить якісь обмеження. Відповідно, слід додавати новий інструмент (фреймворк, бібліотеку), тільки коли він приносить більше користі, ніж завдає шкоди. У розмовах із членами команди про додавання бібліотек і фреймворків я часто жартома використовую такий прийом: “Хочеш додати нову залежність у проект — ставиш команді ящик пива. Якщо вважаєш, що ця залежність ящика пива не варта, не додавай”..

Припустимо, ми створюємо якийсь додаток, скажімо, на Java та для маніпуляції датами додаємо у проект бібліотеку TimeMagus (приклад вигаданий). Бібліотека відмінна, вона надає нам безліч можливостей, відсутніх у стандартній бібліотеці класів. Чим таке рішення може бути шкідливим? Давайте розберемо по пунктах можливі сценарії:

  1. Далеко не всі розробники знають нестандартну бібліотеку, поріг входження для нових розробників буде вище. Зростає шанс, що новий розробник зробить помилку при маніпуляціях із датою за допомогою невідомої йому бібліотеки.
  2. Збільшується розмір дистрибутива. Коли розмір середнього додатку на Spring Boot може легко розростися до 100 Мб, це зовсім не дрібниця. Я бачив випадки, коли заради одного методу в дистрибутив затягувалася бібліотека на 30 Мб. Це пояснювали так: “Цю бібліотеку я використовував у минулому проекті, і там є зручний метод”.
  3. У залежності від бібліотеки може помітно збільшуватися час старту.
  4. Розробник бібліотеки може закинути своє дітище, тоді бібліотека почне конфліктувати з новою версією Java, або в ній виявиться баг (викликаний, наприклад, зміною часових поясів), а ніякого патчу не випустять.
  5. Ліцензія бібліотеки в якийсь момент вступить у конфлікт із ліцензією вашого продукту (ви ж перевіряєте ліцензії для всіх-всіх продуктів, які використовуєте?).
  6. Jar hell — бібліотека TimeMagus потрібує останньої версії бібліотеки SuperCollections, потім через декілька місяців вам необхідно підключити бібліотеку для інтеграції зі стороннім API, яка не працює з останньою версією SuperCollections, а працює тільки з версією 2.x. Не підключати API ви не можете ніяк, а іншої бібліотеки для роботи з цим API немає.

З іншого боку, стандартна бібліотека надає нам досить зручні засоби для маніпуляції датами, і якщо у вас немає необхідності, наприклад, підтримувати якийсь екзотичний календар або обчислювати кількість днів від сьогодні до “другого дня третього Нового Місяця у попередній рік ширяючого орла”, можливо, варто утриматися від використання сторонньої бібліотеки. Навіть якщо вона є абсолютно чудовою і в масштабі проекту заощадить вам цілих 50 рядків коду.

Розглянутий приклад є досить простим, і, я гадаю, прийняти рішення тут нескладно. Але існує низка технологій, які є широко поширеними, в усіх на слуху, і їхня користь є очевидною, що робить вибір більш складним — вони дійсно надають серйозних переваг розробнику. Але це не завжди повинно бути приводом затягувати їх у свій проект. Давайте розглянемо деякі з них.

Docker

До появи цієї дійсно класної технології при розгортанні систем виникало безліч неприємних і складних питань, пов'язаних з конфліктом версій і незрозумілими залежностями. Docker дозволяє упакувати зліпок стану системи, викотити його у продакшн і там запустити. Це дозволяє уникнути згаданих конфліктів, що, звісно, чудово.

Раніше це робилось якимось жахливим чином, а деякі завдання не вирішувалися взагалі ніяк. Наприклад, у вас є додаток на PHP, який використовує бібліотеку ImageMagick для роботи з зображеннями, також вашому додатку потрібні специфічні настройки php.ini, а сам додаток хоститься за допомогою Apache httpd. Але є проблема: деякі регулярні рутини реалізовані запуском Python-скриптів із cron, і бібліотека, яку використовують ці скрипти, конфліктує з версіями бібліотек, що використовуються у вашому додатку. Докер дозволяє упакувати весь ваш додаток разом із настройками, бібліотеками та HTTP-сервером в один контейнер, який обслуговує запити на 80-му порті, а рутини — в інший контейнер. Все разом буде прекрасно працювати, і про конфлікт бібліотек можна буде забути.

Чи варто використовувати Docker для упаковки кожної програми? Моя думка: ні, не варто. На зображенні представлена ​​типова композиція докерізованного додатку, розгорнутого в AWS. Прямокутниками тут позначені шари ізоляції, які в нас є.

Найбільший прямокутник це фізична машина. Далі:

  • операційна система фізичної машини,
  • амазонівскій віртуалізатор,
  • ОС віртуальної машини,
  • докер-контейнер,
  • ОС контейнера,
  • JVM,
  • та Servlet-контейнер (якщо це веб-додаток), всередині якого вже міститься код вашої програми.

Тобто ми вже бачимо досить багато шарів ізоляції.

Ситуація буде виглядати ще гірше, якщо ми подивимося на абревіатуру JVM. JVM — це, як не дивно, Java Virtual Machine, тобто взагалі-то як мінімум одна віртуальна машина в Java в нас є завжди. Додавання сюди ще додаткового Docker-контейнера, по-перше, часто не дає дуже помітної переваги, тому що JVM сама собою вже непогано ізолює нас від зовнішнього оточення, по-друге, не обходиться даром.

Я взяв цифри з дослідження компанії IBM, якщо не помиляюся, дворічної давності. Коротко, якщо ми говоримо про дискові операції, використання процесора або доступ пам'яті, Docker майже не додає оверхедів (буквально частки відсотка), але якщо йдеться про network latency, затримки є цілком відчутними. Вони не гігантські, але в залежності від того, який у вас додаток, можуть вас неприємно здивувати.

Плюс до всього Docker з'їдає додаткове місце на диску, займає частину пам'яті, додає start up time. Всі три моменти є некритичними для більшості систем — зазвичай і місця на диску, і пам'яті досить багато. Час запуску теж, як правило, не є критичною проблемою, головне виконувати цю програму. Але все ж виникають ситуації, коли пам'яті може не вистачати, і сумарний час старту системи, що складається з двадцяти залежних сервісів, уже стає досить великим. До того ж, це позначається на вартості хостингу. І якщо ви займаєтеся будь-яким високочастотним трейдингом, Docker вам категорично не підходить. У загальному випадку будь-який додаток, чутливий до затримок у мережі в межах до 250-500 мс, краще не докерізувати.

Також з докером помітно ускладнюється розбір проблем у мережевих протоколах, не лише ростуть затримки, але й змінюються всі таймінги.

Коли Docker дійсно потрібний?

Коли в нас різні версії JRE, і при цьому було б добре JRE тягти з собою. Бувають випадки, коли для запуску потрібна певна версія Java (не “остання Java 8”, а щось більш специфічне). В цьому випадку добре упакувати JRE разом з додатком і запускати як контейнер. У принципі, зрозуміло, що різні версії Java можна поставити на цільову систему за рахунок JAVA_HOME і т.ін. Але Docker у цьому сенсі помітно зручніше, тому що ви знаєте точну версію JRE, все упаковано разом і з іншого JRE програма не запуститься навіть випадково.

Також Docker необхідний, якщо ви маєте залежності на якісь бінарні бібліотеки, наприклад, для обробки зображень. У цьому випадку непоганою ідеєю може виявитись упаковка всіх необхідних бібліотек разом із самим Java-додатком.

Наступний кейс відноситься до системи, що представляє собою складний композит з різних сервісів, написаних різними мовами. У вас є шматочок на Node.js, є частина на Java, бібліотека на Go, а на додачу — якийсь Machine Learning на Python. Весь цей зоопарк потрібно довго й ретельно налаштовувати, щоб навчити його елементи бачити один одного. Залежності, шляхи, IP-адреси — все це треба розписати й акуратно підняти в продакшені. Звісно, в цьому випадку Docker вам чудово допоможе. Більш того, робити це без його допомоги просто болісно.

Докер може забезпечити деяку зручність, коли для запуску програми вам у командному рядку треба вказувати безліч різних параметрів. З іншого боку, з цим чудово справляються bash-скрипти, часто з одного рядка. Самі вирішуйте, що краще використовувати.

Останнє, що спадає на думку — ситуація, коли ви використовуєте, скажімо, Kubernetes, і вам потрібно робити оркестрації системи, тобто підіймати якусь кількість різних мікросервісів, що автоматично масштабуються за певними правилами.

У всіх інших випадках Spring Boot виявляється достатньо, щоб упакувати все в один jar-файл. І, в принципі, спрінгбутовий jar — непогана метафора Docker-контейнера. Це, зрозуміло, не одне й те саме, але за ступенем зручності розгортання вони дійсно схожі.

Kubernetes

Що робити, якщо ми використовуємо Kubernetes? Почнемо з того, що ця технологія дозволяє деплоїти на різні машини велику кількість мікросервісів, керувати ними, робити autoscaling тощо. Однак існує досить багато додатків, які дозволяють керувати оркестрацією, наприклад, Puppet, CF engine, SaltStack та інші. Сам же Kubernetes, безумовно, є хорошим, але може додавати значного overhead, жити з яким готовий далеко не кожен проект.

Мій улюблений інструмент — Ansible у поєднанні з Terraform там, де це потрібно. Ansible є досить простим декларативним легким інструментом. Він не вимагає встановлення спеціальних агентів і має цілком зрозумілий синтаксис конфігураційних файлів. Якщо ви знайомі з Docker compose, одразу побачите секції, які перегукуються. І якщо ви використовуєте Ansible, немає необхідності докерізувати — можна розгортати системи більш класичними засобами.

Зрозуміло, що це все одно різні технології, але є якась кількість задач, у яких вони є взаємозамінними. І сумлінний підхід до проектування вимагає аналізу того, яка технологія більше підійде для розроблюваної системи та краще відповідатиме їй через декілька років.

Якщо кількість різних сервісів у вашій системі є невеликою, а їхня конфігурація —відносно простою, наприклад, у вас усього один jar-файл, при цьому ви не бачите якогось раптового, вибухового зростання складності, можливо, варто обійтися класичними механізмами розгортання.

Тут виникає запитання “зачекайте, як один jar-файл?”. Система ж повинна складатись із безлічі якомога більше атомарних мікросервісів! Давайте розберемо, кому й що система винна разом з мікросервісами.

МІКРОСЕРВІСИ

Перш за все, мікросервіси дозволяють досягти більшої гнучкості та масштабованості, дозволяють гнучко версіонувати окремі частини системи. Припустимо, в нас є якийсь додаток, який у продакшені вже багато років. Функціонал зростає, але ми не можемо нескінченно розвивати його екстенсивним чином. Наприклад.

У нас є додаток на Spring Boot 1 і Java 8. Прекрасне, стабільне поєднання. Але на дворі 2019 рік і, хочемо ми того чи ні, потрібно рухатися в бік Spring Boot 2 і Java 12. Навіть відносно простий перехід великої системи на нову версію Spring Boot може бути вельми трудомістким, а про стрибок над прірвою із Java 8 на Java 12 я й говорити не хочу. Тобто в теорії все просто: мігруємо, правимо проблеми, що виникли, все тестуємо та запускаємо у production. На практиці це може означати декілька місяців роботи, що не приносить бізнесу нового функціоналу. Трішечки переїхати на Java 12, як ви розумієте, теж не вийде. Тут нам може допомогти мікросервісна архітектура.

Ми можемо виділити якусь компактну групу функцій нашого застосування в окремий сервіс, мігрувати цю групу функцій на новий технічний стек і за відносно короткий час викотити це у продакшен. Повторювати процес шматочок за шматочком до повного вичерпання старих технологій.

Також мікросервіси дозволяють забезпечити fault isolation, коли один компонент, що впав, не руйнує всю систему.

Мікросервіси дозволяють нам мати гнучкий технічний стек, тобто писати все монолітно однією мовою та однією версією, а за необхідністю використовувати різний технічний стек для окремих компонент. Зрозуміло, краще, коли ви використовуєте однорідний технічний стек, але це не завжди можливо, і в такому випадку мікросервіси можуть виручити.

Також мікросервіси дозволяють технічним способом вирішити ряд менеджерських проблем. Наприклад, коли ваша велика команда складається з окремих груп, які працюють у різних компаніях (сидять у різних часових зонах і розмовляють різними мовами). Мікросервіси допомагають ізолювати це організаційне розмаїття по компонентах, які будуть розвиватися окремо. Проблеми однієї частини команди залишатимуться всередині одного сервісу, а не розповзатимуться по всьому додатку.

Але мікросервіси не є єдиним способом вирішення перелічених проблем. Як не дивно, декілька десятків років тому для половини з них люди придумали класи, а трохи пізніше — компоненти та патерн Inversion of Control.

Якщо ми подивимося на Spring, то побачимо, що фактично це мікросервісна архітектура всередині Java-процесу. Ми можемо оголошувати компонент, який, по суті, є сервісом. Ми маємо можливість робити lookup через @Autowired, засоби управління життєвим циклом компоненти та можливість роздільно конфігурувати компоненти з десятка різних джерел. У принципі, ми отримуємо майже все те саме, що маємо з мікросервісами — тільки всередині одного процесу, що істотно скорочує витрати. Звичайний Java-class це той же API-контракт, який точно таким же чином дозволяє ізолювати деталі реалізації.Строго кажучи, в Java-світі мікросервіси найбільше схожі на OSGi — там ми маємо практично точну копію всього, що є в мікросервісах, хіба що крім можливості використання різних мов програмування і виконання коду на різних серверах. Але навіть залишаючись у межах можливостей Java-класів, ми маємо досить потужний інструмент для вирішення великої кількості проблем з ізоляцією.

Навіть в “менеджерському” сценарії з ізоляцією команди ми можемо створити окремий репозиторій, який містить окремий Java-модуль з чітким зовнішнім контрактом і набором тестів. Це істотно скоротить можливості однієї команди за необережністю ускладнити життя іншій команді.

Мені неодноразово доводилося чути, що ізолювати деталі реалізації без мікросервісів ніяк не можна. Але я можу відповісти, що вся software-індустрія як раз про ізоляцію реалізації. Для цього була придумана спочатку підпрограма (в 50-х роках минулого століття), потім функції, процедури, класи, ще пізніше мікросервіси. Але те, що мікросервіси у цьому ряду з'явилися останніми, не робить їх найвищою точкою розвитку та не зобов'язує нас з вами завжди вдаватися до їхньої допомоги.

При використанні мікросервісів треба також брати до уваги, що виклики між ними займають деякий час. Часто це неважливо, але мені доводилося бачити випадок, коли замовнику необхідно було вмістити час відповіді системи у 3 секунди. Це було контрактне зобов'язання для підключення до сторонньої системи. Ланцюжок викликів проходив через декілька десятків атомарних мікросервісів, і накладні витрати на здійснення HTTP-викликів ніяк не дозволяли втиснутись у 3 секунди. Загалом треба розуміти, що будь-який поділ монолітного коду на декілька сервісів неминуче погіршує загальну продуктивність системи. Просто тому, що дані не можуть переміщуватися між процесами та серверами «безкоштовно».

Коли мікросервіси все ж потрібні?

У яких же випадках монолітний додаток дійсно потрібно розбити на декілька мікросервісів?

По-перше, коли у функціональних галузях присутнє незбалансоване використання ресурсів. Наприклад, у нас є група API-викликів, які виконують обчислення, що вимагають великої кількості процесорного часу. І є група API-викликів, які виконуються дуже швидко, але вимагають для виконання тримати в пам'яті громіздку структуру даних на 64 Гб. Для першої групи нам потрібна група машин, що мають загалом 32 процесора, для другої достатньо однієї машини (ОК, нехай буде дві машини для відмовостійкості) з 64 Гб пам'яті. Якщо ми маємо монолітний додаток, то нам на кожній машині будуть потрібні 64 Гб пам'яті, що збільшує вартість кожної машини. Якщо ж ці функції розділені на два окремі сервіси, ми можемо заощадити ресурси за рахунок оптимізації сервера під конкретну функцію. Конфігурація серверів може виглядати наприклад так:

Мікросервіси також потрібні, якщо ми маємо серйозно масштабувати якусь вузьку функціональну область. Наприклад, сотня API-методів викликаються з періодичністю 10 разів на секунду, а, скажімо, чотири API-методи викликаються 10 тисяч разів на секунду. Масштабувати всю систему часто немає необхідності, тобто ми, звісно, можемо розмножити всі 100 методів на безліч серверів, але це, як правило, відчутно дорожче та складніше, ніж масштабування вузької групи методів. Ми можемо виділити ці чотири виклики в окремий сервіс і масштабувати тільки його на велику кількість серверів.

Також зрозуміло, що мікросервіс може нам знадобитись, якщо окрема функціональна область у нас написана, наприклад, на Python. Тому що якась бібліотека (скажімо, для Machine Learning) виявилася доступною лише на Python, і ми хочемо виділити її в окремий сервіс. Також має сенс зробити мікросервіс, якщо якась частина системи схильна до збоїв. Добре, звісно, писати код так, щоб збоїв не було у принципі, але причини можуть бути й зовнішніми. Та й від власних помилок ніхто не застрахований. У цьому випадку баг можна ізолювати всередині окремого процесу.

Якщо у вашому додатку нічого з перерахованого вище немає і в доступній для огляду перспективі не передбачається, швидше за все, монолітний додаток вам підійде найкраще. Єдине — рекомендую писати його так, щоб не пов'язані один з одним функціональні області не залежали одна від іншої в коді. Щоб за необхідності не пов'язані між собою функціональні області можна було відокремити одну від іншої. Втім, це завжди гарна рекомендація, дотримання якої підвищує внутрішню несуперечливість та привчає акуратно формулювати контракти модулів.

РЕАКТИВНА АРХІТЕКТУРА ТА РЕАКТИВНЕ ПРОГРАМУВАННЯ

Реактивний підхід є річчю відносно новою. Моментом його появи можна вважати 2014 рік, коли було опубліковано The Reactive Manifesto. Вже за два роки після публікації маніфесту він був у всіх на слуху. Це дійсно революційний підхід до проектування систем. Його окремі елементи використовувалися десятки років тому, але всі принципи реактивного підходу разом, у тому вигляді, як це викладено в маніфесті, дозволили індустрії зробити серйозний крок уперед до проектування більш надійних і високопродуктивних систем.

На жаль, реактивний підхід до проектування часто плутають із реактивним програмуванням. На запитання, навіщо в проекті використовувати реактивну бібліотеку, мені доводилося чути відповідь: “Це реактивний підхід, ти що реактивного маніфесту не читав!?”. Маніфест я читав і підписував, але, от біда, реактивне програмування не має до реактивного підходу до проектування систем прямого відношення, крім того, що в назвах обох є слово “реактивний”. Можна легко зробити реактивну систему, використовуючи на 100% традиційний набір інструментів, і створити абсолютно не реактивну систему, використовуючи новітні напрацювання функціонального програмування.

Реактивний підхід до проектування систем — це досить загальний принцип, який можна застосовувати до дуже багатьох систем — він безумовно заслуговує окремої статті. Тут же я хотів би розповісти про можливість застосування реактивного програмування.

У чому суть реактивного програмування? Спочатку розглянемо, як працює звичайна нереактивна програма.

Ниткою виконується якийсь код, який робить якісь обчислення. Потім настає необхідність провести якусь операцію введення-виведення, наприклад, HTTP-запит. Код надсилає мережею пакет, і нитка блокується в очікуванні відповіді. Відбувається переключення контексту, і на процесорі починає виконуватись інша нитка. Коли мережею приходить відповідь, контекст знову перемикається, і перша нитка продовжує виконання, обробляючи відповідь.

Як такий самий фрагмент коду працюватиме в реактивному стилі? Нитка виконує обчислення, посилає HTTP-запит і замість того, щоб блокуватися і при отриманні результату синхронно обробити його, описує код (залишає callback), який повинен бути виконаний в якості реакції (звідси слово реактивний) на результат. Після цього нитка продовжує роботу, роблячи якісь інші обчислення (можливо, як раз обробляючи результати інших HTTP-запитів) без перемикання контексту.

Основною перевагою тут є відсутність перемикання контексту. Залежно від архітектури системи ця операція може займати декілька тисяч тактів. Тобто для процесора з тактовою частотою 3 Ghz перемикання контексту займе не менше мікросекунди, насправді, за рахунок інвалідації кеша і т.ін., швидше, кілька десятків мікросекунд. Кажучи практично, для середнього Java-додатку, який обробляє багато коротких HTTP-запитів, приріст продуктивності може скласти 5–10%. Не можна сказати, що це вирішально багато, але, скажімо, якщо ви орендуєте 10 серверів по $50 на місяць кожен — ви зможете заощадити $500 на місяць на хостингу. Звісно, це не супербагато, але вистачить, щоб декілька разів напоїти команду пивом.

Отже, вперед за пивом? Давайте розглянемо ситуацію докладно.

Програму в класичному імперативному стилі значно простіше читати, розуміти та як наслідок налагоджувати та модифікувати. В принципі, добре написана реактивна програма теж досить зрозуміло виглядає, проблема в тому, що написати хорошу, зрозумілу не тільки автору коду тут і зараз, але й іншій людині через півтора роки, реактивну програму набагато складніше. Але це досить слабкий аргумент, я не сумніваюся, що для читачів статті писати простий і зрозумілий реактивний код не становить проблеми. Давайте розглянемо інші аспекти реактивного програмування.

Далеко не всі операції введення-виведення підтримують неблокуючі виклики. Наприклад, JDBC на поточний момент не підтримує (в цьому напрямку йдуть роботи см. ADA, R2DBC, але поки що все це не вийшло на рівень релізу). Оскільки зараз 90% всіх додатків ходять до баз даних, використання реактивного фреймворка автоматично перетворюється з переваги на недолік. Для такої ситуації є рішення — обробляти HTTP-виклики в одному пулі потоків, а звернення до бази даних — в іншому пулі потоків. Але при цьому процес значно ускладнюється, і без гострої необхідності я б так робити не став.

Коли варто використовувати реактивний фреймворк?

Використовувати фреймворк, що дозволяє виробляти реактивну обробку запитів, варто, коли запитів ви маєте багато (кілька сотень секунд і більше) і при цьому на обробку кожного з них витрачається дуже невелика кількість тактів процесора. Найпростіший приклад — проксінг запитів або балансування запитів між сервісами або якась досить легка обробка відповідей, які прийшли від іншого сервісу. Де під сервісом ми розуміємо щось, запит до чого можна послати асинхронно, наприклад, за допомогою HTTP.

Якщо ж при обробці запитів вам потрібно буде блокувати нитку в очікуванні відповіді, або обробка запитів займає відносно багато часу, наприклад, треба конвертувати картинку з одного формату в інший, писати програму в реактивному стилі, можливо, не варто.

Також не варто без необхідності писати в реактивному стилі складні багатокрокові алгоритми обробки даних. Наприклад, задачу “знайти в каталозі та всіх його підкаталогах файли з певними властивостями, конвертувати їхній вміст і переслати іншому сервісу” можна реалізувати у вигляді набору асинхронних викликів, але, в залежності від деталей завдання, така реалізація може виглядати зовсім непрозорою і при цьому не давати помітних переваг перед класичним послідовним алгоритмом. Скажімо, якщо ця операція повинна запускатися раз на добу, і немає великої різниці, буде вона виконуватися 10 або 11 хвилин, можливо, варто вибрати не найкращу, а більш просту реалізацію.

ВИСНОВОК

На закінчення хочеться сказати, що будь-яка технологія завжди призначена для вирішення конкретних задач. І якщо при проектуванні системи в доступній для огляду перспективі ці завдання перед вами не стоять, швидше за все, ця технологія вам тут і зараз не потрібна, якою б чудовою вона при цьому не була.