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

Фото обложки: Anirban Sengupta, Unsplash
Фото обложки: Anirban Sengupta, Unsplash

Большинство.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.

Какую архитектуру использует ваша команда — и был ли это осознанный выбор или шаблон, который пришёл вместе с проектом? Интересно узнать, как это сработало на практике. ?

Комментарии (0)