Архітектура

C4: як змінювати масштаб архітектурного пояснення

У главі 13 ми розглянули документ архітектури програмної системи — SAD (Software Architecture Document). Він збирає рішення, межі й важливі потоки. Тепер…

Глава 14Заморожене українське видання

У главі 13 ми розглянули документ архітектури програмної системи — SAD (Software Architecture Document). Він збирає рішення, межі й важливі потоки. Тепер розглянемо, як описати одну систему з різною глибиною, щоб власник продукту не мусив читати функції класів, а автор компонента бачив потрібні йому межі.

C4 — це модель архітектурних абстракцій і пов’язаних із ними видів опису. Назва нагадує чотири рівні: Context, Containers, Components, Code — контекст, застосунки та сховища, компоненти й код. Вони послідовно звужують поле зору від системи в її оточенні до деталей реалізації. Це не чотири обов’язкові документи, не організаційний процес і не вимога малювати схеми. Коли графіка недоступна або зайва, ті самі межі й відносини можна пояснити текстом і таблицями.

Після глави читач уміє:

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

Навіщо змінювати масштаб опису

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

Уявімо карту міста й маршрут від дверей до конкретного офісу. На огляді міста не потрібні номери кабінетів; для пошуку офісу недостатньо знати назву країни. Аналогія обмежена: архітектурний рівень — це не просто більше пікселів на карті. Кожне наближення змінює типи елементів, межу та запитання, на які відповідає опис. Не можна вставити деталь коду в опис контексту тільки тому, що її легше знайти.

Офіційний сайт автора C4 Саймона Брауна описує модель як ієрархію абстракцій і відповідних видів діаграм, а також додаткові системні, динамічні та подання фізичного розгортання (deployment). Він прямо називає модель незалежною від нотації та інструмента. C4 допомагає описувати статичну структуру; процеси, робочі потоки, моделі даних і стани можна додати окремими описами, коли це потрібно офіційний сайт C4, поширені запитання про модель C4.

Отже, назва рівня задає масштаб опису, а не «хто має право говорити» про архітектуру. Контекст потрібен, щоб побачити відповідальність і залежності; нижчі рівні потрібні, коли читач має знайти конкретну межу чи перевірити правило.

Рівень 1: контекст програмної системи

Межа системи на контекстній схемі C4

Система в C4 — цілісний програмний продукт, межу якого ми зараз розглядаємо. На рівні System Context система перебуває поруч із людьми та іншими системами. Ми називаємо її мету, аудиторію, зовнішніх учасників і відносини з ними. Усередині системи на цьому рівні немає бібліотек, класів, баз даних або внутрішніх сервісів: це свідомий крок назад від реалізації.

Елемент Що записати Що не додавати до цього рівня
Людина чи роль Хто взаємодіє із системою і заради якої мети Усі внутрішні ролі авторизації, якщо вони не змінюють розуміння контексту
Програмна система Назву, коротке призначення й межу відповідальності Технологічний стек і деталі кожного модуля
Зовнішня система Який окремий продукт або постачальник взаємодіє з нашою системою Внутрішню базу даних, яку команда вважає частиною власного продукту
Зв’язок Хто ініціює взаємодію, що передає або отримує і для чого Безіменну лінію «з’єднані» без напряму й змісту

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

Рівень 2: контейнери всередині системи

Контейнер C4 і контейнер Docker — різні поняття

Контейнер у C4 — самостійний застосунок, що виконується як робоча одиниця, або сховище даних із помітною відповідальністю та взаємодією з іншими елементами системи. Наприклад, браузерний односторінковий застосунок, серверний застосунок, мобільний клієнт або база даних. Тут «контейнер» не означає обов’язково Docker-контейнер. В одному фізичному образі чи процесі може бути кілька логічних частин, а одна логічна C4-частина може мати кілька робочих копій у розгортанні. Контейнерна модель показує основні технології й комунікацію, а не повний перелік машин та екземплярів C4: container diagram.

Для кожного контейнера назвіть:

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

Далі використовуємо термін програмний інтерфейс застосунку (API, Application Programming Interface). Для мережевого обміну браузер із сервером може застосовувати протокол HTTP (Hypertext Transfer Protocol, протокол передавання гіпертексту) або його захищений варіант HTTPS (Hypertext Transfer Protocol Secure); JSON — JavaScript Object Notation, формат структурованих даних. Це лише умовний набір прикладу, а не вимога C4.

