$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогCargo workspaces для масштабирования: когда монорепо становится преимуществом

Cargo workspaces для масштабирования: когда монорепо становится преимуществом

$ sudo teach IT
·21 июля 2026 г.·11 мин·51
Cargo workspaces для масштабирования: когда монорепо становится преимуществом

Разберём, как правильно разнести библиотеки/сервисы, управлять зависимостями и избежать дублирования кода в больших Rust-проектах.

Содержание
Что меняется с ростом Rust-проектаТипичные проблемы без workspacesАрхитектура: библиотеки и сервисы в одном workspaceПример структуры репозиторияКорневой Cargo.toml workspaceКак “правильно разнести” код: правила декомпозиции1) Разделяйте доменные и инфраструктурные библиотеки2) Не делайте один общий “god crate”3) Управляйте направлением зависимостейПаттерн зависимостей: workspace вместо публикации на каждом шагеЕдиная стратегия версий зависимостейГде Cargo действительно помогаетПример: задаём общие зависимости на уровне workspaceТипичные ошибкиТонкости feature flags: избегайте “feature hell”Практика: документируйте и минимизируйте features в shared cratesПрактика: единые features на уровне workspaceСтратегия компоновки: общие типы ошибок и APIОшибки: от домена к инфраструктуреAPI-контракты и сериализацияКак избежать дублирования кода при росте командТри конкретных механизма против дублированияПараллельная сборка и “как не сделать всё медленно”Практика: тестируйте по скоупуПрактика: учитывайте зависимость по dev-dependenciesКонфигурация workspace для реального проектаКомпоненты, которые стоит стандартизироватьКогда монорепо может быть проблемой (и как это признать)Как снизить рискиВыбор: workspace vs “публиковать на crates.io”Технологический чеклист: как разложить проект без дублированияСтруктураЗависимостиПроцессКонтроль дублированияУглубление в Cargo: с чего начать, если нужно систематизировать знания

Когда Rust-проекты начинают расти, почти всегда приходит момент, когда «просто добавить ещё пару библиотек» превращается в задачу с архитектурными последствиями. Команды сталкиваются с типичными симптомами: дублирование кода между сервисами, несогласованные версии зависимостей, сложность обновлений, разный стиль API у библиотек, а также хаотичный процесс релизов. Часто виноват не компилятор и не менеджер пакетов, а структура репозитория и способ управления зависимостями.

Cargo workspaces — официальный механизм для работы с множеством пакетов (crates) в одном репозитории. В разговорах его нередко воспринимают как синоним монорепо, но в реальности речь о другом: о согласованной модели сборки, тестирования и версионирования набора взаимосвязанных crates. Правильно организованный workspace может стать преимуществом: сократить трение при разработке, облегчить рефакторинги и сделать обновление зависимостей предсказуемым.

Ниже разберём, как разносить библиотеки и сервисы, как управлять зависимостями, как избежать дублирования и каких ловушек стоит избегать. Примеры будут на уровне реальных конфигураций Cargo.


Что меняется с ростом Rust-проекта

Рост обычно начинается с «логического» разрастания: один сервис обрастает модулями, появляется необходимость вынести общие куски в библиотеку, затем появляется вторая команда/сервис, которой нужна та же логика (например, обёртки над API, модели данных, клиенты, типы ошибок, утилиты сериализации). На этом этапе монорепозиторий часто кажется удобным, но без правильной структуры он быстро превращается в источник новых проблем.

Типичные проблемы без workspaces

  1. Дублирование кода между репозиториями Если общий код вынесен «вручную» (copy/paste) или через публикацию crate на каждом этапе, то команда неизбежно теряет синхронность изменений.

  2. Несогласованность зависимостей Даже если у двух сервисов одинаковая библиотека, они могут тянуть разные версии «транзитивных» зависимостей. В Rust это иногда проявляется не как runtime-конфликт, а как несовместимости API (или в худшем случае — как эффект «работает локально, ломается в CI»).

  3. Дорогое обновление Обновить версию общей библиотеки во всех сервисах становится бюрократически сложно: приходится вручную пройтись по конфигам, собрать, проверить, разобраться в регрессиях.

  4. Нестабильный CI При большом количестве репозиториев или разрозненных проектов CI тратит время на повторяющиеся сборки, а тестирование общей логики превращается в «пачку отдельных шагов», не отражающих реальную связность системы.

