Архитектура уровня Senior: паттерны, DDD, микросервисы, безопасность
Зачем эта тема нужна после производительности, данных и тестирования
К этому моменту курса вы уже умеете писать корректный C# код, понимать async/await, работать с EF Core и SQL, покрывать систему тестами и диагностировать проблемы через GC/профилирование/трейсинг. Следующий скачок к Senior — научиться принимать архитектурные решения так, чтобы система:
развивалась без постоянных переписываний
выдерживала рост команды и сложности
оставалась наблюдаемой и безопасной
имела понятные границы ответственностиАрхитектура Senior-уровня — это не набор модных слов, а дисциплина про границы, контракты, компромиссы и управление рисками.
Полезные источники:
Руководство по микросервисной архитектуре .NET
DDD и CQRS паттерны в микросервисах .NET
Dependency injection в ASP.NET Core
Security в ASP.NET Core
OWASP Top 10
Microsoft identity platformКак думает Senior: качество архитектуры как набор свойств
Архитектуру удобно оценивать через качества системы. Они часто конфликтуют, поэтому архитектор делает выбор.
Ключевые качества:
Поддерживаемость: изменения локальны и не разрушают систему.
Расширяемость: новые сценарии добавляются без переписывания старого.
Надёжность: ошибки изолируются, есть ретраи, таймауты, деградация.
Производительность: система укладывается в требования по задержкам и ресурсу.
Наблюдаемость: по логам/метрикам/трейсам можно понять, что происходит.
Безопасность: данные и доступ защищены на уровне дизайна и реализации.Важно: нет архитектуры, которая одновременно максимизирует всё. Например, микросервисы часто повышают автономность команд, но усложняют наблюдаемость и консистентность данных.
!Качественные атрибуты и их компромиссы
Паттерны и стили: как структурировать код и зависимости
Слой, модуль и компонент: термины без путаницы
Слой: логическое разделение ответственности (например, API, приложение, домен, инфраструктура).
Модуль: часть кода, которая развивается относительно независимо (например, Billing, Orders).
Компонент: развертываемая единица или крупный блок (например, сервис, библиотека).Senior-уровень начинается там, где вы перестаёте смешивать всё в одном слое и начинаете управлять зависимостями между частями.
Clean Architecture и Hexagonal: идея одна
У разных школ разные названия, но суть общая:
бизнес-логика не должна зависеть от UI, базы данных, очередей и фреймворков
внешние зависимости подключаются через интерфейсы
направление зависимостей идёт внутрь к доменуЭто продолжает линию курса про интерфейсы, тестирование и DI.
!Направление зависимостей к домену
Практическая структура проекта в .NET
Один из рабочих вариантов для монолита или модульного монолита:
MyApp.Domain — доменная модель и правила
MyApp.Application — сценарии (use cases), команды/запросы, транзакционные границы
MyApp.Infrastructure — EF Core, внешние клиенты, файловая система, брокер сообщений
MyApp.Api — ASP.NET Core контроллеры/эндпоинты, авторизация, сериализация
MyApp.Tests — unit и integrationВажная дисциплина:
Domain не ссылается на Infrastructure
Application знает только про контракты (интерфейсы), а реализации живут в InfrastructureDependency Injection как механизм, но не как архитектура
DI контейнер в ASP.NET Core решает задачу сборки графа зависимостей, но сам по себе не гарантирует хорошую архитектуру.
Типичные правила зрелого DI:
регистрировать зависимости по интерфейсам, если есть вариативность реализации
не вводить интерфейсы там, где нет причины для замены
держать жизненные циклы (scoped/singleton/transient) осознаннымиДокументация: Dependency injection в ASP.NET Core
DDD: как моделировать сложный бизнес без «анемичной модели»
DDD (Domain-Driven Design) — подход к проектированию, где вы строите код вокруг предметной области и языка бизнеса.
DDD не обязателен всегда. Он окупается, когда:
домен сложный и постоянно меняется
много правил, исключений и вариантов поведения
важно, чтобы модель была понятна не только разработчикамUbiquitous Language: общий язык как инструмент качества
Ubiquitous Language — единый язык, на котором одинаково говорят бизнес и разработчики.
Признаки, что язык работает:
названия классов и методов совпадают с терминами бизнеса
правила выражаются в коде прямо, а не в комментариях
тесты читаются как спецификацияBounded Context: границы смыслов
Bounded Context — граница, внутри которой термины и правила имеют однозначный смысл.
Пример: слово "Заказ" в интернет-магазине и в логистике может означать разные вещи.
Практический вывод:
один большой "универсальный" Order на весь бизнес часто превращается в конфликтную сущность
лучше иметь разные модели в разных контекстах и связать их интеграцией!Bounded Context и связи между контекстами
Entity и Value Object: что есть что
Entity (сущность): имеет идентичность, важна «та же самая» сущность во времени (OrderId, UserId).
Value Object (объект-значение): идентичности нет, важно значение целиком (например, Money, Email). Обычно делают неизменяемым.Пример value object, который защищает инвариант (валидный email):
Связь с предыдущими темами:
вы используете неизменяемость (как в record), чтобы снижать количество ошибок
вы делаете проверку на границах, чтобы не разносить null и невалидные значения по всей системеAggregate: транзакционная граница домена
Aggregate (агрегат) — группа объектов, которая должна быть консистентна в рамках одной операции изменения.
у агрегата есть Aggregate Root — главный объект, через который разрешены изменения
внутри агрегата вы поддерживаете инвариантыПример: Order как aggregate root, который не позволяет добавить отрицательное количество.
Практический смысл для EF Core и транзакций:
агрегат часто соответствует тому, что вы меняете атомарно в SaveChangesAsync
это помогает не раздувать транзакции и не пытаться "одной операцией" менять полсистемыDomain Service и Application Service: где какая логика
Domain Service: бизнес-правило, которое не принадлежит одной сущности естественным образом.
Application Service (use case): оркестрация шагов сценария, работа с репозиторием, транзакцией, интеграциями.Типичный зрелый вариант:
домен содержит правила
application слой собирает сценарий и управляет зависимостями
инфраструктура реализует I/OРепозитории и EF Core: полезное напряжение
Repository в DDD — абстракция коллекции агрегатов.
В .NET с EF Core часто выбирают один из подходов:
DbContext как реализация Unit of Work + ограниченный слой репозиториев
явные репозитории только там, где это упрощает домен и тестированиеКлючевые правила из темы EF Core сохраняются:
DbContext scoped, не использовать из нескольких потоков
проекции через Select, контроль N+1, AsNoTracking для чтенияДокументация про DDD в микросервисах .NET: DDD и CQRS паттерны в микросервисах .NET
Микросервисы: когда они нужны и какие проблемы создают
Микросервис — это не технология, а организационно-архитектурная единица
Обычно микросервис подразумевает:
отдельное развертывание
свою модель данных и часто свою базу данных
автономность командыПочти всегда цена микросервисов:
сложнее эксплуатация и CI/CD
сложнее наблюдаемость
сложнее безопасность (больше точек входа)
сложнее консистентность данных (нет простых транзакций на всё)Когда лучше не делать микросервисы
Признаки, что микросервисы сейчас навредят:
маленькая команда и нет потребности в независимых релизах
домен ещё не стабилен, границы контекстов не ясны
нет зрелых практик мониторинга, логирования и инцидент-менеджментаЧасто лучший шаг между "монолитом" и "микросервисами" — модульный монолит:
один деплой
строгие границы модулей
явные контракты между модулямиСогласованность данных: почему распределённая транзакция почти всегда плохая идея
В монолите вы легко делаете транзакцию на несколько таблиц.
В микросервисах транзакции между сервисами требуют сложной координации. Чаще выбирают eventual consistency: данные становятся согласованными не мгновенно, а спустя небольшое время.
Чтобы это работало, нужны паттерны надёжной интеграции.
Outbox: надёжная публикация событий
Проблема:
вы сохранили изменения в БД
вам нужно отправить событие в брокер сообщений
если приложение упало между этими шагами, система рассинхронизируетсяOutbox решает это так:
вместе с бизнес-данными в той же транзакции записывается запись в таблицу outbox
отдельный фоновый процесс читает outbox и публикует событияЭто связывает тему EF Core, транзакций и надёжности.
!Outbox для атомарности БД и публикации событий
Идемпотентность: защита от повторной обработки
В распределённых системах повторы нормальны:
ретраи
повторная доставка сообщения
таймаутыИдемпотентная операция даёт тот же итог при повторном выполнении.
Практика:
использовать ключ идемпотентности (например, RequestId)
хранить обработанные ключи и не выполнять действие дваждыУстойчивость: таймауты, ретраи, лимиты параллелизма
Это продолжает тему async/await и SemaphoreSlim:
всегда задавайте таймауты для внешних вызовов
используйте ретраи только для безопасных операций и с backoff
ограничивайте параллелизм к внешним ресурсамВ .NET для клиентской устойчивости часто используют Polly, но важно сначала понять принципы.
Наблюдаемость микросервисов
То, что в монолите было "просто логом", в микросервисах требует корреляции.
База:
сквозной TraceId через Activity
структурированные логи
метрики по RPS, latency, error rateЭто напрямую связывается с темой диагностики и Activity из прошлой статьи.
Безопасность как часть архитектуры, а не «после релиза»
Модель угроз: что защищаем и от кого
Senior-уровень начинается с вопроса: какие активы у нас есть и какие риски реальны.
Активы:
учетные записи и сессии
персональные данные
платежные данные
секреты (ключи, токены)
бизнес-операции (например, возврат денег)Даже простая модель угроз помогает не забыть критичное.
OWASP Top 10 как чеклист типовых уязвимостей
OWASP Top 10 — полезная карта рисков веб-приложений.
Источник: OWASP Top 10
С архитектурной точки зрения особенно важны:
контроль доступа
аутентификация и управление сессиями
инъекции
утечки данных
SSRFАутентификация и авторизация: разделяйте ответственность
Аутентификация отвечает на вопрос "кто ты?"
Авторизация отвечает на вопрос "что тебе можно?"В ASP.NET Core это поддерживается стандартными middleware.
Документация:
Security в ASP.NET Core
Microsoft identity platformПрактики зрелого уровня:
применять принцип наименьших привилегий: доступ только к необходимому
хранить роли и права как часть дизайна, а не как if-ы по всему коду
выносить правила в политики авторизацииВалидация входных данных и защита от инъекций
Ключевая мысль: доверять нельзя ни запросу пользователя, ни данным из другого сервиса.
Практики:
валидировать DTO на границе API
в EF Core не собирать SQL строками, использовать параметризацию
ограничивать размеры входных данных и сложность запросовСекреты и конфигурация
Нельзя хранить секреты в репозитории. В архитектуре должны быть предусмотрены:
секрет-хранилище (например, переменные окружения или специализированные сервисы)
ротация ключей
разделение окруженийДаже если вы не внедряете конкретный продукт, важно заложить механизм, а не "потом добавим".
Логи и персональные данные
Наблюдаемость легко превращается в утечку.
Правила:
не логировать пароли, токены, полные номера карт
маскировать PII, если она появляется в логах
управлять сроками хранения логовSupply chain: зависимости как часть атаки
В .NET вы зависите от NuGet пакетов.
Практики:
фиксировать версии
обновлять зависимости регулярно
включать проверки уязвимостей в CIКак связать всё вместе в одном сценарии
Архитектура Senior-уровня обычно строится от границ:
API слой принимает запрос, аутентифицирует, авторизует, валидирует
application слой выполняет сценарий, управляет транзакцией, вызывает домен
домен обеспечивает инварианты и понятную модель
инфраструктура делает I/O (EF Core, внешние сервисы)
события интеграции публикуются надёжно (например, через outbox)
тесты проверяют доменную логику и интеграцию, CI не даёт сломать качество
наблюдаемость позволяет доказательно диагностировать проблемыИтоги
После этой темы у вас должна появиться “карта” Senior-подхода:
архитектура измеряется качественными атрибутами и компромиссами
Clean Architecture помогает управлять зависимостями и тестируемостью
DDD даёт инструменты моделирования сложного бизнеса через bounded contexts, value objects и агрегаты
микросервисы полезны при определённых организационных и технических условиях, но требуют паттернов надёжности и наблюдаемости
безопасность должна быть встроена в дизайн: аутентификация, авторизация, валидация, секреты, контроль утечекЭта тема соединяет весь курс в практическую систему взглядов: от синтаксиса и коллекций до устойчивой, наблюдаемой и безопасной архитектуры.