Офіційна модель на цьому рівні включає в межу однієї системи застосунки та сховища, а ззовні показує безпосередніх людей і програмні системи. Наприклад, користувач відкриває вебзастосунок через захищене з’єднання, вебзастосунок викликає бекенд, бекенд читає і записує дані в сховище. Ці рядки пояснюють відповідальність і комунікацію; спосіб запуску трьох копій бекенду належить іншому поданню. Таке розділення запобігає поширеній помилці: назвати кожну базу «зовнішньою системою», хоча вона є частиною продукту, або назвати систему контейнером тільки через Kubernetes чи Docker.

Один вебзастосунок може показувати браузерний інтерфейс і серверний застосунок як різні C4-контейнери. Водночас «Frontend» і «Backend» — не єдині правильні межі: у невеликому desktop-застосунку чи програмі з одним процесом рішення може бути іншим. Визначайте контейнер за самостійністю виконання, зберіганням і способом взаємодії, а не за назвою папки.

Рівень 3: компоненти всередині обраного контейнера

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

На цьому рівні уточнюють:

  1. Зону опису: який саме контейнер ми деталізуємо і чому;
  2. Відповідальність: яку роботу компонент виконує і чого не робить;
  3. Межу: які дані або рішення належать йому;
  4. Інтерфейс: що він отримує та що надає іншим частинам;
  5. Відносини: хто кого викликає або яким подіям підписаний.

Офіційна сторінка C4 називає компонентне подання необов’язковим: створюйте його для контейнера або питання, де воно прояснює структуру, а не заради повної інвентаризації кожної бібліотеки C4: component diagram. В описі може бути одна функціональна група, наприклад обробка бронювання, хоча в коді вона охоплює кілька класів і таблиць.

Рівень 4: деталі коду за конкретним питанням

Рівень Code може називати класи, функції, інтерфейси, таблиці чи інші реалізаційні елементи. Він відповідає на вузьке запитання: «Де захищається правило від подвійного бронювання?» або «Який клас перетворює зовнішню відповідь на нашу модель?» Він не є вимогою вручну перелічити кожну функцію. Офіційна документація C4 застерігає від зайвої деталізації; для коду, який часто змінюється, короткий згенерований опис або навігація в редакторі можуть бути надійнішими за застарілу ручну карту C4: code diagram.

Якщо читачеві треба тільки знати, що під час бронювання перевіряється вільний час і запис зберігається атомарно, зазвичай досить компонента й його контракту. Імена BookingEndpoint.Create, ReservationCoordinator.ReserveSlot та обмеження таблиці варто додати, коли команда перевіряє конкретний конкурентний дефект. Якщо в наступному рефакторингу назви класів зміняться, системна межа лишається тією самою.

Відносини: напрямок, зміст і контекст

Фраза «сервіс використовує базу даних» залишає невідомими важливі речі. Чи записує він бронювання або тільки читає доступні часові вікна? Хто ініціює запит? Через що передаються дані? Що відбувається у разі відмови?

Для структурного відношення, коли це істотно, вкажіть:

  • джерело та ціль;
  • дію з погляду джерела: «отримує список», «записує підтверджене бронювання», «передає лист»;
  • тип даних або результат;
  • протокол чи формат, якщо вони допомагають зрозуміти межу: наприклад, захищений HTTP та JSON;
  • відповідальність за помилку або підтвердження, коли це визначає архітектуру.

Саме назви протоколів недостатньо. «API викликає сервіс через HTTPS» повідомляє транспорт, але не призначення даних. І, навпаки, для внутрішньої функції не обов’язково вказувати «HTTP», якщо вона викликається в межах процесу.

Структурне відношення показує, що один елемент має залежність або можливість взаємодії з іншим. Послідовність подій показує, що відбулося раніше й що сталося потім у конкретному сценарії. «Booking API надсилає підтвердження через Notification Worker» може бути структурним відношенням. «Після транзакції Worker читає подію, отримує адресу, надсилає лист, згодом повторює невдалу доставку» — послідовність. Її описують окремим нумерованим сценарієм або динамічним поданням, не намагаючись приписати час кожній лінії структури.

Додаткові подання: динаміка та фізичне розгортання

Чотири назви C4 — не єдині можливі подання. Автор описує також контекст ландшафту, динамічне подання та подання фізичного розгортання. Динамічне подання варто використовувати вибірково, коли важливий сценарій взаємодії неочевидний зі структурного опису; автор радить не робити його без потреби. Ці подання не створюють «п’ятий рівень компонентів» і не замінюють чотири рівні.

