Архитектура, которую я использую в проектах. Продолжение
Предыдущий пост об архитектуре на разных площадках собрал отличные вопросы, и я решил разобрать их подробнее в отдельной публикации.
Давайте вспомним основные требования
- Приложение должно легко покрываться тестами.
- Приложение должно без труда обновляться и переходить на новые версии .NET.
- Код должен быть простым.
- Поставщика данных должно быть легко заменить, например, с базы данных на внешний сервис.
- Не использовать библиотеки, которые влияют на архитектуру.
Эти требования ориентированы на практическое использование. Нет цели строго соответствовать DDD, CQRS, Event Sourcing и т.д.
Почему я против внешних библиотек, которые глубоко проникают в архитектуру
В целом, использование внешних библиотек — это всегда риск. Платные библиотеки могут существенно повысить стоимость поддержки проекта. Автор open source-библиотеки может потерять мотивацию, добавить вредоносный код или сделать проект платным. Конечно, можно сделать форк, но тогда поддержка и обновление лягут на вас. Если библиотека используется только в ограниченной части приложения, это приемлемо, но если она плотно интегрируется в архитектуру — это становится проблемой.
Такие библиотеки обычно используются для сокращения объёма кода, например, за счет рефлексии. Однако если вернуться к изначальным требованиям, такой цели не было. Не всегда "меньше кода" — это лучше, тем более сейчас IDE отлично помогают с автодополнением. Отказавшись от подобных библиотек, мы получаем преимущества: независимость проекта, удобство дебага и простоту навигации по коду.
Почему я использую юзкейсы, а не большие сервисы
В комментариях часто спрашивают, зачем создавать отдельный класс use case для каждого действия, если можно сделать один сервис с несколькими методами. Такой подход действительно не противоречит архитектурным требованиям — и раньше я тоже использовал сервисы. Но со временем сервисы разрастаются и превращаются в классы на 1000+ строк с десятками методов. Внутри появляются приватные методы, которые используются разными публичными, что вроде бы способствует переиспользованию, но на деле сильно увеличивает связность кода. Кроме того, такие сервисы часто имеют десятки зависимостей, что сильно усложняет тестирование.
Можно ли использовать базовые классы для use case или репозиториев?
Я предпочитаю так не делать. В требованиях я это пока не указывал, но всегда держу в уме, что должна быть возможность легко выделить часть функционала в отдельный сервис или микросервис. Наследование базовых классов усложняет этот процесс, навязывает поведение и увеличивает связность кода.
EF и Repository
Существует распространённое мнение, что если мы используем EF, то не нужно создавать дополнительную абстракцию — ведь EF уже абстрагирует работу с БД. Но в этом случае мы получаем сильную зависимость от ORM, хотя можем захотеть заменить часть операций, например, отправлять данные в Kafka вместо сохранения в БД. В моей архитектуре работа с EF происходит только на уровне Infrastructure, внутри реализации репозитория (важно, у меня нет базового репозитория, каждый — независим).
Пост на LinkedIn
Предыдущий пост об архитектуре на разных площадках собрал отличные вопросы, и я решил разобрать их подробнее в отдельной публикации.
Давайте вспомним основные требования
- Приложение должно легко покрываться тестами.
- Приложение должно без труда обновляться и переходить на новые версии .NET.
- Код должен быть простым.
- Поставщика данных должно быть легко заменить, например, с базы данных на внешний сервис.
- Не использовать библиотеки, которые влияют на архитектуру.
Эти требования ориентированы на практическое использование. Нет цели строго соответствовать DDD, CQRS, Event Sourcing и т.д.
Почему я против внешних библиотек, которые глубоко проникают в архитектуру
В целом, использование внешних библиотек — это всегда риск. Платные библиотеки могут существенно повысить стоимость поддержки проекта. Автор open source-библиотеки может потерять мотивацию, добавить вредоносный код или сделать проект платным. Конечно, можно сделать форк, но тогда поддержка и обновление лягут на вас. Если библиотека используется только в ограниченной части приложения, это приемлемо, но если она плотно интегрируется в архитектуру — это становится проблемой.
Такие библиотеки обычно используются для сокращения объёма кода, например, за счет рефлексии. Однако если вернуться к изначальным требованиям, такой цели не было. Не всегда "меньше кода" — это лучше, тем более сейчас IDE отлично помогают с автодополнением. Отказавшись от подобных библиотек, мы получаем преимущества: независимость проекта, удобство дебага и простоту навигации по коду.
Почему я использую юзкейсы, а не большие сервисы
В комментариях часто спрашивают, зачем создавать отдельный класс use case для каждого действия, если можно сделать один сервис с несколькими методами. Такой подход действительно не противоречит архитектурным требованиям — и раньше я тоже использовал сервисы. Но со временем сервисы разрастаются и превращаются в классы на 1000+ строк с десятками методов. Внутри появляются приватные методы, которые используются разными публичными, что вроде бы способствует переиспользованию, но на деле сильно увеличивает связность кода. Кроме того, такие сервисы часто имеют десятки зависимостей, что сильно усложняет тестирование.
Можно ли использовать базовые классы для use case или репозиториев?
Я предпочитаю так не делать. В требованиях я это пока не указывал, но всегда держу в уме, что должна быть возможность легко выделить часть функционала в отдельный сервис или микросервис. Наследование базовых классов усложняет этот процесс, навязывает поведение и увеличивает связность кода.
EF и Repository
Существует распространённое мнение, что если мы используем EF, то не нужно создавать дополнительную абстракцию — ведь EF уже абстрагирует работу с БД. Но в этом случае мы получаем сильную зависимость от ORM, хотя можем захотеть заменить часть операций, например, отправлять данные в Kafka вместо сохранения в БД. В моей архитектуре работа с EF происходит только на уровне Infrastructure, внутри реализации репозитория (важно, у меня нет базового репозитория, каждый — независим).
Пост на LinkedIn
🔥17👍3
Как NASA измеряет риски в коде. Уроки цикломатической сложности
После программных сбоев во время испытательного полета Boeing CST-100, Центр инженерной безопасности NASA провел масштабное исследование цикломатической сложности — и его выводы стоит знать каждому разработчику.
Что такое цикломатическая сложность?
Это метрика, которая показывает количество точек принятия решений в коде на основе его графа управления (Control Flow Graph). Проще говоря - сколько разных путей может выбрать программа. Каждый оператор ветвления или проверки добавляет один путь:
Главные выводы исследования NASA:
— Для критически важных систем NASA рекомендует держать цикломатическую сложность функции ≤ 15. В индустрии стандарт обычно колеблется от 10 до 20.
— Функцию со сложностью 28 пришлось тестировать 31 час, тогда как сложность 5 заняла меньше часа. Зависимость не линейная, а экспоненциальная!
— Низкая сложность упрощает не только тестирование — она напрямую связана с меньшим числом дефектов.
— Не путать с объемом кода. Количество строк не отражает сложность, можно написать 100 строк "прямолинейного" кода со сложностью 1 и всего 10 строк с несколькими вложенными условиями и куда большей сложностью.
Как посчитать в Visual Studio 2022 (C#)
Правый клик на
В отчёте для каждого метода будет показана
Полный отчет можно найти по названию — "Cyclomatic Complexity and Basis Path Testing Study, 2020"
После программных сбоев во время испытательного полета Boeing CST-100, Центр инженерной безопасности NASA провел масштабное исследование цикломатической сложности — и его выводы стоит знать каждому разработчику.
Что такое цикломатическая сложность?
Это метрика, которая показывает количество точек принятия решений в коде на основе его графа управления (Control Flow Graph). Проще говоря - сколько разных путей может выбрать программа. Каждый оператор ветвления или проверки добавляет один путь:
if , switch, циклы for, while, do…while (когда условие прерывает поток) и т. д.Главные выводы исследования NASA:
— Для критически важных систем NASA рекомендует держать цикломатическую сложность функции ≤ 15. В индустрии стандарт обычно колеблется от 10 до 20.
— Функцию со сложностью 28 пришлось тестировать 31 час, тогда как сложность 5 заняла меньше часа. Зависимость не линейная, а экспоненциальная!
— Низкая сложность упрощает не только тестирование — она напрямую связана с меньшим числом дефектов.
— Не путать с объемом кода. Количество строк не отражает сложность, можно написать 100 строк "прямолинейного" кода со сложностью 1 и всего 10 строк с несколькими вложенными условиями и куда большей сложностью.
Как посчитать в Visual Studio 2022 (C#)
Правый клик на
Solution → Analyze and Code Cleanup → Calculate Code Metrics.В отчёте для каждого метода будет показана
Cyclomatic Complexity вместе с другими метриками.Полный отчет можно найти по названию — "Cyclomatic Complexity and Basis Path Testing Study, 2020"
🔥14👍3
Какой длины должен быть метод?
Многие разработчики знают главное правило - метод должен выполнять одно действие и легко читаться, не создавая когнитивной нагрузки.
Однако в некоторых случаях нам нужны конкретные числовые рекомендации. Например, для начинающих разработчиков числовое ограничение более практично. Также числовые ограничения удобны при настройке статических анализаторов и настройке флагов при создании пул-реквестов.
Ниже прямые цитаты из ключевых источников, которые чаще всего всплывают в дискуссиях о размере методов.
Роберт Мартин, «Чистый код»
«Первое правило функций — они должны быть маленькими. Второе правило функций — они должны быть ещё меньше… Желательно, чтобы длина функции не превышала 20 строк»
Стив Макконнел, «Совершенный код»
«…Время от времени реализация сложного алгоритма будет требовать создания более длинного метода, и тогда методу можно будет позволить вырасти до 100–200 строк… Десятилетия исследований говорят о том, что методы такой длины не более подвержены ошибкам, чем методы меньших размеров.»
Android Java Style Guide
«Методы должны быть небольшими и решать конкретную задачу. Если метод превышает 40 строк, подумайте, можно ли разбить его на части, не нарушив структуру программы.»
Я же придерживаюсь рекомендаций статического анализатора Meziontou - не более 60 строк кода. Для меня и моей команды это стало хорошей практикой.
Многие разработчики знают главное правило - метод должен выполнять одно действие и легко читаться, не создавая когнитивной нагрузки.
Однако в некоторых случаях нам нужны конкретные числовые рекомендации. Например, для начинающих разработчиков числовое ограничение более практично. Также числовые ограничения удобны при настройке статических анализаторов и настройке флагов при создании пул-реквестов.
Ниже прямые цитаты из ключевых источников, которые чаще всего всплывают в дискуссиях о размере методов.
Роберт Мартин, «Чистый код»
«Первое правило функций — они должны быть маленькими. Второе правило функций — они должны быть ещё меньше… Желательно, чтобы длина функции не превышала 20 строк»
Стив Макконнел, «Совершенный код»
«…Время от времени реализация сложного алгоритма будет требовать создания более длинного метода, и тогда методу можно будет позволить вырасти до 100–200 строк… Десятилетия исследований говорят о том, что методы такой длины не более подвержены ошибкам, чем методы меньших размеров.»
Android Java Style Guide
«Методы должны быть небольшими и решать конкретную задачу. Если метод превышает 40 строк, подумайте, можно ли разбить его на части, не нарушив структуру программы.»
Я же придерживаюсь рекомендаций статического анализатора Meziontou - не более 60 строк кода. Для меня и моей команды это стало хорошей практикой.
👍6🔥3
Оптимизируем nullable-аннотации с помощью [NotNullWhen(true)]
Если вы используете nullable-аннотации в C#, скорее всего сталкивались с предупреждениями CS8602/CS8603.
Часть этих сообщений действительно помогает ловить потенциальные null-референции, однако в более сложных методах компилятор не всегда способен учесть пользовательские проверки и трактует значения как "возможно null", даже когда они проверены.
Эту проблему могут решить атрибуты из
Ниже показан пример, как
1. Без использования атрибута, получаем предупреждение
2. С использованием атрибута
Зачем это нужно
Меньше проверок на null — сокращается объtм кода.
Явные контракты в сигнатуре — упрощается чтение и ревью.
Точнее статический анализ — компилятор концентрируется на реальных ошибках.
Другие атрибуты, которые стоит знать
Посты на LinkedIn, Medium
Если вы используете nullable-аннотации в C#, скорее всего сталкивались с предупреждениями CS8602/CS8603.
Часть этих сообщений действительно помогает ловить потенциальные null-референции, однако в более сложных методах компилятор не всегда способен учесть пользовательские проверки и трактует значения как "возможно null", даже когда они проверены.
Эту проблему могут решить атрибуты из
System.Diagnostics.CodeAnalysis, позволяющие формально описать контракты методов.Ниже показан пример, как
[NotNullWhen(true)] устраняет лишние предупреждения в классическом Try*-паттерне.1. Без использования атрибута, получаем предупреждение
bool TryGetDomain(string? url, out string? domain)
{
if (url is null || !url.Contains("://"))
{
domain = null;
return false;
}
domain = url.Split("://")[1];
return true; // здесь domain гарантированно не null
}
if (TryGetDomain(link, out var d))
{
Console.WriteLine(d.Length); // Предупреждение CS8602 «возможен null»
}
2. С использованием атрибута
[NotNullWhen(true)] анализатор понимает что при true, out-параметр не будет null.bool TryGetDomain(
string? url,
[NotNullWhen(true)] out string? domain)
{
if (url is null || !url.Contains("://"))
{
domain = null;
return false; // при false null допустим
}
domain = url.Split("://")[1];
return true; // при true null невозможен
}
if (TryGetDomain(link, out var d))
{
Console.WriteLine(d.Length); // без варнингов и лишних проверок
}
Зачем это нужно
Меньше проверок на null — сокращается объtм кода.
Явные контракты в сигнатуре — упрощается чтение и ревью.
Точнее статический анализ — компилятор концентрируется на реальных ошибках.
Другие атрибуты, которые стоит знать
[MaybeNullWhen(true)] — метод возвращает true, однако значение может быть null.
[NotNullIfNotNull("param")] — если входной параметр не null, результат тоже не будет null.
[MemberNotNull("Field")] — метод гарантирует инициализацию поля или свойства до выхода.
[DoesNotReturnIf(true)] — метод не возвращает управление при выполнении условия (обычно выбрасывает исключение).Посты на LinkedIn, Medium
👍15🔥3
Как добавить юнит-тесты в легаси‑проект
Может возникнуть вопрос - зачем писать тесты для старого кода?
Давайте рассмотрим несколько причин:
— Защита ключевой логики, ошибка в которой может повлечь финансовые потери. Представьте, что в методе спрятан сложный расчёт цены или процентной ставки. Тест гарантирует, что будущие изменения не сломают его.
— Быстрая отладка. Вместо настройки БД под определенный кейс, SMTP‑сервера и других сервисов, вы можете протестировать нужный кусок кода отдельно.
— Пошаговый рефакторинг. Полная переписка проекта — это риск. Вынося небольшие части в тестируемые единицы, вы получаете мгновенные плюсы и уверенность для более масштабных изменений в будущем.
Подход "Простой объект" (Humble Object)
Чтобы начать писать тесты, не нужна идеальная архитектура. С шаблоном Humble Object:
1. Выносите бизнес‑логику в отдельный класс без внешних зависимостей.
2. Оставляете доступ к базе, отправку email, логирование и прочую инфраструктуру в исходном методе.
3. Пишете тесты только для этого нового простого класса. Остальной код не меняется.
Пример
Вот пример контроллера, который смешивает вычисления с вызовами email, базы данных и логирования. Тестирование такого кода напрямую потребует замокать EF Core, DateTime.Now, SMTP и логгер одновременно
Выносим расчет цены в отдельный класс
Пишем тесты
Сейчас контроллер просто связывает компоненты
Теперь вы можете начать тестировать легаси‑код сразу, без масштабного рефакторинга.
Достаточно просто вынести основную логику в небольшие классы, которые легко тестировать. Даже несколько тестов критических методов может начать приносить пользу.
Посты на LinkedIn, Medium
Может возникнуть вопрос - зачем писать тесты для старого кода?
Давайте рассмотрим несколько причин:
— Защита ключевой логики, ошибка в которой может повлечь финансовые потери. Представьте, что в методе спрятан сложный расчёт цены или процентной ставки. Тест гарантирует, что будущие изменения не сломают его.
— Быстрая отладка. Вместо настройки БД под определенный кейс, SMTP‑сервера и других сервисов, вы можете протестировать нужный кусок кода отдельно.
— Пошаговый рефакторинг. Полная переписка проекта — это риск. Вынося небольшие части в тестируемые единицы, вы получаете мгновенные плюсы и уверенность для более масштабных изменений в будущем.
Подход "Простой объект" (Humble Object)
Чтобы начать писать тесты, не нужна идеальная архитектура. С шаблоном Humble Object:
1. Выносите бизнес‑логику в отдельный класс без внешних зависимостей.
2. Оставляете доступ к базе, отправку email, логирование и прочую инфраструктуру в исходном методе.
3. Пишете тесты только для этого нового простого класса. Остальной код не меняется.
Пример
Вот пример контроллера, который смешивает вычисления с вызовами email, базы данных и логирования. Тестирование такого кода напрямую потребует замокать EF Core, DateTime.Now, SMTP и логгер одновременно
[HttpPost("create")]
public IActionResult Create(OrderDto dto)
{
var customer = _db.Customers.Find(dto.CustomerId);
var now = DateTime.Now;
decimal subtotal = dto.Lines.Sum(l => l.Price * l.Qty);
// Скидка на черную пятницу
if (now.Month == 11 && now.Day >= 25 && now.Day <= 30)
subtotal *= 0.8m;
decimal vat = subtotal * 0.12m;
decimal total = subtotal + vat;
customer.Balance -= total;
_db.SaveChanges();
_smtp.Send("sales@corp", customer.Email,
"Thanks for your order", $"Total = {total:C}");
_log.Write($"{now:u} Order {dto.Id} for {customer.Id} = {total}");
return Ok(new { id = dto.Id, total });
}Выносим расчет цены в отдельный класс
public class OrderPricer
{
private readonly IClock _clock;
private const decimal VatRate = 0.12m;
// в .NET 8 и выше можно использовать встроенный TimeProvider вместо IClock
public OrderPricer(IClock clock) => _clock = clock;
public decimal CalculateTotal(IEnumerable<OrderLineDto> lines)
{
decimal subtotal = lines.Sum(l => l.Price * l.Qty);
var now = _clock.UtcNow;
// Скидка на черную пятницу
if (now.Month == 11 && now.Day >= 25 && now.Day <= 30)
subtotal *= 0.8m;
decimal vat = subtotal * VatRate;
return subtotal + vat;
}
}
Пишем тесты
public class FixedClock : IClock
{
public DateTime UtcNow { get; init; }
}
public class OrderPricerTests
{
[Fact]
public void CalculatesTotalWithVat_NoDiscount()
{
var clock = new FixedClock { UtcNow = new DateTime(2025, 7, 19) };
var pricer = new OrderPricer(clock);
var total = pricer.CalculateTotal(new[]
{
new OrderLineDto { Price = 100, Qty = 1 }
});
Assert.Equal(112m, total);
}
// остальные тесты
}
Сейчас контроллер просто связывает компоненты
[HttpPost("create")]
public IActionResult Create(OrderDto dto)
{
var customer = _db.Customers.Find(dto.CustomerId);
var pricer = new OrderPricer(_clock);
decimal total = pricer.CalculateTotal(dto.Lines);
customer.Balance -= total;
_db.SaveChanges();
_smtp.Send("sales@corp", customer.Email,
"Thanks for your order", $"Total = {total:C}");
_log.Write($"{_clock.UtcNow:u} Order {dto.Id} for {customer.Id} = {total}");
return Ok(new { id = dto.Id, total });
}Теперь вы можете начать тестировать легаси‑код сразу, без масштабного рефакторинга.
Достаточно просто вынести основную логику в небольшие классы, которые легко тестировать. Даже несколько тестов критических методов может начать приносить пользу.
Посты на LinkedIn, Medium
🔥7✍6
Как подключить локальный MCP сервер к GitHub Copilot
MCP (Model Context Protocol) описывает, как ИИ-агент взаимодействует с внешними инструментами. Это простой способ встроить ваши процессы в Copilot Chat.
Для этого поста я написал MCP-сервер, который запускается локально (пример на TypeScript. Поддержка .NET уже в превью).
Этот MCP сравнивает c помощью Git две ветки и помогает провести раннее код-ревью прямо в Copilot Chat.
Что сделаем
— Поднимем сервер с тремя функциями:
— Подключим его к Copilot через mcp.json.
— Запустим промпт и получим обзор пулл-реквеста.
Зачем это разработчику
— Подключайте готовые серверы, оборачивайте свои приложения, скрипты в MCP серверы для расширения возможностей Copilot или других ИИ агентов.
— Прокачиваете навыки работы с ИИ сегодня это требование к AI-ready developer.
— Все операции выполняются в привычном интерфейсе Copilot Chat.
Шаги
— Клонируйте репозиторий https://github.com/sigmade/Git-MCP-Server
— Выполните команды ниже
— В папке build появится
— В
— Откройте любой проект в VS Code с двумя ветками в моем случае это
— Убедитесь что MCP стал доступен в VS Code, для этого в Copilot Chat в режиме Agent нажмите на кнопку Configure Tools... в выпадающем списке появится список доступных инструментов, найдите секцию MCP Server в ней должен отобразиться наш MCP - simple-merge-review а так же его функции
— В чат введите промпт например -
Copilot сам поймет какой MCP сервер нужен и какие функции использовать, после чего соберет диффы и покажет изменения, улучшения и риски.
*Если вы используете Cursor возможно ему придется явно прописать в промпт какой MCP использовать.
Итог
MCP дает разработчику возможность подключать любые скрипты, API или внутренние сервисы к ИИ-агенту и использовать их в Copilot Chat для автоматизации рутины и расширения возможностей для работы с ИИ агентами
MCP (Model Context Protocol) описывает, как ИИ-агент взаимодействует с внешними инструментами. Это простой способ встроить ваши процессы в Copilot Chat.
Для этого поста я написал MCP-сервер, который запускается локально (пример на TypeScript. Поддержка .NET уже в превью).
Этот MCP сравнивает c помощью Git две ветки и помогает провести раннее код-ревью прямо в Copilot Chat.
Что сделаем
— Поднимем сервер с тремя функциями:
quick_merge_summary, show_merge_diff, show_file_diff.— Подключим его к Copilot через mcp.json.
— Запустим промпт и получим обзор пулл-реквеста.
Зачем это разработчику
— Подключайте готовые серверы, оборачивайте свои приложения, скрипты в MCP серверы для расширения возможностей Copilot или других ИИ агентов.
— Прокачиваете навыки работы с ИИ сегодня это требование к AI-ready developer.
— Все операции выполняются в привычном интерфейсе Copilot Chat.
Шаги
— Клонируйте репозиторий https://github.com/sigmade/Git-MCP-Server
— Выполните команды ниже
npm install
npm run build
npm run start
— В папке build появится
index.js.— В
C:\Users\YourUserName\AppData\Roaming\Code\User создайте mcp.json и укажите путь к сбилженному файлу index.js.— Откройте любой проект в VS Code с двумя ветками в моем случае это
master и new-feature .— Убедитесь что MCP стал доступен в VS Code, для этого в Copilot Chat в режиме Agent нажмите на кнопку Configure Tools... в выпадающем списке появится список доступных инструментов, найдите секцию MCP Server в ней должен отобразиться наш MCP - simple-merge-review а так же его функции
quick_merge_summary, show_merge_diff, show_file_diff.— В чат введите промпт например -
Из ветки new-feature в master будет создан пулл-реквест. Сделай предварительное код-ревью
Copilot сам поймет какой MCP сервер нужен и какие функции использовать, после чего соберет диффы и покажет изменения, улучшения и риски.
*Если вы используете Cursor возможно ему придется явно прописать в промпт какой MCP использовать.
Итог
MCP дает разработчику возможность подключать любые скрипты, API или внутренние сервисы к ИИ-агенту и использовать их в Copilot Chat для автоматизации рутины и расширения возможностей для работы с ИИ агентами
🔥5👍4
Чистая архитектура против многослойной
В своем недавнем посте я рассказывал, как структурирую свои приложения, используя всего три слоя:
Сегодня я хочу подробно остановиться на ключевом отличии многослойной архитектуры от чистой - инверсии зависимостей.
Многослойная архитектура
В многослойной архитектуре API вызывает методы класса
Чистая архитектура с инверсией зависимостей
В чистой архитектуре вызовы происходят через абстракции.
Этот принцип лежит в основе создания поддерживаемого кода, и о нем часто спрашивают на собеседованиях.
Итог
Инвертируя зависимости, вы делаете свой слой
В своем недавнем посте я рассказывал, как структурирую свои приложения, используя всего три слоя:
API, Core (бизнес-логика) и Infrastructure (работа с базой данных, кэшем и внешними сервисами).Сегодня я хочу подробно остановиться на ключевом отличии многослойной архитектуры от чистой - инверсии зависимостей.
Многослойная архитектура
В многослойной архитектуре API вызывает методы класса
Core (UseCase), а Core в свою очередь вызывает методы класса Infrastructure (Repository). Такое разделение уже помогает распределить обязанности, но Core все еще знает о конкретных классах из Infrastructure. Это усложняет изоляцию и тестирование доменной логики.Чистая архитектура с инверсией зависимостей
В чистой архитектуре вызовы происходят через абстракции.
Core зависит от интерфейсов, а конкретные классы подключаются только во время выполнения. Поток управления остается прежним, но тестировать становится намного проще - мы можем подставлять моки и проверять доменную логику в полной изоляции.Этот принцип лежит в основе создания поддерживаемого кода, и о нем часто спрашивают на собеседованиях.
Итог
Инвертируя зависимости, вы делаете свой слой
Core чистым и тестируемым. Это ускоряет разработку и снижает риски при изменении требований. Сделайте инверсию зависимостей фундаментальной частью своего инструментария, чтобы обеспечить гибкость и надежность вашего кода по мере его роста. Для более глубокого погружения в тему, рекомендую Архитектурные принципы и Распространенные архитектуры веб-приложений👍13
Популяризирую C# среди казахстанского IT комьюнити. Ну и кстати подписывайтесь на меня в инстаграме https://www.ddinstagram.com/p/DNpRRw5swpJ https://www.instagram.com/landromad?igsh=MW51ZDM4aG04OGtjdA%3D%3D&utm_source=qr
👍14🔥5
Фича-флаги в .NET с IFeatureManager
Где пригодится
— Временное включение или быстрое отключение фичи, например при релизе новой версии или хотфиксе
— Отключение проблемного функционала при сбоях
— A/B-тесты
— Временные сценарии, такие как акции
Как это устроено
Регистрируем Feature Management в DI, проверяем состояние флага через
В итоге поведение приложения можно менять конфигурацией, без нового деплоя.
Такой подход экономит время, снижает риск ошибок при релизах и дает команде больше контроля над функциональностью прямо в продакшене.
IFeatureManager из пакета Microsoft.FeatureManagement управляет фича-флагами в .NET. Он позволяет выбирать нужную ветку логики во время работы приложения.Где пригодится
— Временное включение или быстрое отключение фичи, например при релизе новой версии или хотфиксе
— Отключение проблемного функционала при сбоях
— A/B-тесты
— Временные сценарии, такие как акции
Как это устроено
Регистрируем Feature Management в DI, проверяем состояние флага через
IFeatureManager и задаем значения в appsettings.json. При необходимости добавляем фильтры например, TimeWindow активирует флаг только в заданном интервале времени.В итоге поведение приложения можно менять конфигурацией, без нового деплоя.
Такой подход экономит время, снижает риск ошибок при релизах и дает команде больше контроля над функциональностью прямо в продакшене.
👍17🔥3
Как из Visual Studio 2022 работать с файлами в корне решения
В Visual Studio 2022 неудобно работать с файлами и папками в корневой директории, где лежит
Это особенно заметно, когда нужно быстро править/добавить конфиги, документы или служебные каталоги (например, .github с copilot-instructions md - лишь один из частых кейсов).
Мы, конечно, можем вместо решения открыть сразу корневую папку. Однако чаще удобнее работать именно с открытым
В VS Code это решено наличием полноценного File Explorer рядом с Solution Explorer.
В VS2022 аналог можно получить через расширение
Что дает File Explorer в VS2022
— Полный доступ ко всем файлам и папкам в корне репозитория, а не только к проектам из решения.
— Меньше переключений между IDE и проводником Единое рабочее окно Solution Explorer + файловый эксплорер.
Как настроить
—
— Установите и перезапустите Visual Studio.
— В Solution Explorer появится папка которая отображает содержимое корневой папки репозитория/решения
Если вы часто работаете с файлами в корне репозитория (конфиги, CI/CD, документацию, тот же .github), это расширение - must-have.
Пост на LinkedIn, Medium
В Visual Studio 2022 неудобно работать с файлами и папками в корневой директории, где лежит
.sln. Solution Explorer (Обозреватель решения) показывает состав решения, а не реальную структуру репозитория - многие папки/файлы остаются вне обозревателя. Это особенно заметно, когда нужно быстро править/добавить конфиги, документы или служебные каталоги (например, .github с copilot-instructions md - лишь один из частых кейсов).
Мы, конечно, можем вместо решения открыть сразу корневую папку. Однако чаще удобнее работать именно с открытым
Solution Explorer.В VS Code это решено наличием полноценного File Explorer рядом с Solution Explorer.
В VS2022 аналог можно получить через расширение
File Explorer от Mads Kristensen. Что дает File Explorer в VS2022
— Полный доступ ко всем файлам и папкам в корне репозитория, а не только к проектам из решения.
— Меньше переключений между IDE и проводником Единое рабочее окно Solution Explorer + файловый эксплорер.
Как настроить
—
Extensions - Manage Extensions - найдите File Explorer (Mads Kristensen). — Установите и перезапустите Visual Studio.
— В Solution Explorer появится папка которая отображает содержимое корневой папки репозитория/решения
Если вы часто работаете с файлами в корне репозитория (конфиги, CI/CD, документацию, тот же .github), это расширение - must-have.
Пост на LinkedIn, Medium
👍5✍2