C# Portal | Программирование
Каждое приложение, которое ты используешь, общается с сервером через API. Много лет стандартом был REST: - роуты, которые представляют ресурсы (/users, /posts) - классические методы: GET, POST, PUT, DELETE Работает отлично, но когда приложение разрастается…
Что происходит под капотом GraphQL-запроса
1) Клиент делает запрос
→ приложение отправляет запрос (HTTP POST) на эндпоинт
→ это один вызов, в котором клиент точно указывает, какие данные ему нужны
2) Сервер интерпретирует запрос
→ GraphQL парсит запрос и сверяет его со своей схемой — картой данных и связей между ними
→ если запрос не соответствует схеме (например, клиент запрашивает поле, которого бэкенд не отдает), запрос падает с ошибкой
3) Выполняются резолверы
→ у каждого поля в запросе есть функция, которая знает, как получить нужные данные
• user → ищет пользователя в базе данных
• posts → достает посты этого пользователя
→ GraphQL выполняет все эти функции и собирает результат в один ответ
4) Возврат ответа
→ сервер возвращает объект с той же структурой, что и исходный запрос
→ клиент получает ровно те данные, которые запросил, ничего лишнего
Клиент сам описывает, что ему нужно, а сервер знает, как это отдать
Вот это и есть GraphQL
На 2 иллюстрации хорошо видно, как GraphQL связывает всё воедино:
→ схема определяет, какие данные существуют и как они связаны
→ резолверы знают, откуда эти данные брать (база данных, API)
→ сервер собирает всё вместе и отдает клиенту единый ответ
👉 @KodBlog
1) Клиент делает запрос
→ приложение отправляет запрос (HTTP POST) на эндпоинт
/graphql→ это один вызов, в котором клиент точно указывает, какие данные ему нужны
2) Сервер интерпретирует запрос
→ GraphQL парсит запрос и сверяет его со своей схемой — картой данных и связей между ними
→ если запрос не соответствует схеме (например, клиент запрашивает поле, которого бэкенд не отдает), запрос падает с ошибкой
3) Выполняются резолверы
→ у каждого поля в запросе есть функция, которая знает, как получить нужные данные
• user → ищет пользователя в базе данных
• posts → достает посты этого пользователя
→ GraphQL выполняет все эти функции и собирает результат в один ответ
4) Возврат ответа
→ сервер возвращает объект с той же структурой, что и исходный запрос
→ клиент получает ровно те данные, которые запросил, ничего лишнего
Клиент сам описывает, что ему нужно, а сервер знает, как это отдать
Вот это и есть GraphQL
На 2 иллюстрации хорошо видно, как GraphQL связывает всё воедино:
→ схема определяет, какие данные существуют и как они связаны
→ резолверы знают, откуда эти данные брать (база данных, API)
→ сервер собирает всё вместе и отдает клиенту единый ответ
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3
Идемпотентность не зависит от HTTP-статусов. Она относится только к состоянию сервера.
Это довольно частое заблуждение у разработчиков: операция может оставаться идемпотентной, даже если сервер возвращает разные коды ответов. Главное, чтобы повторные запросы не меняли состояние системы.
Пример:
Даже если второй запрос возвращает 404 (потому что ресурса уже нет), операция всё равно идемпотентна, при условии, что повторное удаление не вызывает новых побочных эффектов.
👉 @KodBlog
Это довольно частое заблуждение у разработчиков: операция может оставаться идемпотентной, даже если сервер возвращает разные коды ответов. Главное, чтобы повторные запросы не меняли состояние системы.
Пример:
Запрос Ответ
DELETE /products/123 204
DELETE /products/123 404
Даже если второй запрос возвращает 404 (потому что ресурса уже нет), операция всё равно идемпотентна, при условии, что повторное удаление не вызывает новых побочных эффектов.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14
This media is not supported in your browser
VIEW IN TELEGRAM
Странный техно-эксперимент:
Парень использовал RayLib C# для генерации визуала, запускал всё в
Получилось что-то вроде стриминга гипермедиа-приложения или игры — без WebGL, без видео, чисто поток div-ов.
Безумие? Да. Гениально? Тоже да.
👉 @KodBlog
Парень использовал RayLib C# для генерации визуала, запускал всё в
ASP.NET webhost, снимал пиксельные кадры, превращал их в HTML-div-ы как пиксели, и слал эти div-ы в браузер через Server-Sent Events.Получилось что-то вроде стриминга гипермедиа-приложения или игры — без WebGL, без видео, чисто поток div-ов.
Безумие? Да. Гениально? Тоже да.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤14🥴7🔥3
Не выкатывай приложение, работающее с Kafka, в прод, без нагрузочного тестирования
Если твой Kafka-пайплайн не прогонялся под реальной нагрузкой, мелкие баги очень быстро превратятся в крупные на проде.
А такие проблемы с производительностью в продакшене обходятся дорого, и по деньгам, и по репутации.
Kafka-пайплайн может работать идеально в девелоперской среде, где трафик минимальный.
Но в проде неожиданные пики нагрузки легко вскрывают скрытые проблемы:
• проблемы с конкурентным доступом;
• неверные настройки для обработки backpressure;
• узкие места в I/O или сериализации при обработке сообщений;
• неправильное партиционирование, из-за которого нагрузка на консьюмеров распределяется неравномерно;
• неверно рассчитанное масштабирование числа консьюмеров под ожидаемую нагрузку;
• некорректное использование Kafka-транзакций, требующих координации;
• задержки commit-ack от нод.
В зависимости от твоего кейса, объёма данных, формата сообщений, сетевых условий, железа, конфигурации и качества кода, при работе с Kafka могут возникать разные проблемы:
• потеря или дублирование сообщений;
• перегрузка или падение брокеров;
• уязвимости или нарушения требований безопасности и комплаенса.
Для многих компаний скорость Kafka-пайплайна критична - особенно если она влияет на SLA.
Когда задаются SLO или SLA, латентность конкретных потоков должна оставаться стабильной, что делает задачу по обеспечению скорости и эффективности пайплайна ещё сложнее.
NBomber - мощный инструмент для симуляции реальной нагрузки на Kafka в .NET-приложениях.
Он позволяет тестировать продюсеров, консьюмеров и end-to-end флоу с помощью сценариев, написанных на C# или F#.
С NBomber можно:
• измерять полную латентность между продюсером и консьюмером;
• отслеживать, как меняется throughput при росте нагрузки;
• определять момент, когда консьюмер начинает отставать от продюсера.
Я использовал NBomber для нагрузочного тестирования боевого Kafka-пайплайна системы Fraud Detection, построенной на микросервисах.
👉 @KodBlog
Если твой Kafka-пайплайн не прогонялся под реальной нагрузкой, мелкие баги очень быстро превратятся в крупные на проде.
А такие проблемы с производительностью в продакшене обходятся дорого, и по деньгам, и по репутации.
Kafka-пайплайн может работать идеально в девелоперской среде, где трафик минимальный.
Но в проде неожиданные пики нагрузки легко вскрывают скрытые проблемы:
• проблемы с конкурентным доступом;
• неверные настройки для обработки backpressure;
• узкие места в I/O или сериализации при обработке сообщений;
• неправильное партиционирование, из-за которого нагрузка на консьюмеров распределяется неравномерно;
• неверно рассчитанное масштабирование числа консьюмеров под ожидаемую нагрузку;
• некорректное использование Kafka-транзакций, требующих координации;
• задержки commit-ack от нод.
В зависимости от твоего кейса, объёма данных, формата сообщений, сетевых условий, железа, конфигурации и качества кода, при работе с Kafka могут возникать разные проблемы:
• потеря или дублирование сообщений;
• перегрузка или падение брокеров;
• уязвимости или нарушения требований безопасности и комплаенса.
Для многих компаний скорость Kafka-пайплайна критична - особенно если она влияет на SLA.
Когда задаются SLO или SLA, латентность конкретных потоков должна оставаться стабильной, что делает задачу по обеспечению скорости и эффективности пайплайна ещё сложнее.
NBomber - мощный инструмент для симуляции реальной нагрузки на Kafka в .NET-приложениях.
Он позволяет тестировать продюсеров, консьюмеров и end-to-end флоу с помощью сценариев, написанных на C# или F#.
С NBomber можно:
• измерять полную латентность между продюсером и консьюмером;
• отслеживать, как меняется throughput при росте нагрузки;
• определять момент, когда консьюмер начинает отставать от продюсера.
Я использовал NBomber для нагрузочного тестирования боевого Kafka-пайплайна системы Fraud Detection, построенной на микросервисах.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3
Я как кодер довольно щепетилен в разнице между цифрой 0 и буквой O.
В конце созвона саппорт продиктовал мне номер тикета:
четыре, один, восемь, T, A, два, «ОУ»
Зная, что обычные люди вечно путают, я уточнил:
«Это ноль, цифра? Или O, буква?»
(долгая пауза)
«Это ноль, буква.»
👉 @KodBlog
В конце созвона саппорт продиктовал мне номер тикета:
четыре, один, восемь, T, A, два, «ОУ»
Зная, что обычные люди вечно путают, я уточнил:
«Это ноль, цифра? Или O, буква?»
(долгая пауза)
«Это ноль, буква.»
Please open Telegram to view this post
VIEW IN TELEGRAM
😁36🤯25🔥2
Разбор самой жёсткой уязвимости в .NET. Request smuggling и CVE-2025-55315
https://andrewlock.net/understanding-the-worst-dotnet-vulnerability-request-smuggling-and-cve-2025-55315/
В статье объяснили, что такое request smuggling, разбирают свежую дыру в AspNetCore с критичностью 9.9 и показывают, как атакеры могут это дело использовать.
👉 @KodBlog
https://andrewlock.net/understanding-the-worst-dotnet-vulnerability-request-smuggling-and-cve-2025-55315/
В статье объяснили, что такое request smuggling, разбирают свежую дыру в AspNetCore с критичностью 9.9 и показывают, как атакеры могут это дело использовать.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4
image_2025-10-29_09-13-40.png
2 MB
Проект на выходных: змейка на C#
Код получился кривоватый. Уверен, есть тысяча способов сделать это лучше. Я просто пытался сам допереть, как всё работает.
Немного олдскульной ностальгии по Nokia.
👉 @KodBlog
Код получился кривоватый. Уверен, есть тысяча способов сделать это лучше. Я просто пытался сам допереть, как всё работает.
Немного олдскульной ностальгии по Nokia.
Please open Telegram to view this post
VIEW IN TELEGRAM
👏14🥴7❤2👍2
Media is too big
VIEW IN TELEGRAM
Только что вышел Minecraftonia — воксельный движок на C# 13/.NET 9 и Avalonia. В проекте экспериментируют с кастомным воксельным рейтрейсингом, процедурной генерацией террейна и отзывчивым десктопным интерфейсом, при этом всё полностью кроссплатформенно.
Ссылка на релиз: https://github.com/wieslawsoltes/Minecraftonia/releases/tag/v0.1.0
👉 @KodBlog
Ссылка на релиз: https://github.com/wieslawsoltes/Minecraftonia/releases/tag/v0.1.0
Please open Telegram to view this post
VIEW IN TELEGRAM
😁15🔥14🥴3
В Entity Framework Core ToDictionaryAsync (и синхронный ToDictionary) вытягивают из базы весь объект целиком.
Если глянуть на сигнатуру ToDictionaryAsync:
Аргументы тут обычные лямбды (Func<TSource, ...>), а не выражения (Expression<Func<...>>). Значит, вычисление этих селекторов происходит на клиенте.
Допустим, у нас есть такой класс:
И вот такой запрос:
Звучит красиво, но по факту EF притащит не только Author и Title, а вообще все поля, например, и Content тоже. На это есть issue, и я случайно наткнулся и офигел.
Как обойти проблему: сначала спроецировать данные через Select, это уже выполнится на стороне сервера:
👉 @KodBlog
Если глянуть на сигнатуру ToDictionaryAsync:
public static Task<Dictionary<TKey, TElement>> ToDictionaryAsync<TSource, TKey, TElement>(
this IQueryable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector,
CancellationToken cancellationToken = default)
where TKey : notnull
=> ToDictionaryAsync(source, keySelector, elementSelector, comparer: null, cancellationToken);
Аргументы тут обычные лямбды (Func<TSource, ...>), а не выражения (Expression<Func<...>>). Значит, вычисление этих селекторов происходит на клиенте.
Допустим, у нас есть такой класс:
public class BlogPost
{
public int Id { get; set; }
public required string Title { get; set; }
public required string Description { get; set; }
public required string Content { get; set; }
public required string Author { get; set; }
}
И вот такой запрос:
return await dbContext.BlogPosts
.Where(...)
.ToDictionaryAsync(k => k.Author, v => v.Title);
Звучит красиво, но по факту EF притащит не только Author и Title, а вообще все поля, например, и Content тоже. На это есть issue, и я случайно наткнулся и офигел.
Как обойти проблему: сначала спроецировать данные через Select, это уже выполнится на стороне сервера:
return await dbContext.BlogPosts
.Where(...)
.Select(s => new { Author = s.Author, Title = s.Title })
.ToDictionaryAsync(k => k.Author, v => v.Title);
Please open Telegram to view this post
VIEW IN TELEGRAM
❤17🔥7🤔2
Мне очень нравится, что в C# 14 убрали лишнюю возню, связанную с полями, лежащими в основе свойств.
Особенно круто для таких штук, как INotifyPropertyChanged, потому что больше не нужен отдельный приватный field только ради вызова уведомления.
Ссылка: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14#the-field-keyword
👉 @KodBlog
Особенно круто для таких штук, как INotifyPropertyChanged, потому что больше не нужен отдельный приватный field только ради вызова уведомления.
Ссылка: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14#the-field-keyword
Please open Telegram to view this post
VIEW IN TELEGRAM
❤15🔥12🥰2👍1
Перемещение Файлов и Папок в Корзину в .NET
При работе с файлами и папками в .NET-приложениях на Windows иногда нужно отправлять объекты в корзину, а не удалять их насовсем (File.Delete, Directory.Delete). Это позволяет пользователю восстановить случайно удалённые данные. Вот как сделать это через Windows Shell API:
Этот метод подходит и для файлов, и для каталогов:
👉 @KodBlog
При работе с файлами и папками в .NET-приложениях на Windows иногда нужно отправлять объекты в корзину, а не удалять их насовсем (File.Delete, Directory.Delete). Это позволяет пользователю восстановить случайно удалённые данные. Вот как сделать это через Windows Shell API:
static void MoveToRecycleBin(string path)
{
var shellType = Type.GetTypeFromProgID("Shell.Application", throwOnError: true)!;
dynamic shellApp = Activator.CreateInstance(shellType)!;
// https://learn.microsoft.com/en-us/windows/win32/api/shldisp/ne-shldisp-shellspecialfolderconstants?WT.mc_id=DT-MVP-5003978
var recycleBin = shellApp.Namespace(0xa);
// https://learn.microsoft.com/en-us/windows/win32/shell/folder-movehere?WT.mc_id=DT-MVP-5003978
recycleBin.MoveHere(path);
}
Этот метод подходит и для файлов, и для каталогов:
MoveToRecycleBin(@"C:\path\to\file.txt");
MoveToRecycleBin(@"C:\path\to\directory");
Please open Telegram to view this post
VIEW IN TELEGRAM
👍17❤3🔥2
This media is not supported in your browser
VIEW IN TELEGRAM
От чат‑бота к агентам: Microsoft Agent Framework в .NET
Microsoft опубликовала руководство по переходу с обычных чат-ботов на AI-агентов в .NET. В примерах показывают, как подключить Microsoft Agent Framework к AI-шаблонам, зарегистрировать агента в Program.cs, вынести поиск в инструмент через
Есть упоминание расширений, мультиягентов, кастомного middleware и развёртывания в Azure с рабочими примерами. Агент уже не болтает, а самостоятельно решает задачи.
👉 @KodBlog
Microsoft опубликовала руководство по переходу с обычных чат-ботов на AI-агентов в .NET. В примерах показывают, как подключить Microsoft Agent Framework к AI-шаблонам, зарегистрировать агента в Program.cs, вынести поиск в инструмент через
SearchAsync, настроить Azure OpenAI и векторное хранилище, а также включить телеметрию через OpenTelemetry и тестирование в .NET Aspire. Есть упоминание расширений, мультиягентов, кастомного middleware и развёртывания в Azure с рабочими примерами. Агент уже не болтает, а самостоятельно решает задачи.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6🥴6🤨2👍1
Это может быть первым шагом при написании юнит-тестов.
Не всегда понятно, с чего начать. Но шаблон AAA почти всегда выручает.
// act ... код, который тестируем
// ДОЛЖЕН БЫТЬ ОДИН КОНКРЕТНЫЙ ВЫЗОВ
// arrange ... моки и подготовка окружения для act
// assert ... проверки результата act
И вот — у вас уже есть юнит-тест.
Дальше можно:
a) дублировать Test01 и менять под следующий кейс
b) параметризовать Test01, чтобы прогонять сразу несколько вариантов
В конце просто переименовать Test01..TestN во что-то осмысленное.
👉 @KodBlog
Не всегда понятно, с чего начать. Но шаблон AAA почти всегда выручает.
// act ... код, который тестируем
// ДОЛЖЕН БЫТЬ ОДИН КОНКРЕТНЫЙ ВЫЗОВ
// arrange ... моки и подготовка окружения для act
// assert ... проверки результата act
И вот — у вас уже есть юнит-тест.
Дальше можно:
a) дублировать Test01 и менять под следующий кейс
b) параметризовать Test01, чтобы прогонять сразу несколько вариантов
В конце просто переименовать Test01..TestN во что-то осмысленное.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15
Requestum — новая быстрая и бесплатная альтернатива MediatR
После новостей о коммерциализации MediatR разработчики начали искать замену. Автор представил Requestum — open source-медиатор под MIT-лицензией, с упором на производительность и простоту миграции.
Библиотека явно разделяет команды, запросы и события, использует отдельные Execute/Handle/Publish, поддерживает sync и async middleware, а также множественных подписчиков. Интеграция с DI минимальная, можно подменить MediatR почти без правок кода.
Бенчмарки показывают ускорение на 20–50% и снижение аллокаций на 30–60%. API прозрачный, CQRS-подход сохраняется, а синхронные обработчики работают реально синхронно.
Requestum уже доступен на NuGet, исходники на GitHub. Хороший вариант для тех, кто не хочет зависеть от лицензий и хочет выжать максимум из .NET-медиатора.
Читать подробнее: https://habr.com/ru/articles/961936/
👉 @KodBlog
После новостей о коммерциализации MediatR разработчики начали искать замену. Автор представил Requestum — open source-медиатор под MIT-лицензией, с упором на производительность и простоту миграции.
Библиотека явно разделяет команды, запросы и события, использует отдельные Execute/Handle/Publish, поддерживает sync и async middleware, а также множественных подписчиков. Интеграция с DI минимальная, можно подменить MediatR почти без правок кода.
Бенчмарки показывают ускорение на 20–50% и снижение аллокаций на 30–60%. API прозрачный, CQRS-подход сохраняется, а синхронные обработчики работают реально синхронно.
Requestum уже доступен на NuGet, исходники на GitHub. Хороший вариант для тех, кто не хочет зависеть от лицензий и хочет выжать максимум из .NET-медиатора.
Читать подробнее: https://habr.com/ru/articles/961936/
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2👨💻1
Совет по Visual Studio:
Когда создаешь метод, который возвращает JSON через raw string literal, добавь
/* lang=json*/
Этот момент нигде не задокументирован. Если в JSON будет синтаксическая ошибка вроде пропущенной запятой, Visual Studio подсветит её.
👉 @KodBlog
Когда создаешь метод, который возвращает JSON через raw string literal, добавь
/* lang=json*/
Этот момент нигде не задокументирован. Если в JSON будет синтаксическая ошибка вроде пропущенной запятой, Visual Studio подсветит её.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍49🔥16❤8👏2
This media is not supported in your browser
VIEW IN TELEGRAM
Тестируй API прямо в браузере
Есть опенсорсный инструмент Hoppscotch. Позволяет тестировать, дебажить и шарить API прямо из веб-браузера.
Отличная альтернатива Postman. Легкий и быстрый.
👉 @KodBlog
Есть опенсорсный инструмент Hoppscotch. Позволяет тестировать, дебажить и шарить API прямо из веб-браузера.
Отличная альтернатива Postman. Легкий и быстрый.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9
Конструкторное внедрение зависимостей и программирование через интерфейсы – основа хорошо тестируемого кода.
Слева: код Боба в PR
Справа: код Дейва в PR
Код Боба проходит ревью
Код Дейва заворачивают
Будь как Боб.
👉 @KodBlog
Слева: код Боба в PR
Справа: код Дейва в PR
Код Боба проходит ревью
Код Дейва заворачивают
Будь как Боб.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤13💯2
Критические изменения в Entity Framework 10
https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/breaking-changes
👉 @KodBlog
https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/breaking-changes
Please open Telegram to view this post
VIEW IN TELEGRAM
Docs
Breaking changes in EF Core 10 (EF10) - EF Core
List of breaking changes introduced in Entity Framework Core 10 (EF10)
🤯1
Когда абстракции реально помогают
Хорошие абстракции изолируют то, что действительно меняется. То, что завтра может поехать.
Пример: платежи
Бизнес-логика не должна напрямую зависеть от SDK Stripe. Если захочешь перейти на Adyen или Braintree, не должно быть каскадного переписывания половины проекта. В таком случае абстракция полностью оправдана:
Теперь доменная логика не завязана на конкретного провайдера:
Абстракция закрывает нестабильную зависимость . Меняется только один класс, когда провайдеры выкатывают новые API или ты решаешь их сменить. Это хороший пример: он реально приносит пользу.
Когда абстракции превращаются в техдолг
Проблема начинается, когда абстрагируем то, что вообще-то стабильно. Получается лишний слой, который никому не нужен.
Репозиторий, который потерял смысл
Начиналось всё мило:
Но требования растут:
Интерфейс раздувается, запросы лезут прямо в контракты. А ведь EF Core уже умеет делать это из коробки через LINQ: типобезопасно, оптимизируемо и без излишних прокладок. Репозитории были полезны, когда ORM были кривые. Сейчас это часто просто лишнее.
Я тоже таким грешил. Но взросление разработчика — это понимать, когда паттерн становится анти-паттерном.
Сервисные обёртки:
Нормальный пример: обёртка над внешним API
Все детали HTTP и сериализации спрятаны внутри. Это даёт ценность.
Плохой пример: сервис-прокладка
Просто пробрасывание вызовов. Никаких правил, никакой логики. Таких слоёв становится много, а толку то ноль.
Как принимать нормальные решения
- Абстрагируй политику, а не механику
Политика меняется: платёжки, стратегии ретраев, кеши
Механика стабильна: LINQ, HttpClient, JSON
- Жди второй реализации
Одна реализация? Не спеши с интерфейсами.
Абстракция должна появляться из реальных требований, а не из фантазий.
- Разграничивай
Внутри приложения — конкретика
На границах системы — абстракции
Как выпиливать неудачные абстракции
Спроси себя: код станет проще без неё? Если да = смело удаляй.
👉 @KodBlog
Хорошие абстракции изолируют то, что действительно меняется. То, что завтра может поехать.
Пример: платежи
Бизнес-логика не должна напрямую зависеть от SDK Stripe. Если захочешь перейти на Adyen или Braintree, не должно быть каскадного переписывания половины проекта. В таком случае абстракция полностью оправдана:
public interface IPaymentProcessor
{
Task ProcessAsync(Order order, CancellationToken ct);
}
public class StripePaymentProcessor : IPaymentProcessor
{
public async Task ProcessAsync(Order order, CancellationToken ct)
{
// Stripe-специфика
}
}
public class AdyenPaymentProcessor : IPaymentProcessor
{
public async Task ProcessAsync(Order order, CancellationToken ct)
{
// Другая API, тот же бизнес-результат
}
}
Теперь доменная логика не завязана на конкретного провайдера:
public class CheckoutService(IPaymentProcessor processor)
{
public Task CheckoutAsync(Order order, CancellationToken ct) =>
processor.ProcessAsync(order, ct);
}
Абстракция закрывает нестабильную зависимость . Меняется только один класс, когда провайдеры выкатывают новые API или ты решаешь их сменить. Это хороший пример: он реально приносит пользу.
Когда абстракции превращаются в техдолг
Проблема начинается, когда абстрагируем то, что вообще-то стабильно. Получается лишний слой, который никому не нужен.
Репозиторий, который потерял смысл
Начиналось всё мило:
public interface IUserRepository
{
Task<IEnumerable<User>> GetAllAsync();
}
Но требования растут:
public interface IUserRepository
{
Task<IEnumerable<User>> GetAllAsync();
Task<User?> GetByEmailAsync(string email);
Task<IEnumerable<User>> GetActiveUsersAsync();
Task<IEnumerable<User>> GetUsersByRoleAsync(string role);
Task<IEnumerable<User>> SearchAsync(string keyword, int page, int pageSize);
Task<IEnumerable<User>> GetUsersWithRecentActivityAsync(DateTime since);
// и дальше понеслась
}
Интерфейс раздувается, запросы лезут прямо в контракты. А ведь EF Core уже умеет делать это из коробки через LINQ: типобезопасно, оптимизируемо и без излишних прокладок. Репозитории были полезны, когда ORM были кривые. Сейчас это часто просто лишнее.
Я тоже таким грешил. Но взросление разработчика — это понимать, когда паттерн становится анти-паттерном.
Сервисные обёртки:
Нормальный пример: обёртка над внешним API
public interface IGitHubClient
{
Task<UserDto?> GetUserAsync(string username);
Task<IReadOnlyList<RepoDto>> GetRepositoriesAsync(string username);
}
public class GitHubClient(HttpClient httpClient) : IGitHubClient
{
public Task<UserDto?> GetUserAsync(string username) =>
httpClient.GetFromJsonAsync<UserDto>($"/users/{username}");
public Task<IReadOnlyList<RepoDto>> GetRepositoriesAsync(string username) =>
httpClient.GetFromJsonAsync<IReadOnlyList<RepoDto>>($"/users/{username}/repos");
}
Все детали HTTP и сериализации спрятаны внутри. Это даёт ценность.
Плохой пример: сервис-прокладка
public class UserService(IUserRepository repo)
{
public Task<User?> GetByIdAsync(Guid id) => repo.GetByIdAsync(id);
public Task<IEnumerable<User>> GetAllAsync() => repo.GetAllAsync();
public Task SaveAsync(User user) => repo.SaveAsync(user);
}
Просто пробрасывание вызовов. Никаких правил, никакой логики. Таких слоёв становится много, а толку то ноль.
Как принимать нормальные решения
- Абстрагируй политику, а не механику
Политика меняется: платёжки, стратегии ретраев, кеши
Механика стабильна: LINQ, HttpClient, JSON
- Жди второй реализации
Одна реализация? Не спеши с интерфейсами.
// сначала просто класс
public class EmailNotifier
{
public Task SendAsync(...) { ... }
}
// потом появляется SMS, и вот тогда...
public interface INotifier
{
Task SendAsync(...);
}
Абстракция должна появляться из реальных требований, а не из фантазий.
- Разграничивай
Внутри приложения — конкретика
На границах системы — абстракции
Как выпиливать неудачные абстракции
Спроси себя: код станет проще без неё? Если да = смело удаляй.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍2
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
😁17🥴4👏3