Подання На яке питання відповідає Приклад текстового опису
Динамічне Які кроки взаємодії відбуваються в одному важливому сценарії? Користувач надсилає форму; API перевіряє доступ; сервіс резервує слот; сховище фіксує бронювання; після підтвердження фонова задача надсилає лист.
Deployment, тобто фізичне розгортання На яких вузлах, середовищах або хмарних ресурсах працюють екземпляри програмних контейнерів? Три копії Booking API працюють за балансувальником; база розміщена в керованому сховищі; Worker запускається окремим процесом.
Ландшафт систем Які системи та відповідальні команди існують у ширшому організаційному контексті? Бронювання взаємодіє з корпоративною ідентифікацією та поштовою платформою, якими опікуються інші команди.

Deployment-подання описує конкретне середовище. Інформація про балансування навантаження, кластери, реплікацію й відмовостійкість належить окремому поданню для цього середовища; загальна контейнерна структура сама нічого з цього не доводить C4: deployment diagram. Динамічне подання додавайте, коли важливу послідовність не видно зі структури, а не для кожної можливої взаємодії C4: dynamic diagram.

Наскрізний приклад 1: сайт футбольної статистики на чотирьох рівнях

Нижче — вигаданий навчальний сайт футбольної статистики, який показує поточні склади команд і історію трансферів; це не схема реально розгорнутого продукту. Межа прикладу включає інтерфейс, програмну логіку, власне сховище й фоновий імпортер. Уявна офіційна стрічка транзакцій є зовнішнім джерелом підтверджених подій Team A → Team B. Сайт зберігає поточне представлення складу, один канонічний трансфер і історію версій джерела. Поле effective_at визначає момент впливу трансферу на поточний склад; час отримання повідомлення не є його заміною. Це той самий приклад, що й у главі 13; тут ми простежимо зміну масштабу від контексту системи до її контейнера, компонента й потрібного фрагмента коду.

Контекст: хто має результат і що поза відповідальністю команди

Тип елемента Назва Призначення Напрям і мета взаємодії
Людина Читач статистики Переглядає поточний склад і попередні трансфери гравця Запитує сайт про команду або історію гравця.
Людина Редактор даних Перевіряє помилки імпорту й невідповідності Розглядає відхилені події; не змінює зовнішнє джерело.
Система Сайт футбольної статистики Показує поточні склади та збережену історію переходів Читає власне сховище й імпортує підтверджені події зі стрічки транзакцій.
Зовнішня система Офіційна стрічка транзакцій (вигадана в прикладі) Публікує підтверджені переходи між командами та їхні виправлені версії Надає сайту ключ події, номер версії, учасників і час або дату набуття чинності.

На цьому рівні ми не показуємо браузер, таблиці бази, клас імпортера чи бібліотеку HTTP. Читач бачить мету, зовнішнього учасника й межу відповідальності: стрічка публікує події, а сайт відповідає за поточне представлення й історію. Якщо зовнішній постачальник зміниться, зовнішній зв’язок і контракт треба переглянути, але внутрішнє сховище сайту не обов’язково зміниться.

Контейнери: самостійні застосунки й сховища в межі системи

Тип і назва Відповідальність Технологія в навчальному прикладі Структурні відносини
Браузерний односторінковий застосунок (SPA, single-page application) Показує поточний склад команди та хронологію переходів гравця TypeScript і браузер Читач керує інтерфейсом; SPA надсилає запити до Roster API через HTTPS у форматі JSON.
Roster API — серверний застосунок Віддає поточний склад та історію; перевіряє параметри запиту ASP.NET Core Приймає JSON-запити від SPA й читає проєкцію складу та історію з реляційного сховища.
Transaction Importer — окремий фоновий застосунок Забирає й перевіряє версії транзакцій, зберігає прийнятий трансфер і планує його вплив на поточний склад Фоновий процес .NET Читає офіційну стрічку через HTTPS; після перевірки записує версію джерела та канонічний трансфер, а проєкцію запускає не раніше effective_at.
Сховище складів та історії Зберігає поточне членство, один канонічний трансфер, версії та відбитки вмісту джерела, а також заплановані проєкції PostgreSQL; запити мовою SQL (Structured Query Language) Roster API читає склад та історію; Importer зберігає версії й канонічний трансфер; проєктор змінює склад лише коли трансфер набув чинності.
Офіційна стрічка транзакцій — зовнішня система Публікує офіційні підтверджені події переходів HTTP API у навчальному прикладі Transaction Importer періодично запитує нові події та отримує структуровану відповідь.

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

