Архитектурный разбор, как одна ИИ-сгенерированная кодовая база может обслуживать веб, мобильные приложения и API: слои, контракты, сборка и тесты.

«Одна кодовая база» — это не попытка сделать абсолютно одинаковым всё. Речь о том, чтобы критически важные части продукта жили в одном месте и менялись синхронно: правила бизнеса, модели данных, проверки, расчёты, статусы, ограничения и формат обмена данными.
Обычно в общий «ядро‑пакет» выносят:
Это даёт главный эффект: изменение правила (например, как считать комиссию или когда заказ считается «оплаченным») вносится один раз и одинаково работает в вебе, мобильном приложении и на сервере.
Почти всегда остаются специфичными:
Это нормально: у платформ разные ограничения и ожидания пользователей. Важно лишь, чтобы эти различия не «протекали» в бизнес‑логику.
Практичная формулировка цели: любой клиент (веб, iOS/Android, партнёрский сервис) разговаривает с системой на одном языке данных и следует одним правилам. Тогда API — не отдельный проект со своей логикой, а один из способов подключиться к ядру.
У подхода есть понятные метрики:
Единая кодовая база работает, когда у неё есть «скелет», который не зависит от платформы. Самая практичная схема — разделить систему на домен (что мы делаем), приложение/юзкейсы (как мы это делаем) и адаптеры (через что пользователи и внешние системы с нами общаются).
Домен — это сущности, значения, инварианты и правила предметной области: расчёты, статусы, ограничения, проверки. Здесь не должно быть знаний о UI, HTTP, базе данных, SDK мобильной платформы или конкретном фреймворке.
Почему так важно держать домен независимым:
Юзкейсы описывают шаги: «создать заказ», «оплатить», «подтвердить доставку». Они оркестрируют домен и обращаются к абстрактным портам (интерфейсам) — например, Payments, OrdersRepo, Notifications.
Ключевая идея: юзкейс не знает, откуда пришёл запрос — из веб‑формы, мобильного экрана или API‑метода. Он получает входные данные, применяет правила домена и возвращает результат в нейтральном виде.
Адаптеры переводят «язык платформы» в «язык юзкейса»:
Граница простая: всё, что связано с транспортом, хранением, форматами, авторизацией на уровне протокола — в адаптерах/инфраструктуре; правила и сценарии — в домене и приложении.
ИИ удобно использовать для ускорения каркаса: генерации интерфейсов портов, типовых DTO/мапперов, шаблонов юзкейсов и тестовых заготовок. Но решение о границах слоёв и зависимостях — архитектурное: если домен «потянет» за собой фреймворк, единая база быстро превратится в набор сцепленных платформенных модулей.
Если вы строите процесс вокруг «виб‑кодинга», полезно, когда платформа умеет не только генерировать заготовки, но и поддерживать дисциплину слоёв: например, в TakProsto.AI удобно сначала зафиксировать контракты и структуру модулей (planning mode), а затем поручить генерацию повторяемых частей (DTO, адаптеры, тестовые шаблоны) — с возможностью экспортировать исходники и развивать их в привычном репозитории.
Если одна кодовая база обслуживает веб, мобильные клиенты и API, то самым надёжным «центром тяжести» становится доменная модель — описание предметной области, которое не зависит от интерфейса и транспорта.
В домене фиксируются:
Заказ, Пользователь, Подписка.Деньги, Адрес, Период.Важно, что эти правила живут в домене, а не в UI и не в контроллерах. Тогда веб‑форма, мобильный экран и серверная ручка будут одинаково «понимать», что допустимо.
Расхождение DTO обычно начинается, когда каждый клиент «придумывает» свой формат. Практичнее договориться, что домен задаёт канонические типы, а наружу экспортируются контракты (request/response), полученные из них: так проще синхронизировать API и клиентов.
Хороший признак — когда изменения в полях и их смыслах проходят через домен и автоматически отражаются в схемах, а не размазываются по нескольким приложениям.
Домен должен уметь явно сообщать о проблемах: не «500 что-то пошло не так», а типизированные ошибки вроде НедостаточноСредств, НедопустимыйСтатус. Эти ошибки затем переводятся адаптерами в HTTP‑коды, тексты для UI и аналитику.
Для операций оплаты, создания и отправки используйте идемпотентные ключи на уровне юзкейса/домена: повтор запроса не создаёт дублей, а возвращает тот же результат.
На практике они хорошо сочетаются:
Выбирайте терминологию, которая понятна команде, но неизменно держите принцип: домен не знает, как выглядит экран и какой у вас транспорт — HTTP, gRPC или локальная база.
Юзкейсы (application layer) — это место, где продуктовые сценарии описываются один раз и одинаково работают для веба, мобайла и API. Здесь нет деталей экранов, HTTP‑эндпойнтов или базы данных: только то, что должно произойти и в каком порядке.
Обычно юзкейсы оформляют как команды (изменяют состояние) и запросы (читают данные). Внутри сценария живут:
Важно: доменная модель хранит бизнес‑правила «по смыслу», а юзкейсы — «по процессу» (последовательность и интеграции).
Юзкейс часто отвечает за границы транзакции: начать, выполнить изменения, зафиксировать. Если есть внешние вызовы (платёж, доставка), сценарий решает, где нужны ретраи и как обрабатывать временные ошибки.
Публикация доменных событий тоже удобна на этом уровне: юзкейс фиксирует факт («заказ создан»), а дальше подписчики (почта, аналитика, пуши) реагируют через адаптеры.
Чтобы один и тот же сценарий работал везде, юзкейсы зависят от портов (интерфейсов), а не от реализаций: репозитории, часы (time provider), шифрование, сеть/HTTP‑клиент, генератор идентификаторов. Тогда веб, мобильный и серверный адаптеры подставляют свои реализации, не меняя ядро.
Юзкейс не должен знать про экраны, навигацию, состояния кнопок, тексты ошибок «как показать». Он возвращает понятный результат (успех/ошибка с кодом и данными), а UI‑адаптер решает, как это отрисовать. Это сохраняет переиспользуемость сценариев и не превращает общий слой в сборник компромиссов под разные клиенты.
Когда веб, мобильные приложения и сервер развиваются параллельно, главный источник рассинхронизации — «устные договорённости» о формате запросов и ответов. Поэтому контракт API стоит воспринимать как продукт: он описывает, что именно гарантирует система, и одинаково важен для бэкенда и всех клиентов.
Важно: контракт должен быть один, а реализаций вокруг — много. Сервер генерирует валидаторы и типы, клиенты — типобезопасные модели и методы.
Держите курс на совместимость назад: добавляйте поля как optional, не меняйте смысл существующих, избегайте «ломающих» переименований. Для изменений, которые всё же ломают совместимость, вводите депрекейты: помечайте устаревшее, давайте срок, добавляйте миграционные подсказки. Версионировать можно как endpoint‑ами (/v1/…), так и через эволюцию схем — главное, чтобы правила были одинаковыми для всех команд.
Единый контракт позволяет выпускать SDK для веба и мобайла автоматически: типы, сериализацию, обработку ошибок, ретраи. Это снижает ручной код, а ИИ можно использовать точечно — например, чтобы дописать удобные «обёртки» над сгенерированным API, не трогая сам контракт.
Контрактные тесты проверяют, что сервер и клиенты говорят «на одном языке»: сервер действительно возвращает то, что обещано схемой, а клиенты не отправляют лишнего. Хорошая практика — прогонять в CI валидацию схем, тесты совместимости и генерацию SDK как обязательный шаг перед релизом.
Адаптеры — это «переводчики» между пользовательским интерфейсом и общими юзкейсами. Они знают про конкретный фреймворк, навигацию, состояние экрана, сетевые особенности — но не содержат бизнес‑правил. Благодаря этому веб и мобильные клиенты могут выглядеть и ощущаться по‑разному, оставаясь логически едиными.
Для веба удобно мыслить UI как адаптер в стиле MVC или SPA: контроллер/экшен (или handler) собирает входные данные, вызывает юзкейс и превращает результат в состояние страницы.
Ключевой принцип: веб‑слой не «лезет» в домен напрямую. Он оперирует DTO/моделями представления и маппингом:
Мобильный адаптер почти всегда сложнее из‑за окружения:
Важно: офлайн‑механика — это инфраструктура клиента, но «что считать конфликтом» и «как решать» должно оставаться в юзкейсах, иначе логика расползётся.
Реально переиспользуются дизайн‑токены, иконки, тексты, схемы состояния (empty/loading/error), иногда — простые компоненты. Но компоновку экранов, навигационные паттерны и интерактивность лучше делать отдельно: веб и мобильные платформы ожидают разный UX.
Домен и юзкейсы должны возвращать нейтральные результаты (например, «успех/ошибка + данные»), а решение «куда перейти» и «как показать» остаётся в адаптере. Для состояния используйте view‑model/Presenter слой: он хранит UI‑состояние и подписывается на результаты юзкейсов, не затягивая в ядро детали роутера, реактивности или жизненного цикла экранов.
Если домен и юзкейсы — «ядро», то HTTP/GraphQL/gRPC — всего лишь способы доставить запрос до этих сценариев и вернуть ответ. В чистой архитектуре серверный API — такой же адаптер, как веб‑UI или мобильный клиент: он не содержит бизнес‑правил и не диктует их форму.
Критерий выбора — не «мода», а тип взаимодействий и окружение:
При этом домен не должен «знать» про URL, резолверы и protobuf. Меняется транспорт — юзкейсы остаются теми же.
Контроллер (или резолвер) делает три вещи: валидирует вход, маппит DTO ↔︎ доменные типы и вызывает юзкейс. Всё остальное — в ядре. Это позволяет держать API предсказуемым и не размазывать правила по роутам.
Аутентификация (токены, сессии, OAuth) — инфраструктура адаптера. Авторизация (кто что может) — часть доменных правил: политика доступа должна быть тестируема на уровне юзкейсов (например, «владелец может редактировать, наблюдатель — нет») без запуска сервера.
Заранее зафиксируйте формат ошибок: стабильный code, человекочитаемый message, поле details для валидации, и trace_id для поиска в логах. Один стандарт облегчает поддержку всех клиентов и снижает «зоопарк» обработчиков на вебе, мобайле и в интеграциях.
Единая кодовая база начинает «расползаться», когда доменная логика тянет за собой конкретную БД, SDK платёжки или клиент очереди. Чтобы этого не произошло, инфраструктуру важно держать на периферии: ядро описывает что нужно системе, а внешний слой решает как именно это сделать в конкретной среде.
В домене и юзкейсах говорим языком бизнеса: «получить заказ», «сохранить пользователя», «проверить лимит». Для этого вводятся порты (интерфейсы) репозиториев и хранилищ.
Например, юзкейс зависит от UserRepository и SessionStore, но не знает, это PostgreSQL, MongoDB или Redis. Реализации живут в инфраструктурном модуле и могут отличаться для разных деплойментов, при этом сценарии и тесты остаются неизменными.
Любая внешняя система — это риск: меняются API, тарифы, поля, появляются ограничения. Поэтому интеграции лучше оформлять как «адаптеры» к порту, например PaymentsGateway, EmailSender, AnalyticsTracker.
Так вы избегаете ситуации, когда UI или юзкейс начинает разбираться в специфике провайдера. В результате проще заменять поставщика, делать фейковые реализации для тестов и безопасно подключать окружения (песочница/прод).
Событийная модель полезна для действий «после факта»: отправить письмо, пересчитать статистику, запустить антифрод — то, что не должно блокировать ответ пользователю.
Но не стоит превращать очередь в «чёрный ящик» бизнес‑транзакций: если операция требует строгой согласованности (например, списание и подтверждение), синхронный юзкейс с явной обработкой ошибок обычно понятнее и надёжнее.
Конфигурацию (URL БД, ключи API, фиче‑флаги) подаём снаружи через контейнер/провайдер зависимостей. Ядро не должно читать переменные окружения напрямую и тем более хранить секреты в коде.
Минимум зависимостей в центре — максимум свободы на краях: один и тот же домен и юзкейсы спокойно обслуживают веб, мобильные клиенты и API, даже если инфраструктура в каждом окружении отличается.
Правильная структура репозитория решает две задачи: делает «общее ядро» действительно общим и не даёт платформенным деталям протечь в домен и юзкейсы. Если это не закрепить на уровне сборки и зависимостей, единая кодовая база быстро превращается в набор взаимных костылей.
Монорепо удобно, когда веб, мобильное приложение и сервер развиваются синхронно: единые правки контракта и юзкейсов проходят одним PR, проще настроить сквозные проверки и единый стиль. Минусы — тяжелее CI, сложнее права доступа и больше риск «случайно» потянуть лишнюю зависимость.
Мульти‑репо лучше, когда команды и релизные циклы разные, либо часть кода должна быть изолирована юридически. Компромиссный вариант: монорепо для продуктов и отдельных пакетов + публикация «ядра» как версионируемых библиотек.
Практичная схема (названия условные, смысл важнее):
core/ — домен, юзкейсы, контракты (без UI и без инфраструктуры)adapters/ — реализации портов: HTTP, БД, очереди, файловые хранилищаapps/ — веб, мобильные клиенты, сервер (композиция зависимостей)packages/ — переиспользуемые библиотеки (например, валидация, логирование)tools/ — генераторы, скрипты, линтеры, шаблоныДаже в монорепо полезно относиться к core как к продукту: отдельная сборка, отдельные артефакты, понятное версионирование (часто достаточно семантического).
Подход: изменения в core публикуются как пакет(ы), а apps/* зависят от конкретной версии. Так проще откатываться и поддерживать несколько веток приложений. Для внутренних пакетов обычно достаточно приватного реестра.
Ключевое правило: core не импортирует ничего из веба/мобайла/сервера. Любые платформенные вещи оформляются как порты (интерфейсы) в core, а их реализации лежат в adapters.
Технические меры:
core не видел лишних библиотекcore собирается в «чистом» окружении без платформенных SDKЭта дисциплина делает сборку предсказуемой, а единое ядро — действительно переносимым между вебом, мобильными клиентами и сервером.
Единая кодовая база даёт сильное преимущество: вы можете проверять ключевую бизнес‑логику один раз — и быть уверенными, что одинаковые правила работают в вебе, мобайле и на сервере. Но для этого тесты и наблюдаемость должны быть частью архитектуры, а не «добавкой» в конце.
Начинайте с самого стабильного слоя — домена и юзкейсов. Это дешевле всего поддерживать и быстрее всего гонять.
Сделайте библиотеку тестовых данных общей: один генератор пользователей, заказов, прав доступа, типовых ошибок. Тогда веб, мобильные тесты и серверные проверки говорят «на одном языке» и не расходятся в трактовках.
Практика, которая окупается: хранить фикстуры в одном месте и давать доступ из всех пакетов, а также иметь несколько «срезов» данных — минимальный, типовой и стресс‑набор.
Чтобы понимать, что происходит в разных адаптерах, вводите единые правила:
Важно договориться о формате ошибок и событий, чтобы диагностика была одинаково понятна всем командам.
Разделите пайплайны:
Так единая кодовая база остаётся предсказуемой: критичная логика защищена постоянно, а тяжёлые проверки не превращают разработку в ожидание.
ИИ действительно помогает строить единую кодовую базу быстрее — но только если он работает «в рамках правил» архитектуры, а не подменяет их. Лучший результат получается, когда вы просите ИИ генерировать повторяемые заготовки, а решения по границам слоёв и контрактам принимаете осознанно и фиксируете в репозитории.
ИИ особенно эффективен там, где много механической работы и мало архитектурной неопределённости:
Важно: пусть ИИ генерирует «провода» (glue code), но не «истину» домена. Доменную модель и правила лучше держать максимально ручными и обсуждаемыми.
В этом смысле полезны платформы, которые ориентированы именно на производство приложений, а не на разовые ответы в чате. Например, TakProsto.AI позволяет собирать веб‑приложения (React), сервер (Go + PostgreSQL) и мобильные клиенты (Flutter) через диалог, при этом поддерживает экспорт исходников, деплой/хостинг, снапшоты и откат — что удобно для итераций без потери контроля.
Чтобы ИИ не превратил монорепозиторий в набор случайных решений, нужны автоматические и процессные ограничители:
Чаще всего проблемы выглядят так:
Промпт: укажите слой, вход/выход, ограничения и примеры.
Приёмка изменений:
Единая кодовая база даёт скорость и согласованность, но со временем может начать тормозить развитие продукта. Важно заранее видеть сигналы и иметь план «мягкого расхождения», чтобы не превратить монолит в узкое горлышко.
На практике тревожные симптомы довольно приземлённые:
Если вы всё чаще откладываете улучшения ядра «чтобы никого не сломать», единая база начинает стоить дороже, чем приносит.
Есть несколько типичных ошибок:
Вместо резкого «переписываем на микросервисы» лучше идти по шагам:
Чтобы углубиться, посмотрите другие материалы в /blog. Если выбираете формат внедрения и поддержку процесса разработки, может быть полезна страница /pricing.
Лучший способ понять возможности ТакПросто — попробовать самому.