Эти симптомы не уникальны для Rust, но Cargo-ecosystem позволяет решить их системно — и именно workspaces дают точку опоры.


Архитектура: библиотеки и сервисы в одном workspace

Ключевое решение: внутри одного workspace держать набор crates, которые развиваются совместно. В простом варианте это сервисы + общие библиотеки, на которые сервисы опираются через path-зависимости (внутри workspace).

Пример структуры репозитория

Рассмотрим типичный layout:

code
repo/
  Cargo.toml
  crates/
    common/
      Cargo.toml
      src/
    domain/
      Cargo.toml
      src/
  services/
    api/
      Cargo.toml
      src/
    worker/
      Cargo.toml
      src/

Такой подход помогает разделить ответственность: crates/ — библиотеки (повторно используемые доменные сущности, клиенты, утилиты), services/ — точки входа (binaries).

Корневой Cargo.toml workspace

Корневой файл определяет состав пакетов:

code
[workspace]
members = [
  "crates/common",
  "crates/domain",
  "services/api",
  "services/worker",
]
resolver = "2"

resolver = "2" — важная деталь. Он включает новую модель разрешения зависимостей, которая лучше работает в больших наборах crates, снижая неожиданные эффекты, возникающие из-за различных требований зависимостей.


Как “правильно разнести” код: правила декомпозиции

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

1) Разделяйте доменные и инфраструктурные библиотеки

Часто общие crates делятся на две категории:

  • domain / model: типы, правила, чистая логика без конкретики инфраструктуры (HTTP, БД, очереди).
  • infra / adapter: интеграции — клиенты API, репозитории, адаптеры к БД, сериализация специфичных форматов.

С практической точки зрения цель — не позволить сервисам и доменным модулям «тянуть» инфраструктуру слишком глубоко. Иначе вы получите плотную взаимосвязь и «невозможность вынести одну часть».

2) Не делайте один общий “god crate”

Одна из распространённых ловушек: собрать всё в crates/common, и он начинает разрастаться до уровня «всё зависит от всего». Плюсы исчезают: обновление общей библиотеки приводит к перекомпиляции всей экосистемы, а смена одной части может затронуть десятки crates.

Лучше создавать более узкие библиотеки:

  • crates/domain (модели и доменная логика),
  • crates/common/errors (единые ошибки/типизация),
  • crates/common/serialization (инварианты сериализации),
  • crates/api-client (клиенты к внешним сервисам).

3) Управляйте направлением зависимостей

Стандартное правило: нижние уровни (доменные библиотеки) не должны зависеть от верхних (сервисов). Сервис может зависеть от домена и инфраструктуры, но не наоборот.

Если вы обнаружили, что доменная библиотека тянет actix-web или tokio-postgres — вероятно, границы размыты. В Rust это особенно болезненно из-за того, что вы расширяете граф зависимостей и усложняете компиляцию.


Паттерн зависимостей: workspace вместо публикации на каждом шаге

Один из сильных эффектов workspaces — локальная связность. Внутри workspace вы можете ссылаться на crates через path и развивать изменения совместно.

Например, services/api использует crates/domain:

code
[dependencies]
domain = { path = "../../crates/domain" }
common = { path = "../../crates/common" }

Внешняя публикация (на crates.io) не требуется для внутреннего развития: это ускоряет цикл «изменил — проверил — поправил».

Когда же требуется публикация, вы можете:

  • использовать separate versioning (или единый процесс релиза),
  • публиковать лишь те crates, которые реально предназначены внешним пользователям,
  • или оставить внутренние crates непубликуемыми, если это соответствует бизнесу.

Единая стратегия версий зависимостей

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

Где Cargo действительно помогает

  • resolver = "2" снижает риск странных эффектов.
  • workspace dependency management позволяет задать версии один раз и использовать их последовательно.
  • dependency inheritance (через [workspace.dependencies]) уменьшает дрейф конфигураций.

Пример: задаём общие зависимости на уровне workspace

В корневом Cargo.toml:

code
[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
anyhow = "1"
thiserror = "1"

Далее в crate:

code
[dependencies]
serde.workspace = true
serde_json.workspace = true
tokio.workspace = true
anyhow.workspace = true
thiserror.workspace = true

Это даёт два эффекта:

  1. Версии не расходятся из-за человеческого фактора.
  2. Если нужно обновить tokio, правка делается один раз на уровне workspace.

Типичные ошибки

  • Смешивание workspace и явных версий
    Если где-то указать tokio = "1.37" напрямую, а где-то tokio.workspace = true, можно получить разнородное поведение при разрешении версий и всплывающие сложности при обновлении.

  • Слишком широкие feature flags
    У большой команды часто появляется привычка добавлять features «на всякий случай». В результате сервисы тянут больше зависимостей, чем нужно, и компиляции становятся медленнее. Особенно это заметно, когда включаются heavy features (например, TLS/crypto опции или дополнительные компоненты).


Тонкости feature flags: избегайте “feature hell”

Rust features — гибкий механизм, но в больших workspaces они часто приводят к эффекту: «на одном сервисе включено X, на другом — нет», а общий crate вдруг начинает работать иначе.

Практика: документируйте и минимизируйте features в shared crates

В библиотеке объявляйте features осмысленно и старайтесь не превращать их в «пульт управления всем». Например:

code
# crates/domain/Cargo.toml
[features]
default = []
serde_support = ["serde", "serde_json"]

А в коде:

  • включайте #[cfg(feature = "serde_support")] для специфичных реализаций,
  • оставляйте core-типы стабильными без условностей.

Практика: единые features на уровне workspace

Если библиотека поддерживает serde_support, то задайте это согласованно для всех сервисов:

code
# services/api/Cargo.toml
[dependencies]
domain = { path = "../../crates/domain", features = ["serde_support"] }

Или, если у вас несколько сервисов, задайте одинаковые features через политику (например, Cargo.toml для сервисных входов). Цель — чтобы «модели» не отличались по сериализации между сервисами.


Стратегия компоновки: общие типы ошибок и API

Один из наиболее практичных способов избежать дублирования — выделить общие контракты: модели ошибок, доменные события, единые DTO/форматы, клиенты и адаптеры.

Ошибки: от домена к инфраструктуре

Рекомендуемый подход — разделять:

  • доменные ошибки (семантика),
  • инфраструктурные ошибки (источники: БД, сетевые проблемы),
  • и маппинг между ними.

Если вы всё смешаете в одну схему ошибок в common, то сервисы будут тянуть инфраструктурные зависимости даже там, где они не нужны.

Например:

  • crates/domain — определяет enum доменных ошибок (или типы, которые можно конвертировать),
  • crates/common/errors — предоставляет единые обёртки/утилиты,
  • сервисы — маппят доменные ошибки в HTTP/графовую схему.

API-контракты и сериализация

Если сервисы используют одни и те же JSON-контракты, лучше хранить:

  • типы запросов/ответов,
  • правила сериализации (с serde),
  • и валидационные инварианты

в отдельном crate (условно crates/api-contracts). Тогда изменение контракта — это правка в одном месте, а сервисы получают одинаковые типы. Да, иногда это приводит к необходимости регулярной публикации внутри репозитория, но workspaces решает эту проблему локальным развитием.


Как избежать дублирования кода при росте команд

Дублирование обычно появляется не из-за отсутствия «лучшего» паттерна, а из-за разрыва в коммуникации и архитектурной дисциплине. workspaces помогают, но не отменяют необходимость правил.

Три конкретных механизма против дублирования

  1. “Переиспользуемость по умолчанию” через проектирование crates Если у вас есть доменные типы — они живут в библиотеке, а сервисы используют их через dependency, а не копируют.

  2. Снижение стоимости изменения общих частей Когда общий код находится в том же workspace, вы меняете его и одновременно прогоняете тесты всех зависимых crates. Это превращает рефакторинг из страшного события в регулярную практику.

  3. CI как механизм архитектурного контроля Не просто “cargo test” по одному пакету, а проверка связностей: тесты библиотек + тесты сервисов, зависящих от них. При грамотном setup CI становится инструментом борьбы с дрейфом.


Параллельная сборка и “как не сделать всё медленно”

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

Практика: тестируйте по скоупу

Вместо запуска всего репозитория можно собирать конкретные пакеты:

code
cargo test -p domain
cargo test -p api

А для всего workspace:

code
cargo test --workspace

Практика: учитывайте зависимость по dev-dependencies

Если shared crate имеет heavy dev-dependencies (например, большие mocking/fuzzing библиотеки), они будут влиять на компиляцию в тестовой фазе. Часто имеет смысл:

  • выносить отдельные тестовые утилиты в отдельный crate,
  • или использовать features для тестовой инфраструктуры.

Конфигурация workspace для реального проекта

Набор пакетов обычно включает:

  • общий формат кода,
  • единые политики lint/format,
  • использование workspace для common tooling.

Хотя это выходит за рамки Cargo как такового, на практике именно единая конфигурация снижает «шум» между сервисами.

Компоненты, которые стоит стандартизировать

  • Rust edition (обычно одинаковая),
  • версии rustfmt/clippy (или хотя бы конфигурация),
  • минимальные требования к компилятору,
  • единая стратегия по deny(warnings) (где уместно).

В Cargo это частично отражается через package.metadata или через единый набор настроек в CI, но важно понимать: workspace — это не только про зависимость, но и про процесс.


Когда монорепо может быть проблемой (и как это признать)

Workspaces — не универсальная кнопка. Монорепозиторий может стать проблемой, если:

  1. Crates с разной жизненной циклом развиваются слишком вместе Например, один сервис нужен для внешнего интегратора и живёт отдельно по релизам, а второй — экспериментальный. Если держать их в одном массиве releases и CI прогоняет всё, команда начнёт страдать от лишней работы.

  2. Непрерывная компиляция слишком дорогая Если компиляция длинная, а CI гоняет тесты всего workspace на каждый push — это быстро превратится в “slow feedback loop”.

  3. Слишком много пакетов без дисциплины Добавление “ещё одного shared crate” без чёткой границы и назначений приводит к расползанию архитектуры.

Как снизить риски

  • Разделяйте workspace на логические группы, если проект реально разнороден (иногда лучше несколько workspaces по доменам).
  • Делайте CI умным: шёл push — запускались затронутые пакеты.
  • Держите границы crates жёсткими: меньше “общего всего”, больше “общего для конкретной ответственности”.

Выбор: workspace vs “публиковать на crates.io”

Иногда команда задаётся вопросом: зачем держать shared crates внутри репозитория, если можно публиковать на crates.io и подключать по версии?

Ответ зависит от задачи:

  • Для внутреннего развития и быстрой итерации workspaces дают лучший цикл разработки: нет лишнего шага релиза, изменения видны сразу.
  • Для продуктов/публичных библиотек публикация может быть уместна, но часто удобнее начинать с workspace и публиковать то, что реально стабилизировалось.

Практический компромисс: сначала развиваете внутри workspace, а когда crate достигает стабильности API — делаете процесс публикации. В Rust это нормальная эволюция, а не попытка угадать будущее заранее.


Технологический чеклист: как разложить проект без дублирования

Вот компактный чеклист, который помогает избежать типичных архитектурных провалов.

Структура

  • Есть отдельные crates для domain/model и для infra/adapters.
  • Нет “god crate”, который включает всё подряд.
  • Направление зависимостей идёт “снизу вверх”: домен не тянет сервисы.

Зависимости

  • resolver = "2" включен на уровне workspace.
  • В workspace.dependencies вынесены версии общих библиотек.
  • features в shared crates минимальны и осмысленны.

Процесс

  • CI проверяет как минимум зависимости: тесты библиотек и сервисов, которые их используют.
  • Есть возможность тестировать/собирать по конкретным пакетам (-p), а не только --workspace.

Контроль дублирования

  • Контракты (типы запросов/ответов, события) вынесены в shared crate.
  • Ошибки и маппинги реализованы по слоям, а не одной общей “магией”.

Углубление в Cargo: с чего начать, если нужно систематизировать знания

Если вам важно не только “

Войдите, чтобы поставить лайк и оставить комментарий.

Автор

$ sudo teach IT

Cargo – для начинающих!

Курс по теме

Cargo – для начинающих!

С нуля до уверенной работы с Rust-проектами. Подходит для начинающих и тех, кто уже пишет на Rust. Разберитесь с Cargo раз и навсегда — и перестаньте копировать команды вслепую.

Бесплатно
Открыть курс

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу
Простыми словами о защищенном соединении

Простыми словами о защищенном соединении

Базовый курс для обычных людей: что такое защищённое соединение, как оно устроено, что оно умеет и чего не умеет, как выбрать сервис и настроить его на своих устройствах.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Сервис, который не падает - как построить безопасную зашифрованную сеть

Сервис, который не падает - как построить безопасную зашифрованную сеть

Практический курс по безопасности сети: как собрать собственный сервис защищённого доступа - от пустого сервера до подписки с автовыбором - и, что важнее, как потом не уронить его своими руками.

Перейти к курсу

Другие статьи

Cargo для продакшена: workspaces, фичи и стратегия версий зависимостей
cargo

Cargo для продакшена: workspaces, фичи и стратегия версий зависимостей

Разберём, как организовать Rust workspace, управлять optional features, фиксировать версии библиотек и уменьшать “дрейф” зависимостей при командной разработке и CI.

18 сентября 2026 г.
1000
FastAPI и фоновые задачи: когда лучше использовать BackgroundTasks, а когда очередь
fastapi

FastAPI и фоновые задачи: когда лучше использовать BackgroundTasks, а когда очередь

Поймём различия между синхронной обработкой, BackgroundTasks и внешними очередями. Разберём idempotency, ретраи и мониторинг фоновых процессов.

22 июля 2026 г.
980
Создание Telegram бота в 2026 легко и просто! Полный курсы!
создание

Создание Telegram бота в 2026 легко и просто! Полный курсы!

19 июня 2026 г.
1431
Как выбрать, куда идти в IT: веб, боты, данные — без хаоса
выбрать

Как выбрать, куда идти в IT: веб, боты, данные — без хаоса

Разберём самые понятные направления для новичка (веб, боты, аналитика/данные) и чем они отличаются по шагам обучения. В конце вы получите простой план, как проверить, что вам подходит, и какой стартовый путь выбрать.

5 октября 2026 г.
580
Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть
путь

Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть

Разберём структуру репозитория, как упаковать проект в понятную историю (проблема → решение → запуск → метрики качества) и какие артефакты повышают доверие: скринкасты, архитектурная схема, чек-лист продакшн-готовности.

17 сентября 2026 г.
1090
Как понять, какой язык программирования учить первым: простой тест и выбор
понять

Как понять, какой язык программирования учить первым: простой тест и выбор

Помогу новичку выбрать первый язык без стресса: объясню варианты по целям (веб, боты, анализ данных), что учить в первую очередь и как не потратить месяцы впустую. В конце — краткий план старта на ближайшие недели.

2 октября 2026 г.
630

Комментарии

Станьте первым в обсуждении

Поделитесь мнением о статье или задайте вопрос автору

Содержание

Что меняется с ростом Rust-проектаТипичные проблемы без workspacesАрхитектура: библиотеки и сервисы в одном workspaceПример структуры репозиторияКорневой Cargo.toml workspaceКак “правильно разнести” код: правила декомпозиции1) Разделяйте доменные и инфраструктурные библиотеки2) Не делайте один общий “god crate”3) Управляйте направлением зависимостейПаттерн зависимостей: workspace вместо публикации на каждом шагеЕдиная стратегия версий зависимостейГде Cargo действительно помогаетПример: задаём общие зависимости на уровне workspaceТипичные ошибкиТонкости feature flags: избегайте “feature hell”Практика: документируйте и минимизируйте features в shared cratesПрактика: единые features на уровне workspaceСтратегия компоновки: общие типы ошибок и APIОшибки: от домена к инфраструктуреAPI-контракты и сериализацияКак избежать дублирования кода при росте командТри конкретных механизма против дублированияПараллельная сборка и “как не сделать всё медленно”Практика: тестируйте по скоупуПрактика: учитывайте зависимость по dev-dependenciesКонфигурация workspace для реального проектаКомпоненты, которые стоит стандартизироватьКогда монорепо может быть проблемой (и как это признать)Как снизить рискиВыбор: workspace vs “публиковать на crates.io”Технологический чеклист: как разложить проект без дублированияСтруктураЗависимостиПроцессКонтроль дублированияУглубление в Cargo: с чего начать, если нужно систематизировать знания