Компоненти: деталізуємо саме Transaction Importer

Компонент Відповідальність і межа Що він не вирішує сам
Feed Client Отримує наступну сторінку подій із зовнішньої стрічки Не визначає авторитетність або правильність переходу.
Event Validator Перевіряє схему, стабільні ключі, версію, сутності та формат effective_at Не зіставляє невідомого гравця за схожим ім’ям і не вигадує часову зону.
Transfer Coordinator Розрізняє точний повтор, новішу версію, застарілу ревізію й конфлікт; координує прийняття Не застосовує версію, що не пройшла контракт і перевірку власника.
Source Revision Repository Зберігає кожне спостереження джерела, номер ревізії, digest та конфлікти Не змінює канонічний трансфер під час невирішеного конфлікту.
Canonical Transfer Repository Зберігає один прийнятий трансфер для (source_id, event_id) і останню прийняту ревізію Не втрачає попередні версії джерела й не вважає час надходження часом чинності.
Effective-time Projector Проєктує прийнятий трансфер у поточний склад у момент або після effective_at Перевіряє, що завдання посилається на останню прийняту ревізію; застаріле заплановане завдання нічого не змінює.

Це один можливий поділ логічної відповідальності. C4 не вимагає окремого процесу, сервісу чи команди для кожного компонента. У реальному коді кілька обов’язків можуть бути об’єднані, якщо межа й перевірки лишаються зрозумілими.

Код: відповідаємо на запитання про прийняття виправлення

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

Кодовий елемент Яка роль у питанні
FeedClient.ReadNext і EventValidator.Validate Отримують подію й перевіряють її поля, ідентифікатори та часові дані.
TransferCoordinator.AcceptRevision Точний повтор версії з тим самим digest не змінює стан; інший вміст тієї самої версії передається на звірку; стара версія не замінює новішу.
SourceRevisionRepository.Record Записує (source_id, event_id, revision), digest і конфліктні спостереження, не перезаписуючи попередню історію.
CanonicalTransferRepository.Upsert Оновлює єдиний канонічний трансфер після прийняття новішої версії.
EffectiveTimeProjector.ApplyDue Застосовує поточну прийняту версію не раніше effective_at; застаріле завдання пропускає.

Схема БД може підкріпити ці правила унікальними ключами (source_id, event_id, revision) для версії джерела й (source_id, event_id) для єдиного канонічного трансферу; це треба перевірити в самій схемі.

Динамічне подання: прийняття, повтор і пізнє виправлення

Структурні таблиці не показують черговості дій. У цьому сценарії:

  1. Transaction Importer отримує з умовної офіційної стрічки подію (feed, event-204, revision=1): гравець переходить із Team A до Team B, а effective_at — майбутній час, визначений контрактом джерела.
  2. Event Validator перевіряє поля й ідентифікатори. В одній транзакції імпортер зберігає ревізію 1 і її digest, створює один канонічний трансфер для (feed, event-204) і планує проєкцію на effective_at. Поточний склад поки лишається в Team A.
  3. Якщо стрічка точно повторює (feed, event-204, revision=1) із тим самим digest, це no-op: нового трансферу не створюють. Якщо той самий номер версії приходить з іншим digest, спостереження зберігають для звірки й не перезаписують ревізію 1.
  4. До запланованого часу стрічка надсилає виправлення revision=2. Після валідації імпортер зберігає ревізію 2, лишає ревізію 1 в історії джерела й оновлює один канонічний запис, позначаючи ревізію 2 останньою прийнятою. У цьому прикладі виправлена дата також майбутня.
  5. Запланована робота від ревізії 1, якщо ще стоїть у черзі, перевіряє актуальну прийняту версію й нічого не змінює. У виправлений effective_at або пізніше робота від ревізії 2 переводить гравця до Team B; сайт показує один трансфер у футбольній історії, а не окремий трансфер на кожну версію джерела.
  6. Якщо після прийняття ревізії 2 стрічка повторно надсилає ревізію 1, імпортер може зберегти факт отримання старої версії, але вона не змінює канонічний трансфер і не відкотить поточний склад. Дата без часу або без визначеної часової зони не означає час доставки: система зберігає вихідне значення й застосовує лише задокументоване правило джерела; без такого правила випадок передають на звірку.

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

