🐹 Бесплатные фишки по ИИ и заработку в нашем Telegram
Вайб-кодинг

SaaS архитектура веб-приложений — как устроены масштабируемые системы

Полное руководство по структурированию SaaS-приложения: от базовой MVC до модульной HMVC архитектуры. Как организовать код так, чтобы он оставался предсказуемым при росте проекта.

SaaS архитектура изнутри: полное руководство 2024

Ключевые цифры

🏗️
4 контекста
Front, App, Admin, API
📊
1 модель
в разных контекстах
⚙️
Сервисы
вынос повторяющейся логики
🔑
Провайдеры
абстракция интеграций

MVC: основа разделения ответственности

MVC (Model-View-Controller) — это фундаментальный паттерн архитектуры, который разбивает приложение на три независимых слоя с чётко определённой ответственностью. Каждый слой отвечает за свою часть логики, что позволяет изолировать изменения и упростить тестирование.

Рассмотрим практический пример: пользователь заходит на страницу /billing/history (история платежей).

Как работает цикл запроса

  • Роутинг: Запрос маршрутизируется к контроллеру BillingHistoryController, методу index
  • Контроллер: Выступает «дирижёром». Решает, какие данные нужны (платежи, информация о подписке) и обращается к соответствующим моделям
  • Модель: Слой работы с данными. Например, PaymentModel знает, как получить платежи из базы данных, применить фильтры, сортировку
  • Вид (View): Получает данные от контроллера и превращает их в HTML-страницу (таблицы, графики, кнопки действий)

Главное преимущество такого разделения: изменение шаблона не затрагивает логику работы с данными. Вы можете переделать дизайн таблицы платежей, и при этом логика выборки платежей из базы остаётся нетронутой. Это снижает риск регрессии и упрощает разработку с использованием AI-помощников.

HMVC: модульная архитектура для крупных SaaS

По мере роста приложения простая MVC структура становится громоздкой. Когда в одной папке лежат контроллеры для регистрации, личного кабинета, админ-панели и API — навигация по коду становится кошмаром.

HMVC (Hierarchical MVC) решает эту проблему — приложение делится на независимые модули, каждый из которых содержит свой собственный MVC.

Примеры модулей

  • Auth — авторизация и аутентификация
  • Users — управление пользователями
  • Billing — счета и платежи
  • ApiKeys — управление ключами доступа
  • Reports — аналитика и отчёты

Каждый модуль содержит:

  • Свои контроллеры
  • Свои модели данных
  • Свои маршруты (роуты)
  • Свои шаблоны (views)

Модули могут общаться через чёткие интерфейсы, но при этом остаются слабо связанными. Если задача изменить модуль Billing, разработчик (или AI-помощник) видит его структуру и пишет код, который не сломает Auth или Reports. Это особенно важно для автоматизированной разработки.

Четыре контекста приложения: Front, App, Admin, API

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

Front контекст (публичная часть)

Лендинги, блоги, страницы с информацией о продукте. Доступны без авторизации. Пример: модуль Pricing показывает на Front контекстах разные тарифы.

App контекст (личный кабинет)

Личный кабинет зарегистрированного пользователя: дашборд, настройки, управление своими данными. Пример: модуль User в контексте App позволяет пользователю редактировать свой профиль, загружать аватар, менять пароль.

Admin контекст (админ-панель)

Инструменты администратора: управление всеми пользователями, аналитика, модерация контента. Пример: модуль User в контексте Admin позволяет администратору видеть всех пользователей, блокировать аккаунты, менять их статусы.

API контекст (REST API)

Стандартный JSON API для мобильных приложений и интеграций. Пример: модуль Payments предоставляет эндпоинты GET /api/payments, POST /api/payments с полной документацией.

Ключевой момент: для модуля User используются разные контроллеры и шаблоны в каждом контексте, но одна и та же модель, которая работает с базой данных. Это гарантирует консистентность данных.

Сервисы: централизация повторяющейся логики

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

Проблема дублирования

Представьте регистрацию пользователя. Логика одна:

  1. Валидация email и пароля
  2. Проверка, что пользователь не существует
  3. Хеширование пароля
  4. Создание записи в базе
  5. Отправка приветственного письма
  6. Создание начальных настроек

Но эта логика требуется в трёх контекстах: Front (самообслуживание пользователя), Admin (регистрация администратором), API (программная регистрация). Если дублировать код, при изменении условия про проверку email все три места нужно найти и обновить.

Решение: сервисы

Сервис — это класс с бизнес-логикой, которая не принадлежит конкретной модели. Например, UserRegistrationService инкапсулирует всю сложную операцию регистрации.

Все контроллеры (Front, Admin, API) вызывают один сервис. Изменения вносятся в одном месте. Если требование про email изменилось — правишь в одном месте, и все три контекста сразу получают обновление.

