1. Знайомство з архітектурою MVC

Найпопулярніша архітектура застосунків, про яку знає кожен програміст, — це MVC. MVC розшифровується як Model-View-Controller.

Не стільки архітектура застосунків, як архітектура компонентів застосунку, але до цього нюансу ми повернемося пізніше. Що таке MVC?

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

  • Модель (Model) надає дані та реагує на команди контролера, змінюючи свій стан.
  • Подання (View) відповідає за відображення даних моделі користувача, реагуючи на зміни моделі.
  • Контролер (Controller) інтерпретує дії користувача, сповіщаючи модель необхідність змін.

Цю модель вигадали ще в 1978 (!) році. Так, проблеми з правильною архітектурою ПЗ були актуальні ще 50 років тому. Ось як ця модель описується діаграмою в оригіналі:

Модель надає дані та методи роботи з ними: запити до бази даних, перевірку на коректність. Модель не залежить від подання (не знає як візуалізувати дані) та контролера (не має точок взаємодії з користувачем), надаючи доступ до даних та управління ними.

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

Подання відповідає за отримання необхідних даних з моделі та надсилає їх користувачу. Подання не опрацьовує введені дані користувача.

Контролер забезпечує “зв'язок” між користувачем та системою. Контролює та спрямовує дані від користувача до системи та навпаки. Використовує модель та подання для реалізації необхідної дії.

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

2. Архітектура MVC у вебі

Ідея, яка лежить в основі конструкційного шаблону MVC, дуже проста: потрібно чітко розділяти відповідальність за різне функціонування наших застосунків:

Model — обробка даних і логіка програми.

View — надання даних користувачеві в будь-якому форматі, що підтримується.

Controller — обробка запитів користувача та виклик відповідних ресурсів.

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

Контролер (Controller)

Користувач натискає на різні елементи на сторінці в браузері, в результаті чого браузер надсилає різні HTTP запити: GET, POST або інші. До контролера можна віднести браузер та JS-код, які працюють усередині сторінки.

Основна функція контролера в цьому випадку — викликати методи в необхідних об'єктів, управляти доступом до ресурсів виконання завдань, які вказано користувачем. Зазвичай контролер викликає відповідну модель завдання і обирає відповідний вид.

Модель (Model)

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

Припустимо, у нас є інтернет-магазин, який торгує книгами, тоді людина — це лише користувач програми чи ще й автор книги? Ці важливі питання слід вирішити під час проєктування моделі.

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

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

Модель містить найбільш важливу частину логіки нашого додатку — логіку, яка вирішує завдання, з яким ми маємо справу (форум, магазин, банк тощо). Контролер містить переважно організаційну логіку для самої програми (як твій Project Manager).

Вид (View)

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

Вебзастосунок зазвичай складається з набору контролерів, моделей та видів (views). Контролер може бути лише на бекенді, але також може бути варіант кількох контролерів, коли його логіка розмазується і по фронтенду теж. Хороший приклад такого підходу – будь-який мобільний застосунок.

3. Приклад MVC у інтернеті

Припустимо, тобі потрібно розробити інтернет-магазин, який займатиметься продажем книг. Користувач може виконувати такі дії: переглядати книги, реєструватися, купувати, додавати пункти до поточного замовлення, відзначати книги, що сподобалися, і купувати їх.

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

Також потрібні набори views: список книг, інформація про одну книгу, кошик, список замовлень. Кожна сторінка вебзастосунку — це фактично і є окремий view, який відображає користувачеві певний аспект моделі.

Давайте подивимося, що станеться, якщо користувач відкриє список рекомендованих магазином книг для перегляду назв. Всю послідовність дій можна описати у вигляді 6 кроків:

Приклад MVC у Інтернеті