Наскрізний приклад 2: від змішаного переліку до узгоджених рівнів

Початковий опис звучить так: «Оператор закриває звернення; PostgreSQL працює в AWS (Amazon Web Services — хмарна платформа Amazon); validateTicket() перевіряє форму; система використовує AWS». У ньому поруч опинилися людина, сховище, функція, хмарна платформа та нечітка залежність. Це не один C4-рівень і не пояснення архітектури.

Фрагмент опису Що це може означати Де доречно згадати
«Користувач» Людина або роль, яка взаємодіє із системою Контекст; якщо важливий конкретний дозвіл — компонент доступу або окремий опис авторизації
«PostgreSQL» Технологія сховища; якщо база входить до системи, це C4-контейнер сховища Рівень контейнерів; деталізація таблиць належить моделі даних
validateTicket() Функція реалізації, а не окрема система чи контейнер Рівень коду, коли функція відповідає на важливе запитання
«AWS» Назва хмарної платформи Amazon, недостатньо точна для deployment-подання Розгортання; назвіть конкретний ресурс або вузол лише якщо він справді важливий, наприклад керовану базу даних чи середовище виконання
«використовує AWS» Невідомо, хто ініціює що і куди передає Переписати як напрямлену дію: наприклад, Support API зберігає звернення в керованому PostgreSQL через протокол бази даних

Відновимо опис за рівнями:

  1. Контекст: клієнт створює звернення; система перевіряє його особу через зовнішнього постачальника ідентифікації та надсилає оновлення поштовою службою. Межа не включає сервіси обраної хмари.
  2. Контейнери: браузерний застосунок надсилає JSON до Support API через HTTPS; API перевіряє запит і читає або змінює звернення в PostgreSQL; окремий Worker надсилає сповіщення.
  3. Компоненти в API: endpoint перевіряє форму; Access Policy визначає дозвіл; Ticket Coordinator застосовує перехід стану; Ticket Repository зберігає його.
  4. Код: validateTicket() може перевірити поля, але сама функція не доводить, що оператор має право закрити звернення або що перехід стану коректний.
  5. Deployment: опис називає середовище, кількість екземплярів, межу довіри й налаштування доступу. Слово AWS саме по собі цього не пояснює.

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

Паралельний приклад: бронювання від контексту до коду

Той самий спосіб застосуємо до навчального сервісу бронювання з попередньої глави. Клієнт резервує консультацію; корпоративна ідентифікація та поштовий постачальник лишаються зовнішніми. На рівні контексту важливо бачити клієнта, адміністратора, сервіс бронювання та ці два зовнішні зв’язки. На рівні контейнерів браузерний застосунок надсилає запит до Booking API через HTTPS, API працює зі сховищем бронювань, а Notification Worker надсилає лист після підтвердження. Умовна технологія може бути PostgreSQL, але сама назва не описує гарантію.

Усередині Booking API endpoint перевіряє формат запиту, Availability Service читає доступність, Reservation Coordinator координує резервування, а Booking Repository зберігає підтверджений запис і захищає інваріант слота. validateBooking() перевіряє поля, але не доводить, що слот залишився вільним до фіксації: конкурентний запит потребує атомарного обмеження в сховищі. Для вузької перевірки можна назвати шлях і символ коду, але не перетворювати функцію на новий архітектурний контейнер. Якщо лист не надіслано, уже підтверджене бронювання не скасовують; відкладену доставку описують як окремий сценарій.

Наскрізний приклад 3: одна логічна система, кілька екземплярів

Розгляньмо контейнер Roster API із попереднього прикладу. Для підвищення доступності команда може запустити три однакові його екземпляри за балансувальником. Усі обслуговують той самий контракт читання. У deployment-описі можна записати:

Логічний контейнер C4 Фізичне розміщення в умовному production-середовищі Що важливо уточнити
SPA Браузер клієнта; статичні файли доставляє вебсервер Чи збігається версія SPA з контрактом API
Roster API Три ідентичні екземпляри в трьох вузлах застосунку за одним балансувальником Чи не покладається стан сеансу на локальну пам’ять екземпляра
Сховище складів та історії Одна керована база; резервна копія й механізм відновлення описані окремо Який вузол приймає записи, чи є репліка для читання та яке відставання допустиме
Transaction Importer Два робочі екземпляри Як координуються опитування фіда й повторна обробка тієї самої події

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