Правило: Создавайте сервис, когда логика нужна в нескольких местах или когда действие затрагивает несколько моделей одновременно.

Провайдеры: абстракция для внешних интеграций

Приложение часто взаимодействует с внешними сервисами: почта (Mailgun, SendGrid), платежи (Stripe, PayPal), аналитика (Segment), облачное хранилище (S3).

Проблема: жёсткая связанность

Если код прямо обращается к API Mailgun для отправки писем, а потом вы решаете перейти на SendGrid — нужно переписывать все места в коде, где отправляется письмо. Риск ошибок очень высок.

Решение: провайдеры

Провайдер — это прослойка-абстракция между приложением и внешним сервисом. Весь код приложения говорит просто: «отправь письмо», не зная, через какой конкретный сервис это произойдёт.

  • EmailProvider::send($to, $subject, $body) — ваш интерфейс
  • Конкретная реализация (использует Mailgun API) описывается в отдельном классе
  • Конфигурация (какого провайдера использовать) находится в config файле

При смене сервиса меняется только конфигурация и реализация провайдера. Весь остальной код остаётся без изменений. Это делает архитектуру устойчивой к будущим изменениям.

Примеры других провайдеров: PaymentProvider (Stripe vs PayPal), StorageProvider (S3 vs локальный диск), NotificationProvider (email vs SMS vs push).

Практический пример: модуль управления API-ключами

Все эти концепции применены в реальном модуле ApiKeys для управления ключами доступа к API.

Структура модуля

Модель ApiKey: работает с таблицей в базе, знает как сохранить и загрузить ключи.

Контексты:

  • App контекст: Пользователь создаёт новый ключ для своего приложения, видит его один раз (по соображениям безопасности)
  • Admin контекст: Администратор видит все ключи, может отозвать ключ нарушителя, смотреть логи использования
  • API контекст: Внешние системы используют ключ для доступа к REST API

Сервис аутентификации

Сервис ApiKeyAuthenticationService проверяет:

  1. Извлекает ключ из заголовка запроса (обычно Authorization: Bearer KEY)
  2. Ищет хэш ключа в базе (оригинал ключа никогда не хранится в открытом виде)
  3. Проверяет, что ключ активен и не заблокирован
  4. Проверяет лимиты на количество запросов и потрачённые деньги
  5. При успехе — увеличивает счётчик использования
  6. При ошибке — возвращает HTTP 401 (Unauthorized) или 429 (Too Many Requests)

Безопасность: хеширование ключей

В базе сохраняется не сам ключ, а его SHA-256 хеш. Когда пользователь создаёт новый ключ, система показывает оригинальную строку ровно один раз. После этого восстановить невозможно. При проверке сравнивается хеш поступившего ключа с хешами в базе.

Этот подход защищает от компрометации базы данных: даже если база украдена, ключи остаются в безопасности.

Вопросы и ответы

В чём разница между Сервисом и Контроллером?

Контроллер отвечает за HTTP-запросы: он парсит параметры, вызывает нужные операции и возвращает ответ. Сервис содержит <em>бизнес-логику</em> — правила, которые работают независимо от того, через какой интерфейс их вызывать (веб, API, консоль). Один сервис может использоваться несколькими контроллерами и даже другими сервисами.

Когда создавать новый модуль HMVC, а когда сервис?

Модуль нужен, когда функциональность достаточно самостоятельна (свои роуты, контроллеры, модели, вьюхи) — например, Billing, Auth, Reports. Сервис нужен, когда логика повторяется <em>внутри</em> разных контроллеров, но не требует собственных роутов и интерфейса — например, UserRegistrationService, PaymentProcessingService.

Может ли модель использоваться в разных контекстах?

Да, это один из главных принципов. Модель User одна и та же для App контекста (редактирование профиля) и Admin контекста (управление всеми пользователями). Разные контроллеры и вьюхи, <em>одна модель</em> — это гарантирует консистентность данных в базе.

Почему провайдеры важны для долгосрочного проекта?

Когда вы абстрагируете внешние сервисы через провайдеры, замена одного сервиса на другой становится конфигурационным изменением, а не рефакторингом. Это экономит недели разработки и снижает риск регрессии при переходе с Mailgun на SendGrid или со Stripe на другой платёжный процессор.

Главное

Ключевая идея

Масштабируемая SaaS архитектура строится на чётких принципах разделения ответственности: MVC как основа, HMVC для модульности, контексты для разных интерфейсов, сервисы для повторяющейся логики, провайдеры для интеграций. Такая структура делает код предсказуемым, упрощает разработку с AI-помощниками и снижает количество ошибок при добавлении новых функций.

Начни зарабатывать на ИИ уже сегодня

Клуб людей, которые строят бизнес с помощью AI-агентов. Сигналы, разборы, инсайды — в закрытом Telegram.

✓ Бесплатный старт ✓ Живое сообщество ✓ AI-агенты включены