Как каждая из них организует код, где они пересекаются и что учитывать при выборе.
Большинство .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.
Какую архитектуру использует ваша команда — и был ли это осознанный выбор или шаблон, который пришёл вместе с проектом? Интересно узнать, как это сработало на практике. 👇
ссылка на оригинал статьи https://habr.com/ru/articles/1089808/