Той самий принцип можна побачити на сервісі бронювання. Один логічний Booking API може працювати у трьох екземплярах за балансувальником, одна база — приймати записи, а два Notification Worker — обробляти різні завдання через механізм оренди або блокування. Можливий deployment-опис для умовного середовища:

Логічний контейнер C4 Фізичне розміщення в навчальному production-сценарії Що треба уточнити
SPA У браузері клієнта; статичні файли доставляє вебсервер Чи відповідає версія SPA контракту API?
Booking API Три однакові екземпляри на вузлах застосунку за одним балансувальником Чи не покладається стан сеансу на локальну пам’ять екземпляра?
Сховище бронювань Керована база; резервна копія та механізм відновлення описані окремо Який вузол приймає записи, чи є репліка читання й яке її відставання допустиме?
Notification Worker Два екземпляри, що беруть різні завдання за правилом блокування або оренди Чи безпечний повтор листа, як не загубити завдання й побачити дубль?

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

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

Перевірка узгодженості: нижчий рівень уточнює ту саму історію

Узгодженість кількох рівнів схеми C4

Рівні мають утворювати ланцюжок пояснень. Контейнер належить системі з рівня контексту; компонент міститься в названому контейнері; кодовий елемент реалізує частину відповідальності конкретного компонента. Перевірте узгодженість так:

Питання перевірки Ознака узгодженості Типовий розрив
Чи та сама межа системи? Стрічка транзакцій лишається зовнішнім джерелом на нижчих рівнях На рівні компонентів її раптом описали як внутрішню бібліотеку без пояснення
Чи збігаються назви? Roster API і Сховище складів та історії зберігають ті самі відповідальності в тексті й контракті Компонент імпортера змінює публічний склад, хоча раніше його власником названо сховище
Чи зрозуміло, хто володіє станом? Є джерело істини й шлях прийняття трансферу Кілька компонентів можуть непомітно змінити поточне членство
Чи напрям відношення правдивий? Ініціатор і ціль відповідають реальному запиту чи передаванню даних Стрілка й текст з’явилися лише тому, що два блоки стоять поруч
Чи кодова деталь справді потрібна? Вона пояснює перевірку версії або часу набуття чинності Список класів копіює навігатор коду й швидко застаріває
Чи розділено структуру й виконання? Структурна залежність має свій короткий опис, кроки збою — свій сценарій Лінія залежності видається за гарантію послідовності чи доставки

Не заповнюйте рівні заради повноти: деталізуйте компоненти й код лише тоді, коли вони допомагають відповісти на конкретне питання.

Як пояснити систему агенту або новому колезі

Для архітектурного завдання агенту назвіть контекст системи, її межу, питання й потрібний рівень. Без цього запит «покажи архітектуру» може змішати хости, бібліотеки, компоненти та ролі. Текстова таблиця підходить, якщо в ній чітко позначені тип елемента й відношення.

Приклад обмеженого доручення:

Система: сайт футбольної статистики в репозиторії за commit [ідентифікатор].
Межа: власний код сайту; офіційна стрічка транзакцій — зовнішня.
Питання: як прийнятий трансфер переходить у поточний склад?
Спершу коротко покажи контекст системи й її контейнери, потім розкрий Transaction Importer.
Для кожного важливого твердження наведи шлях і символ коду або посилання на контракт.
Познач статус: підтверджено, суперечить джерелу, невідомо або погоджена ціль.
Не роби висновків про виробниче розгортання за локальним кодом.
Поверни українську таблицю: елемент — тип і межа — відповідальність — зв’язок — доказ.
Не додавай графіку, вигаданий номер версії чи елементи лише заради повноти.

Це дисципліна завдання, а не гарантія можливостей агента. Людина перевіряє результат; C4 не визначає процес розробки чи розподіл ролей команди поширені запитання про модель C4.

Межі C4: що треба описати окремо

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

Тому короткий контекст «клієнт використовує сервіс» не є специфікацією вимог. Таблиця компонентів не доводить, що доступ перевіряється безпечно. Deployment-подання з трьома вузлами не доводить готовність до аварійного відновлення. Назва черги не описує гарантію доставки. Для кожного такого висновку потрібен окремий контракт, перевірка чи експлуатаційний доказ.

