Как каждая из них организует код, где они пересекаются и что учитывать при выборе.

Большинство.NET‑разработчиков знакомятся с этими тремя архитектурами в одном и том же порядке: N‑tier — в первых проектах, Clean Architecture — когда её приносит команда или шаблон, и Vertical Slice — когда о ней упоминают на код‑ревью или в докладе на конференции. Каждую из них подают как улучшение предыдущей. На практике они отвечают на несколько разные вопросы, и правильный выбор зависит скорее от проекта, чем от того, какая из них новее.
В этой статье разберём, что представляет собой каждая архитектура, посмотрим, как одна и та же функциональность раскладывается во всех трёх, и сравним их сильные стороны и издержки.
Зачем вообще нужна архитектура
Архитектура — это набор правил о трёх вещах: где лежит код, какие части от каких могут зависеть и насколько дорого будет что‑то изменить в будущем.
Не каждому проекту она нужна. Консольная утилита, которая читает файл, преобразует данные и записывает результат, может выполняться сверху вниз в одном классе, и слои только добавили бы ей церемоний. То же относится ко многим небольшим фоновым сервисам и скриптам, использующим одну общую библиотеку.
Архитектура начинает окупаться, когда кодовая база перерастает объём, который один человек может удержать в голове, когда над ней одновременно работают несколько разработчиков или когда бизнес‑логика должна пережить фреймворк, базу данных или UI вокруг неё. В этот момент правила предотвращают знакомый исход: бизнес‑логика, размазанная по контроллерам, обращения к базе прямо из представлений и отсутствие понятного места для следующей фичи.
Многоуровневая архитектура (N‑tier)
N‑tier — классический многоуровневый подход. Приложение делится на горизонтальные слои, обычно на три:
Presentation / API — контроллеры, модели запросов и ответов
Business Logic Layer (BLL) — сервисы, реализующие правила предметной области
Data Access Layer (DAL) — контекст базы данных, сущности и репозитории
Каждый слой зависит только от нижележащего. API вызывает BLL, BLL вызывает DAL, а DAL обращается к базе данных.
MyApp.sln ├── MyApp.API │ └── Controllers/ │ └── OrdersController.cs ├── MyApp.BLL │ ├── Interfaces/ │ │ └── IOrderService.cs │ └── Services/ │ └── OrderService.cs └── MyApp.DAL ├── AppDbContext.cs ├── Entities/ │ └── Order.cs ├── Interfaces/ │ ├── IOrderRepository.cs │ └── IUnitOfWork.cs ├── Repositories/ │ └── OrderRepository.cs └── UnitOfWork.cs
В.NET такой подход обычно сочетают с Entity Framework, паттерном Repository и классом Unit of Work. Некоторые разработчики считают два последних избыточными, поскольку DbContext уже работает как Unit of Work, а DbSet<T> — как репозиторий. Тем не менее команды часто сохраняют эти обёртки — ради единообразия между проектами и чтобы слой данных было проще подменять в тестах.
Замечание о терминологии: строго говоря, tier — это физическая граница развёртывания, а layer — логическая. По этой причине в документации Microsoft используется термин «N‑Layer». В повседневной.NET‑практике эти термины взаимозаменяемы, и статья следует этому соглашению.
Clean Architecture
Clean Architecture сохраняет идею слоёв, но меняет направление зависимостей на противоположное. Вместо того чтобы бизнес‑логика зависела от доступа к данным, всё зависит от бизнес‑логики.
Domain — сущности и ключевые бизнес‑правила, без внешних зависимостей
Application — сценарии использования (use cases), интерфейсы и DTO; зависит только от Domain
Infrastructure — база данных, файловое хранилище, внешние сервисы; реализует интерфейсы, объявленные в Application
API / Presentation — точка входа; связывает всё воедино через внедрение зависимостей
Главное правило — зависимости направлены внутрь. Проект Domain ничего не знает об Entity Framework, ASP.NET Core или какой‑либо базе данных.
У этого подхода было несколько названий. Алистер Кокберн описал гексагональную архитектуру, также известную как Ports and Adapters, в 2005 году. Джеффри Палермо описал луковую архитектуру (Onion Architecture) в 2008 году. Роберт Мартин популяризировал название Clean Architecture в 2012 году. Руководство по архитектуре от Microsoft рассматривает все эти варианты как одну и ту же идею под разными именами, и различаются они в основном тем, как их рисуют на схемах.
В.NET Clean Architecture очень часто используют вместе с CQRS (Command Query Responsibility Segregation) и библиотекой MediatR. Каждая операция становится либо командой, которая изменяет состояние, либо запросом, который его читает, и у каждой есть собственный обработчик.
MyApp.sln ├── MyApp.Domain │ └── Orders/ │ └── Order.cs ├── MyApp.Application │ ├── Common/ │ │ └── Interfaces/ │ │ └── IAppDbContext.cs │ └── Orders/ │ ├── Commands/ │ │ └── CreateOrder/ │ │ ├── CreateOrderCommand.cs │ │ ├── CreateOrderCommandHandler.cs │ │ └── CreateOrderCommandValidator.cs │ └── Queries/ │ └── GetOrderById/ │ ├── GetOrderByIdQuery.cs │ ├── GetOrderByIdQueryHandler.cs │ └── OrderDto.cs ├── MyApp.Infrastructure │ └── Persistence/ │ └── AppDbContext.cs └── MyApp.API └── Controllers/ └── OrdersController.cs
Одна команда и её обработчик выглядят так:
public record CreateOrderCommand(int CustomerId, decimal Amount) : IRequest<int>; public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, int> { private readonly IAppDbContext _context; public CreateOrderCommandHandler(IAppDbContext context) => _context = context; public async Task<int> Handle(CreateOrderCommand request, CancellationToken cancellationToken) { var order = new Order(request.CustomerId, request.Amount); _context.Orders.Add(order); await _context.SaveChangesAsync(cancellationToken); return order.Id; } }
Контроллер отправляет команду, не зная, кто её обработает:
[HttpPost] public async Task<int> Create(CreateOrderCommand command) => await _mediator.Send(command);
Практическое замечание: в 2025 году MediatR перешла на коммерческую лицензию для крупных организаций. Часть команд теперь использует альтернативные библиотеки или собственные простые интерфейсы обработчиков — сама архитектура при этом не меняется.
Vertical Slice
Архитектура вертикальных срезов (Vertical Slice), популяризированная Джимми Богардом, меняет не направление зависимостей, а ось организации кода.
И в N‑tier, и в Clean Architecture верхний уровень структуры — это набор слоёв, а каждая фича распределена между ними. Создание заказа затрагивает контроллер в одном проекте, обработчик или сервис в другом и сущность в третьем. Vertical Slice транспонирует эту структуру: верхний уровень — это набор фич, и каждая содержит всё, что ей нужно. Для тех, кто работает с базами данных, это тот же приём, что и PIVOT: строки становятся столбцами.
MyApp.sln └── MyApp ├── Features/ │ └── Orders/ │ ├── CreateOrder.cs │ └── GetOrderById.cs ├── Data/ │ └── AppDbContext.cs └── Program.cs
Срез часто хранит запрос, обработчик, валидацию и эндпоинт в одном файле:
public static class CreateOrder { public record Command(int CustomerId, decimal Amount); public static void MapEndpoint(IEndpointRouteBuilder app) => app.MapPost("/orders", Handle); private static async Task<int> Handle(Command command, AppDbContext db) { var order = new Order(command.CustomerId, command.Amount); db.Orders.Add(order); await db.SaveChangesAsync(); return order.Id; } }
В основе лежит принцип: код, который меняется вместе, должен лежать вместе. Срезы могут разделять общую инфраструктуру, например контекст базы данных, но избегают общей бизнес‑логики, поэтому изменение одной фичи с меньшей вероятностью затронет другую.
По духу Vertical Slice близка к тому, как CQRS обычно применяется внутри Clean Architecture, где каждая команда или запрос уже лежит в отдельной папке. Разница в том, что Vertical Slice делает основной единицей всего проекта фичу, а не слой.
Что их объединяет и чем они различаются
Все три разделяют ответственность, все три работают с внедрением зависимостей и все три можно использовать с Entity Framework. Различия — в том, что считается основной единицей структуры и насколько строго соблюдаются границы.
N‑tier |
Clean Architecture |
Vertical Slice |
|
|---|---|---|---|
Основная единица организации |
Технический слой |
Технический слой вокруг ядра предметной области |
Фича |
Направление зависимостей |
Сверху вниз |
Внутрь, к домену |
Внутри каждого среза |
Типичное число проектов |
3 |
4 и больше |
1–2 |
Где живёт одна фича |
Во всех слоях |
Во всех слоях, в большем числе папок |
В одной папке или файле |
Бизнес‑логика зависит от БД |
Да |
Нет |
Зависит от среза |
Объём кода на фичу |
Низкий‑средний |
Высокий |
Низкий‑средний |
Порог входа |
Низкий |
Средний‑высокий |
Низкий‑средний |
Типичное сочетание |
EF + Repository + Unit of Work |
CQRS + MediatR |
Обработчики в стиле CQRS, Minimal API |
Плюсы и минусы
N‑tier
Плюсы
Прост для понимания и быстрого старта
Знаком практически каждому.NET‑разработчику
Хорошо подходит для CRUD‑приложений и проектов со сжатыми сроками
Мало проектов и папок для навигации
Минусы
Бизнес‑логика зависит от деталей доступа к данным
Модульное тестирование бизнес‑логики может требовать базы данных или обширных моков
По мере роста проекта BLL и DAL становятся тесно связанными
Крупные классы сервисов со временем обрастают множеством несвязанных методов
Clean Architecture
Плюсы
Домен независим от фреймворков и инфраструктуры
Бизнес‑логику легко покрывать модульными тестами
Инфраструктуру можно заменить с ограниченным влиянием на остальную систему
Чёткие, проверяемые правила, которые масштабируются на большие команды
Наиболее узнаваемая структура в актуальных.NET‑вакансиях
Минусы
Значительно больше файлов и проектов на одну фичу
Более высокие затраты на начальную настройку
Чтобы проследить один запрос по коду, нужно больше переходов
Может быть тяжелее необходимого для небольших приложений или быстро меняющихся требований
Vertical Slice
Плюсы
Весь код фичи находится в одном месте
Добавление или удаление фичи редко затрагивает другие
Минимум церемоний для простых фич и возможность добавить структуру для сложных
Хорошо сочетается с Minimal API
Минусы
Менее предписывающая, поэтому единообразие зависит от дисциплины команды
Общие бизнес‑правила могут дублироваться в разных срезах
Меньше устоявшихся шаблонов и соглашений, чем у Clean Architecture
Менее знакома многим разработчикам и интервьюерам
Проекты, которым не подходит ни одна из них
Не у каждого приложения есть предметная область, которую стоит защищать. Интеграционный сервис, который получает результаты от внешнего API, преобразует их и передаёт дальше, ближе к паттерну «Адаптер», чем к любой из этих архитектур. Его основная задача — преобразование между двумя форматами, и разнесение этого преобразования по трём‑четырём проектам мало что даёт.
То же касается консольных инструментов, задач по расписанию и небольших внутренних утилит. Разумный вариант по умолчанию для них — простая структура, а архитектура вводится только тогда, когда проект до неё дорастает.
Практические заметки
Несколько наблюдений, которые редко попадают на архитектурные схемы, но важны в повседневной работе.
Навигация действительно стоит времени. В решении на Clean Architecture одна фича может быть разнесена по четырём проектам и нескольким вложенным папкам. Файлы легко найти поиском, но переходить между ними медленнее. В Visual Studio опция Track Active Item in Solution Explorer (“Отслеживать активный элемент в обозревателе решений”; Tools → Options → Projects and Solutions → General) заставляет Solution Explorer следовать за файлом, открытым в редакторе. Для разового перехода то же самое делает Sync with Active Document (Ctrl + [, S).
Шаблонный код стал дешевле. Самым частым практическим возражением против CQRS в связке с Clean Architecture был объём повторяющегося кода: класс команды, затем обработчик, затем валидатор, затем DTO — часто создаваемые копированием существующего набора с последующим переименованием. Ассистенты на базе ИИ теперь надёжно генерируют большую часть этого кода, что снимает значительную долю издержек, из‑за которых подход ещё несколько лет назад казался медленным.
Выигрыш в тестировании неравномерен. Поскольку фичи и бизнес‑правила изолированы, модульные тесты в Clean Architecture и Vertical Slice писать проще, чем в тесно связанном N‑tier‑проекте. Интеграционные тесты требуют примерно одинаковых усилий во всех трёх, поскольку в любом случае проходят через API, как бы ни был организован код за ним.
Частота изменений имеет значение. Проекты со сжатыми сроками и часто меняющимися требованиями сильнее всего ощущают дополнительную структуру Clean Architecture, поскольку каждое изменение затрагивает больше файлов. Проекты со стабильной сложной предметной областью и долгим ожидаемым сроком жизни выигрывают от неё больше всего.
Рынок
Каковы бы ни были технические компромиссы, Clean Architecture сегодня — вариант по умолчанию в.NET‑вакансиях, на собеседованиях и в шаблонах проектов. Разработчики, приходящие в профессию сейчас, часто изучают её первой, и многие команды используют её как стандартную структуру для новых сервисов.
Ситуация похожа на фронтенд‑разработку, где React — далеко не единственный хороший вариант, но именно его чаще всего требуют работодатели. Хорошее понимание Clean Architecture полезно для карьеры.NET‑разработчика независимо от того, какую структуру в итоге использует конкретный проект.
Как выбирать
Универсально правильного ответа нет, но несколько вопросов сужают выбор:
Сколько проживёт этот код? Недолговечные или экспериментальные проекты тяготеют к N‑tier или Vertical Slice. Долгоживущие системы со сложными правилами — к Clean Architecture.
Как часто будут меняться требования? Частые изменения вознаграждают меньший объём церемоний на фичу.
Насколько велика команда? Большим командам полезны строгие и общеизвестные границы Clean Architecture.
Что команда уже знает? Знакомая архитектура, применяемая последовательно, обычно лучше незнакомой, применяемой частично.
Все три — рабочие способы построить.NET‑приложение. Самое важное решение — выбрать одну осознанно и применять её последовательно.
Это перевод моей статьи, впервые опубликованной на Medium.
Какую архитектуру использует ваша команда — и был ли это осознанный выбор или шаблон, который пришёл вместе с проектом? Интересно узнать, как это сработало на практике. ?