1. Архітектура програми
Цей курс розрахований на новачків, тому що проєктувати архітектуру серйозної програми ти ще довго не будеш. Але не треба засмучуватися, хороша архітектура – це скоріше виняток, аніж правило. Дуже складно обрати правильну архітектуру програми до створення програми.
Ось приклади популярних архітектур великих серверних програм:
- Багатошарова архітектура (Layered Architecture).
- Багаторівнева архітектура (Tiered architecture).
- Сервіс-орієнтована архітектура (Service Oriented Architecture – SOA).
- Мікросервісна архітектура (Microservice Architecture).
Кожна з них має свої плюси та свої мінуси. Але вивчення їх тобі нічого не дасть. Архітектура – це відповідь на питання «як організувати взаємодію тисяч об'єктів усередині системи». І доки ти на власному досвіді не відчуєш усю складність проблеми, ти не зможеш зрозуміти і всю багатогранність рішення.
Всі додатки використовують якусь архітектуру або хоча б вдають, що використовують. Тому знання популярних підходів до проєктування програм дозволить тобі швидше і краще зрозуміти, як влаштовано застосунок. А отже, вносити зміни саме в ті місця, в які потрібно.
А що означає «вносити зміни, куди потрібно?». Чи є місця, куди не потрібно вносити зміни? Саме так.
Щоб додати конкретики, припустимо, що ти працюєш над середнім backend-проєктом. Він пишеться вже протягом 5 років командою із 20 осіб. На проєкт витрачено 100 людино-років, у ньому приблизно 100 тисяч рядків коду. Сумарно він складається із двох тисяч класів, які розбиті на 10 модулів різного розміру.
Додамо трохи суворої дійсності. Логіка деяких завдань розмазана кількома модулями. Також бізнес-логіка може бути у фронтенді (який написано на JavaScript) та/або записана у вигляді stored procedure прямо в базі даних. Ти все ще впевнений, що одразу зможеш визначити місце, куди саме вносити зміни?
Це не якийсь кошмар, який я вигадав, щоб тебе налякати. Це типовий проєкт. Буває ще й гірше. Чому так відбувається? Причин може бути безліч, але майже завжди є такі:
- На проєкті працює купа людей – кожен із них бачить його трохи по-своєму.
- За 5 років у проєкті змінилося 10 людей, новачки не стали сильно в ньому розбиратися.
- Створення софту – це постійне внесення змін, які постійно змінюють.
- П'ять років тому, коли визначалися з архітектурою, задум проєкту був дещо іншим.
Але найголовніше, щоб незалежно від архітектури проєкту, всі програмісти, які працюють над ним, дотримувалися одного і того ж розуміння, як цей проєкт влаштовано. Почнемо з найпростішого поняття – клієнт-серверної архітектури.
2. Концепція взаємодії клієнт-сервер
Зараз ми розберемося з концепцією, яка лежить в основі архітектури клієнт-сервер і дозволить вам краще зрозуміти, як організовано взаємодію мільйонів програм у мережі інтернет.
Із назви зрозуміло, що в цій концепції беруть участь дві сторони: клієнт і сервер. Тут все як у житті: клієнт – це замовник тієї чи іншої послуги, а сервер – постачальник послуг. Клієнт і сервер фізично являють собою програми, наприклад, типовим клієнтом є браузер.
Серед серверів можна навести такі приклади:
- Вебсервери, наприклад, Tomcat.
- Сервери баз даних, наприклад, MySQL.
- Платіжні шлюзи, наприклад, Stripe.
Клієнт із сервером зазвичай спілкуються через інтернет, хоча можуть працювати і в одній локальній мережі і в будь-яких інших типах мереж. Спілкування відбувається за стандартними протоколами, такими як HTTP, FTP або більш низькорівневими, такими як TCP або UDP.
Протокол зазвичай обирається під тип послуги, яку надають сервери. Наприклад, якщо це відеозв'язок, зазвичай використовується UDP.
Пам'ятаєш, як працює Tomcat та його сервлети? Сервер отримує HTTP-повідомлення, розпаковує його, дістає звідти потрібну інформацію та передає сервлету на обробку. Потім результат обробки упаковує назад у HTTP-response та надсилає клієнту.
Це і є типова взаємодія клієнт-сервер. Браузер – це вебклієнт, а Tomcat – вебсервер. Tomcat навіть так і називається – вебсервер.
З іншого боку, важлива не назва, а сутність – розподіл ролей між програмами. Твій JS-скрипт, що працює в HTML-сторінці, цілком можна назвати клієнтом, а твій сервлет - сервером. Адже вони працюють у парі в межах концепції клієнт-сервер.
3. Важливий аспект
Ще варто відзначити, що в основі взаємодії клієнт-сервер лежить принцип того, що таку взаємодія починає клієнт: сервер лише відповідає клієнту і повідомляє про те, чи може він надати послугу клієнту і якщо може, то на яких умовах.
Не має значення, де фізично знаходиться клієнт і сервер. Клієнтське програмне забезпечення та серверне програмне забезпечення зазвичай встановлено на різних машинах, але також вони можуть працювати і на одному комп'ютері.
Цю концепцію розробили як перший крок у бік спрощення складної системи. Має такі сильні сторони:
- Спрощення логіки: сервер нічого не знає про клієнта і як він використовуватиме його дані надалі.
- Можуть бути слабкі клієнти: всі ресурсомісткі завдання можна перенести на сервер.
- Незалежний розвиток коду клієнтів та коду сервера.
- Багато різних клієнтів, приклад - Tomcat і різні браузери.
Найбільш базовий варіант взаємодії клієнта та сервера представлено на малюнку:
Тут важливо відзначити дві деталі. По-перше, з картинки видно, що до одного сервера може звертатися безліч клієнтів. По-друге, вони можуть до нього звертатися одночасно. Це також важлива частина роботи сервера.
Один клієнт зазвичай взаємодіє з одним користувачем, тому часто навіть авторизація не потрібна. Однак сервер обробляє запити тисяч клієнтів, і під час розробки коду для нього потрібно вміти відрізняти авторизацію від автентифікації.
Важливо і те, що сервер обробляє тисячі запитів паралельно. А це означає, що під час розробки коду для бекенда тобі завжди потрібно буде думати над завданням одночасного доступу до ресурсів. Також у коду сервера дуже висока ймовірність race condition (перегони потоків), deadlock (взаємне блокування потоків).
Життєвий цикл важливих об'єктів потрібно обов'язково контролювати:
Ти не можеш просто так запустити на сервері новий потік через new Thread().start(). Натомість тобі потрібно мати ThreadPool, який буде нишпорити між усіма сервісними потоками.
Також не можна просто запустити асинхронне завдання, адже вони теж виконуються в окремих потоках. Під час створення такої задачі ти завжди маєш знати, який пул-потоків її виконує і що станеться, якщо такий пул переповниться.
Усі роботи з файлами та директоріями потрібно виконувати через try-with-resources. Якщо у звичайному застосунку ти забув закрити потік чи файл, хіба це проблема? Закриється сам по собі під час виходу з програми. А от якщо ти десь у коді забув закрити файл на сервері, і твій сервер працює місяцями… Скоро таких незакритих файлів накопичаться тисячі, і ОС перестане відкривати нові файли на читання (роботу з файлами контролює ОС). Тімлід вас по голівці не погладить.
4. Архітектура «клієнт-сервер»
Іще один важливий момент. Архітектура клієнт-сервер визначає лише загальні принципи взаємодії між комп'ютерами, деталі взаємодії визначають різні протоколи.
Ця концепція (клієнт-сервер) говорить нам, що потрібно розділяти машини в мережі на клієнтські, яким завжди щось треба і на серверні, які дають те, що треба. У цьому взаємодія завжди починає клієнт, а правила, за якими відбувається взаємодія, визначає протокол.
Існує два види архітектури взаємодії клієнт-сервер: перший отримав назву дворівнева архітектура клієнт-серверної взаємодії, другий – багаторівнева архітектура клієнт-сервер (іноді його називають трирівневою архітектурою або триланковою архітектурою, але це окремий випадок).
Принцип роботи дворівневої архітектури взаємодії клієнт-сервер полягає в тому, що обробка запиту відбувається на одному сервері без звернення до іншим серверів у процесі цієї обробки.
Дворівневу модель взаємодії клієнт-сервер можна намалювати у вигляді простої схеми.
Тут видно, що перший рівень – це все, що стосується клієнта, а другий рівень – це все, що стосується сервера.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