Модель не нав’язує один колір, форму блока, графічний редактор чи спосіб розміщення. Але наочність — не причина залишати без пояснення тип елемента, його відповідальність і сенс напрямку. У текстовій версії таблиця має називати масштаб та межу, щоб читач не мусив виводити їх із розташування блоків C4: notation.

Реальний текстовий приклад: C4 у репозиторії Lighthouse

Публічний репозиторій LetPeopleWork/Lighthouse містить загальний ARCHITECTURE.md і окремий файл C4-подань. Перший називає документ оглядом цільової архітектури й посилається на другий як на набір контекстного, контейнерного та компонентного опису. У файлі C4 автори спершу називають адміністраторів, читача й постачальника ідентичності, а далі деталізують браузерну SPA, Backend API, базу та зовнішню систему. Компонентний приклад проходить усередині Backend API: контролер авторизації, сервіс керування ролями, інтерфейс сервісу, обробник політики й контекст сховища. RBAC означає role-based access control — керування доступом на основі ролей. В окремих відносинах названо і дію, і технологію: наприклад, SPA надсилає запити до API через HTTPS і JSON, а API читає чи записує сутності ролей через Entity Framework Core (EF Core), бібліотеку доступу до даних .NET. Це авторський поділ конкретної функції, а не обов’язковий словник C4; клас стає корисною архітектурною деталлю лише тоді, коли пояснює відповідальність огляд архітектури Lighthouse, C4-подання Lighthouse.

У датованих описах RBAC від 10 травня 2026 року названі React 18 і .NET 8, тоді як загальний огляд описує цільові React 19 і .NET 10. Самі тексти не показують, чи це історичний опис функції, застарілий фрагмент або свідома різниця між версіями. Commit від 8 серпня 2026 року додав C4-подання для іншої частини того самого файла. До перевірки власника й коду не можна оголосити один із цих станів актуальним; історія версій пояснює походження тексту, але не доводить розгортання коміт із C4-поданнями.

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

Практична послідовність підготовки текстового C4-опису

Як вибрати рівень C4 за питанням читача

  1. Назвіть систему й мету читача. Сформулюйте, яке рішення або питання має прояснити опис.
  2. Зафіксуйте межу й дату. Назвіть репозиторій, гілку чи commit; відокремте власний продукт від зовнішнього постачальника.
  3. Опишіть контекст. Додайте важливих людей, системи й напрям взаємодії, а не кожну внутрішню деталь.
  4. Виділіть контейнери. Назвіть застосунки та сховища, їхні відповідальності, технологічні межі й протоколи.
  5. Виберіть контейнер для деталізації. Поясніть, чому він важливий для питання, і перелічіть лише компоненти, що прояснюють структуру.
  6. Перейдіть до коду лише за потреби. Дайте шлях, символ або обмеження, яке відповідає конкретному питанню.
  7. Відокремте структуру від сценарію. Для важливого потоку запишіть кроки успіху, відмови, повтору чи невідомого стану окремо від структурних залежностей.
  8. За потреби додайте фізичне розгортання. Назвіть середовище й кількість екземплярів; не перейменовуйте їх на компоненти.
  9. Перевірте сталість назв і доказів. Звірте межі з вимогами, контрактами й кодом; фактичне, цільове, припущене й невідоме підпишіть окремо.
  10. Позначте пропуски й перевірте читабельність. Якщо питання стосується загроз, даних, бізнес-процесу чи якості, доповніть C4 іншою моделлю. Переконайтеся, що людина розуміє межу, зв’язки й причину наступного рівня деталізації.

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

Підсумок

C4 дає спільні назви для поступового архітектурного наближення: система в оточенні, її застосунки та сховища, внутрішні компоненти й потрібні деталі коду. Контейнер означає логічну програмну частину або сховище, а не неодмінно Docker-об’єкт; компонент не стає окремим сервісом автоматично. Напрямлені відносини мають пояснювати мету взаємодії, а сценарій у часі та фізичне розгортання описують окремими поданнями. Звіряйте назви, межі, відповідальність і джерело версії між рівнями. C4 допомагає побачити структуру, але не замінює вимоги, модель даних, аналіз ризиків або докази роботи production-системи.

Джерела

Офіційні сторінки C4 і публічний репозиторій Lighthouse перевірено 10 жовтня 2026 року. Файл Lighthouse перевірявся з поточної гілки main; дати та статуси в його feature-описах можуть стосуватися окремого зрізу проєкту.