Проектирование программного обеспечения: архитектура и интерфейсы
Место темы в курсе
В предыдущих темах мы последовательно построили основу проектирования ИС:
определили роль проектирования в жизненном цикле
научились формулировать и специфицировать требования
выбрали методологии и нотации для описания системы
выполнили структурный анализ через контекст, DFD и словарь данных
спроектировали информационное обеспечение через ER-модель и нормализациюДальше возникает практический вопрос: как из процессов, потоков данных и логической модели данных перейти к реальному программному решению, которое можно разрабатывать, тестировать, внедрять и сопровождать.
Эта тема отвечает на два ключевых вопроса:
архитектура: как разложить систему на части и распределить ответственность
интерфейсы: как эти части и внешние участники обмениваются данными так, чтобы интеграции были предсказуемыми и проверяемымиБазовые понятия: что именно мы проектируем
Архитектура программного обеспечения
Архитектура ПО описывает ключевые решения о структуре системы:
какие компоненты существуют
как они взаимодействуют
где проходят границы ответственности
какие принципы обеспечивают нефункциональные требованияОдна из практичных формулировок: архитектура фиксирует решения, изменение которых дорого.
Справочно: Software architecture.
Компонент, модуль, сервис
Чтобы не путаться, полезно договориться о терминах.
Модуль: часть кода внутри приложения, выделенная для управляемости (пакет, библиотека, подсистема).
Компонент: более крупная единица разбиения с явным интерфейсом и ответственностью (например, подсистема биллинга).
Сервис: компонент, развертываемый отдельно и взаимодействующий по сети (часто в микросервисной архитектуре).Практическое правило: если вы не можете коротко сказать, за что отвечает часть системы, значит граница выбрана плохо.
Интерфейс и контракт
Интерфейс описывает, как одна часть системы может быть использована другой частью.
Контракт усиливает интерфейс, добавляя проверяемые условия:
какие входные данные допустимы
какие выходные данные гарантируются
какие ошибки возможны
какие ограничения действуют (права, лимиты, время ответа)Для внешних интеграций контракт обычно оформляют как спецификацию API, например через OpenAPI Specification.
От каких артефактов зависит архитектура
Архитектура и интерфейсы не проектируются изолированно. Они опираются на уже созданные артефакты.
Требования
Контекстная диаграмма
DFD и декомпозиция процессов
Словарь данных
ER-модель и ограничения данныхТиповая ошибка: начинать архитектуру с выбора технологий (например, "микросервисы"), не привязав это к требованиям, потокам данных и качественным характеристикам.
От требований к архитектуре: качества, которые задают форму решения
Функциональные требования отвечают на вопрос что делать. Но архитектуру сильнее всего формируют нефункциональные требования.
Частые архитектурно значимые требования:
производительность и задержки
масштабируемость
доступность и отказоустойчивость
безопасность и аудит
сопровождаемость и скорость изменений
интеграции и совместимостьЧтобы не оставлять эти свойства «в воздухе», удобно фиксировать для них измеримые критерии (например, целевое время ответа и ожидаемую нагрузку) и связывать их с проектными решениями.
Справочно о модели качества: ISO/IEC 25010.
Архитектурные представления и документация
Архитектуру важно описывать так, чтобы разные участники видели систему на нужном уровне.
Модель C4 как удобная структура описания
Практичный способ описывать архитектуру по уровням детализации дает C4 model:
контекст: кто и как взаимодействует с системой
контейнеры: крупные исполняемые части (приложение, БД, брокер сообщений)
компоненты: крупные части внутри контейнера
код: детали реализации (обычно уже на уровне разработки)Справочно: C4 model.
!Пример того, как описывать архитектуру от контекста к контейнерам
Архитектурные решения как управляемые записи
Сильная практика для управляемости архитектуры: фиксировать ключевые решения как ADR.
что решили
почему
какие альтернативы рассматривали
какие последствия у решенияСправочно: Architecture decision record.
Декомпозиция: как выделять компоненты
Задача декомпозиции: разрезать систему так, чтобы изменения в одной части минимально затрагивали другие.
Ниже несколько способов, которые часто комбинируют.
Декомпозиция по бизнес-возможностям
Компоненты выделяют вокруг крупных бизнес-функций, которые видны в DFD уровня 0.
Пример для системы заявок:
управление заявками
управление клиентами
справочники и статусы
уведомления
отчетностьСвязь с DFD полезна как проверка: почти каждый крупный процесс DFD должен «приземляться» в компонент или модуль.
!Иллюстрация перехода от структурного анализа к архитектурной декомпозиции
Декомпозиция по данным и ответственности
ER-модель подсказывает, где у данных единый жизненный цикл и владелец.
Примеры:
данные Клиента управляются компонентом клиентского учета
данные Заявки управляются компонентом обработки заявок
справочники статусов управляются отдельным компонентом или общим справочникомПрактическое правило: если два компонента постоянно спорят, кто «главный» за сущность, значит границы ответственности выбраны плохо.
Слоистая структура внутри приложения
Даже если система развертывается как один продукт, внутри полезно разделять слои ответственности.
Типовой вариант:
слой представления: UI, API-контроллеры
прикладной слой: сценарии, оркестрация операций
доменный слой: бизнес-правила
инфраструктурный слой: доступ к БД, внешним сервисам, очередямЭто не «единственно верно», но помогает не смешивать в одном месте интерфейсы, бизнес-логику и доступ к данным.
Справочно: Layered architecture.
Выбор архитектурного стиля: монолит, модульный монолит, микросервисы
Выбор стиля влияет на скорость разработки, операционную сложность и устойчивость к изменениям.
Сравнение подходов
| Подход | Суть | Когда часто разумно | Основной риск |
|---|---|---|---|
| Монолит | одно приложение, единое развертывание | небольшая команда, быстрое развитие, умеренные интеграции | разрастание в «комок» без модульности |
| Модульный монолит | один деплой, но строгие внутренние границы модулей | нужна простота эксплуатации, но важна управляемость изменений | границы модулей формальны, а зависимости «протекают» |
| Микросервисы | независимые сервисы, сетевые интерфейсы | много команд, разные темпы развития частей, высокая нагрузка на отдельные модули | сложность эксплуатации, распределенные ошибки |
Справочно о микросервисах: Microservices.
Практические критерии выбора
Сколько независимых команд будет развивать систему
Насколько различаются требования к масштабированию частей системы
Насколько критична независимая поставка изменений
Есть ли зрелая эксплуатация: мониторинг, логирование, CI/CDЕсли уверенности нет, модульный монолит часто безопаснее как стартовая точка.
Проектирование интерфейсов
Интерфейсы делятся на внутренние и внешние.
внутренние интерфейсы отделяют модули внутри приложения
внешние интерфейсы соединяют систему с внешними системами и клиентамиВнешние API: что должно быть в контракте
Минимально полезный контракт внешнего API должен определять:
операции и ресурсы
форматы запросов и ответов
коды ошибок и структуру ошибок
правила аутентификации и авторизации
ограничения: лимиты, таймауты, размеры сообщений
версионирование и совместимостьДля REST-подхода часто ориентируются на понятия ресурса и стандартные методы.
Справочно: Representational state transfer.
Идемпотентность и повторные запросы
В интеграциях всегда возможны повторы запросов из-за сетевых сбоев.
Идемпотентная операция дает один и тот же результат при повторном выполнении с теми же параметрами.
Практический смысл:
GET обычно идемпотентен
PUT часто проектируют идемпотентным
POST часто неидемпотентен, поэтому полезен идемпотентный ключ (например, Idempotency-Key), если это важно для бизнесаВерсионирование API
Изменения контрактов неизбежны, поэтому нужно заранее выбрать правила.
Частые варианты:
версия в URL: /api/v1/...
версия в заголовке
совместимые изменения без смены версии: добавление полей при соблюдении обратной совместимостиПрактическое правило: удаление полей и изменение смысла поля почти всегда требуют новой версии или миграционного периода.
Асинхронные интерфейсы и события
Если система должна быть устойчивой к пиковым нагрузкам и сбоям внешних зависимостей, часто используют обмен сообщениями.
Плюсы асинхронности:
сглаживание нагрузки
меньше связность по времени
возможность ретраев и гарантированной доставкиМинусы:
сложнее отладка и трассировка
нужно проектировать обработку повторов и порядок сообщенийДля документирования событийных контрактов применяют спецификации, например AsyncAPI.
Интеграционные решения: как выбрать стиль взаимодействия
Ниже упрощенная матрица для выбора.
| Ситуация | Часто подходит | Почему |
|---|---|---|
| нужен мгновенный ответ пользователю | синхронный API | можно сразу показать результат |
| внешний сервис нестабилен | очередь и обработка в фоне | система меньше зависит от доступности внешнего сервиса |
| важна гарантированная доставка | брокер сообщений | легче обеспечить повторы и подтверждения |
| много потребителей изменений | события домена | один источник публикует, многие подписываются |
Независимо от стиля, интерфейс должен быть связан со словарем данных: термины и поля должны иметь единые определения.
Данные и границы: транзакции, согласованность, владение
На этапе архитектуры важно решить, как компоненты делят данные.
Владелец данных
Владелец данных определяет:
кто имеет право изменять сущность
кто отвечает за целостность
кто публикует изменения наружуЕсли все компоненты напрямую пишут в одну и ту же таблицу, границы ответственности размываются и система становится хрупкой.
Согласованность между компонентами
В распределенных решениях транзакции «через сеть» становятся дорогими и сложными.
Практичные подходы:
предпочитать локальные транзакции внутри компонента
синхронизировать состояние через события
проектировать компенсации для бизнес-ошибок, если требуется цепочка действийСправочно о подходе с компенсациями: Saga pattern.
Трассируемость: как связать требования, модели и интерфейсы
Чтобы архитектура не превратилась в набор «красивых картинок», нужно сохранить трассируемость.
Управляемая цепочка выглядит так:
Требование
Процесс DFD
Компонент или модуль
Интерфейс и контракт
Тест (в том числе интеграционный)Это продолжает идею из темы требований: изменение требования должно позволять быстро понять, какие компоненты и интерфейсы затронуты.
Минимальный комплект артефактов результата
Набор зависит от масштаба проекта, но как минимум обычно полезны:
диаграмма контекста и контейнеров архитектуры
описание основных компонентов и их ответственности
список внешних интерфейсов с ссылками на спецификации
словарь основных сообщений и ошибок, согласованный со словарем данных
перечень ключевых архитектурных решений (ADR)Частые ошибки в проектировании архитектуры и интерфейсов
подмена требований технологией: выбор стиля без обоснования качественными требованиями
отсутствие явного владельца данных и «общая база для всех» без правил
неописанные ошибки API и отсутствие стандартного формата ошибок
игнорирование повторов сообщений и запросов в интеграциях
контракты не связаны со словарем данных, термины расходятся
отсутствие версионирования и стратегии совместимостиИтоги
Архитектура ПО определяет структуру системы, границы ответственности и способы удовлетворить нефункциональные требования.
DFD и ER-модель дают надежные подсказки для декомпозиции на компоненты и определения владения данными.
Интерфейсы должны быть оформлены как контракты: форматы, ошибки, безопасность, ограничения, совместимость.
Выбор стиля (монолит, модульный монолит, микросервисы) должен быть обоснован контекстом проекта и зрелостью эксплуатации.
Трассируемость "требование → процесс → компонент → интерфейс → тест" делает архитектуру управляемой при изменениях.