Кроки:

  1. Користувач натискає на посилання «рекомендовані» і браузер надсилає запит на, скажімо, /books/recommendations.
  2. Контролер перевіряє запит: користувач повинен бути залогінений. Або в нас мають бути добірки книг для незалогінених користувачів. Потім контролер звертається до моделі та просить її віддати список книг, рекомендованих користувачеві N.
  3. Модель звертається до бази даних, дістає звідти інформацію про книжки: популярні зараз книжки, книжки, куплені користувачем, книжки, куплені його друзями, книжки з його wish list. На основі цих даних модель будує список із 10 рекомендованих книг та повертає їх контролеру.
  4. Контролер одержує список рекомендованих книг і дивиться на нього. На цьому етапі контролер ухвалює рішення! Якщо книг мало або список взагалі порожній, він запитує список книг для незалогіненого користувача. Якщо зараз триває акція, то контролер може додати до списку акційні книги.
  5. Контролер визначається з тим, яку сторінку показати користувачеві. Це може бути сторінка з помилкою, сторінка зі списком книг, сторінка привітання щодо того, що користувач став мільйонним відвідувачем.
  6. Сервер віддає клієнту сторінку (view), обрану контролером. Вона заповнюється потрібними даними (ім'я користувача, список книг) і йде до клієнта.
  7. Клієнт отримує сторінку та відображає її користувачеві.

У чому переваги такого підходу?

Найочевидніша перевага, яку ми отримуємо від використання концепції MVC – це чіткий поділ логіки представлення (інтерфейсу користувача) та логіки програми (серверна частина).

Друга перевага – це поділ серверної частини на дві: розумна модель (виконавець) і контролер (центр прийняття рішень).

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

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

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

Окрім ізолювання видів від логіки програми, концепція MVC істотно зменшує складність великих застосунків. Код виходить набагато структурованішим, і таким чином полегшується підтримка, тестування та повторне використання рішень.

Розуміючи концепцію MVC, ти як розробник усвідомлюєш, де потрібно додати сортування списку книг:

  • На рівні запиту до бази даних.
  • На рівні бізнес-логіки (моделі).
  • На рівні бізнес-логіки (контролер).
  • У представленні — на стороні клієнта.

Це не риторичне питання. Прямо зараз подумай: де і чому потрібно додати код сортування списку книг?

4. Класична модель MVC

Взаємодія між компонентами MVC реалізується по-різному у вебзастосунках та мобільних застосунках. Це відбувається тому, що вебзастосунок живе недовго, обробляє один запит користувача і завершується, а мобільний застосунок обробляє багато запитів без перезапуску.

У вебзастосунках зазвичай використовується "пасивна" модель, а в мобільних – "активна". Активна модель, на відміну від пасивної, дозволяє підписуватись і отримувати повідомлення про зміну в ній. У випадку з вебзастосунками цього не потрібно.

Приблизно так виглядає взаємодія компонентів у різних моделях:

У мобільних програмах (активна модель) використовуються події та механізм підписки на події. При такому підході view (вид) підписується на зміни моделі. Потім, коли відбувається якась подія (наприклад, користувач натискає кнопку), викликається контролер. Він дає і моделі команду зміну даних.

Якщо якісь дані змінилися, модель генерує подію про зміну цих даних. Всі view, які підписалися на цю подію (для яких важлива зміна саме цих даних), отримують цю подію та оновлюють дані у своєму інтерфейсі.

У вебзастосугках все організовано трохи інакше. Основна технічна відмінність – це те, що клієнт не може отримувати повідомлення з боку сервера з ініціативи сервера.

Тому контролер у вебзастосунку зазвичай не надсилає view будь-які повідомлення, а віддає клієнту нову сторінку, яка технічно є новим view або навіть новим клієнтським додатком (якщо одна сторінка нічого не знає про іншу).

Нині ця проблема частково вирішена за допомогою таких підходів:

  • Регулярне опитування сервера щодо зміни важливих даних (раз на хвилину або частіше).
  • WebSocket-и дозволяють клієнту підписуватися на повідомлення сервера.
  • Web-push-повідомлення з боку сервера.
  • Протокол HTTP/2 дозволяє серверу ініціювати надсилання повідомлень клієнту.