Один разработчик поделился экспериментом: он пересмотрел видео tsoding про работу с PPM-форматом и повторил всё на C#. При этом отметил, что реализация на C выглядит куда компактнее, но сама идея остаётся простой и понятной.
PPM выбран ради простоты. Формат состоит из текстового заголовка с описанием параметров изображения, а дальше идут байты по три штуки на пиксель, которые задают RGB.
Для динамики он сгенерировал 60 изображений, а затем собрал их в видео через ffmpeg. На каждом шаге узор немного смещался, и в результате получилась анимация движущейся шахматной сетки.
Классная мини-задача. Очень советую.
👉 @KodBlog
PPM выбран ради простоты. Формат состоит из текстового заголовка с описанием параметров изображения, а дальше идут байты по три штуки на пиксель, которые задают RGB.
Для динамики он сгенерировал 60 изображений, а затем собрал их в видео через ffmpeg. На каждом шаге узор немного смещался, и в результате получилась анимация движущейся шахматной сетки.
Классная мини-задача. Очень советую.
Производительность не была для меня целью, поэтому я не обращал на это внимания.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍4😐3😁2
Как сделать API-эндпоинты быстрее в 426 раз
(подсказка: дело не в кешe)
Когда разработчики видят медленный API, первая реакция: лечить всё кешем.
Но это ошибка в базовом понимании:
кеш не фиксит тормоза, если корень проблемы в кривых запросах к базе.
Поэтому первый шаг к масштабируемой системе — пофиксить её, а не обклеить кешами.
Небольшой эксперимент:
есть веб-API с одним эндпоинтом
До оптимизации:
Обработано запросов: 378
Средний RPS: 11.01
Средняя длительность запроса: ~4 секунды
После оптимизации:
Обработано запросов: 140 331
Средний RPS: 4 689.36
Средняя длительность запроса: 10.69 мс
Сначала фикси доступ к данным.
Потом уже используй кеш, чтобы масштабироваться, а не выживать.
👉 @KodBlog
(подсказка: дело не в кешe)
Когда разработчики видят медленный API, первая реакция: лечить всё кешем.
Но это ошибка в базовом понимании:
кеш не фиксит тормоза, если корень проблемы в кривых запросах к базе.
Поэтому первый шаг к масштабируемой системе — пофиксить её, а не обклеить кешами.
Небольшой эксперимент:
есть веб-API с одним эндпоинтом
/productsДо оптимизации:
Обработано запросов: 378
Средний RPS: 11.01
Средняя длительность запроса: ~4 секунды
После оптимизации:
Обработано запросов: 140 331
Средний RPS: 4 689.36
Средняя длительность запроса: 10.69 мс
Сначала фикси доступ к данным.
Потом уже используй кеш, чтобы масштабироваться, а не выживать.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10🤣6🔥1🥴1
Представили: GitList для linqpad
В LINQPad мне давно хотелось управлять Git-изменениями прямо внутри приложения.
Теперь это реально: Git-репы открываются как кастомное подключение, и ими можно управлять напрямую в LINQPad. Под капотом используется LibGit2Sharp, чтобы читать состояние репозитория и показывать данные прямо в интерфейсе.
Статус: альфа
Что нужно: тестирование
NuGet: LearningLINQPad.GitList
GitHub: https://github.com/ryanrodemoyer/LearningLINQPad-GitList
Примечание: будьте максимально осторожны с важными данными. Инструменту нужно ещё много тестов!
👉 @KodBlog
В LINQPad мне давно хотелось управлять Git-изменениями прямо внутри приложения.
Теперь это реально: Git-репы открываются как кастомное подключение, и ими можно управлять напрямую в LINQPad. Под капотом используется LibGit2Sharp, чтобы читать состояние репозитория и показывать данные прямо в интерфейсе.
Статус: альфа
Что нужно: тестирование
NuGet: LearningLINQPad.GitList
GitHub: https://github.com/ryanrodemoyer/LearningLINQPad-GitList
Примечание: будьте максимально осторожны с важными данными. Инструменту нужно ещё много тестов!
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3👍2
Использование выражений коллекций для своих типов
В C# 12 появились выражения коллекций — упрощённый синтаксис для инициализации коллекций:
Такой синтаксис отлично работает со стандартными коллекциями, но что делать с собственными типами? Здесь пригодится атрибут CollectionBuilderAttribute, который позволяет подключить этот новый синтаксис к пользовательским коллекциям.
Пользовательские коллекции
Допустим, у вас есть свой тип коллекции:
Раньше вы не могли использовать выражения коллекций с таким типом. Приходилось писать по-старинке:
Или так (что сути почти не меняет):
Не особо изящно.
Атрибут CollectionBuilderAttribute решает проблему, подсказывая компилятору, как собирать вашу коллекцию из выражения коллекции:
Компилятор сам вызовет метод Create и передаст ему элементы.
Как это работает
Атрибут принимает два параметра:
Тип построителя — класс, в котором находится фабричный метод (typeof(MyCollectionBuilder));
Имя метода — имя статического метода, создающего объект коллекции ("Create").
Метод построителя должен:
- быть статическим;
- принимать ReadOnlySpan<T> (предпочтительно) или T[];
- возвращать экземпляр вашей коллекции;
- иметь параметры типов, совпадающие с параметрами коллекции.
Замечание: коллекция должна иметь «тип итерации», то есть метод GetEnumerator(), который возвращает IEnumerator или IEnumerator<T>. Можно реализовать IEnumerable / IEnumerable<T>, либо просто добавить метод GetEnumerator() вручную.
Когда все условия выполнены, компилятор сможет генерировать нужный код и создавать вашу коллекцию максимально эффективно.
👉 @KodBlog
В C# 12 появились выражения коллекций — упрощённый синтаксис для инициализации коллекций:
int[] numbers = [1, 2, 3, 4, 5];
List<string> names = ["Alice", "Bob", "Charlie"];
Такой синтаксис отлично работает со стандартными коллекциями, но что делать с собственными типами? Здесь пригодится атрибут CollectionBuilderAttribute, который позволяет подключить этот новый синтаксис к пользовательским коллекциям.
Пользовательские коллекции
Допустим, у вас есть свой тип коллекции:
public class MyCollection<T>
{
private readonly List<T> _items;
public MyCollection(ReadOnlySpan<T> items) =>
_items = [.. items];
public IEnumerator<T> GetEnumerator()
=> _items.GetEnumerator();
// другие члены…
}
Раньше вы не могли использовать выражения коллекций с таким типом. Приходилось писать по-старинке:
var myCol =
new MyCollection<int>(new[] { 1, 2, 3, 4, 5 });
Или так (что сути почти не меняет):
var myCol =
new MyCollection<int>([1, 2, 3, 4, 5]);
Не особо изящно.
Атрибут CollectionBuilderAttribute решает проблему, подсказывая компилятору, как собирать вашу коллекцию из выражения коллекции:
[CollectionBuilder(typeof(MyCollectionBuilder),
nameof(MyCollectionBuilder.Create))]
public class MyCollection<T>
{
//…
}
public static class MyCollectionBuilder
{
public static MyCollection<T>
Create<T>(ReadOnlySpan<T> items)
=> new([..items]);
}
Теперь можно писать так:
MyCollection<int> myCol = [1, 2, 3, 4, 5];
Компилятор сам вызовет метод Create и передаст ему элементы.
Как это работает
Атрибут принимает два параметра:
Тип построителя — класс, в котором находится фабричный метод (typeof(MyCollectionBuilder));
Имя метода — имя статического метода, создающего объект коллекции ("Create").
Метод построителя должен:
- быть статическим;
- принимать ReadOnlySpan<T> (предпочтительно) или T[];
- возвращать экземпляр вашей коллекции;
- иметь параметры типов, совпадающие с параметрами коллекции.
Замечание: коллекция должна иметь «тип итерации», то есть метод GetEnumerator(), который возвращает IEnumerator или IEnumerator<T>. Можно реализовать IEnumerable / IEnumerable<T>, либо просто добавить метод GetEnumerator() вручную.
Когда все условия выполнены, компилятор сможет генерировать нужный код и создавать вашу коллекцию максимально эффективно.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10👍6👎1
Вот моё любимое правило в .editorconfig:
-> Правила именования для async-методов и полей
Задайте понятные правила именования для приватных полей, async-методов, интерфейсов и так далее.
Зачем? это убирает путаницу при работе с async-API и делает намерения очевидными.
Если кто не в курсе:
.editorconfig — это обычный текстовый конфиг, который сообщает вашему редактору или IDE, как форматировать и оформлять код, чтобы весь проект выглядел одинаково, независимо от личных настроек каждого разработчика.
Он работает во многих языках и IDE (Visual Studio, VS Code, JetBrains Rider и прочих), а в .NET особенно полезен, потому что встроен в Roslyn-анализаторы и настройки стиля кода.
👉 @KodBlog
-> Правила именования для async-методов и полей
Задайте понятные правила именования для приватных полей, async-методов, интерфейсов и так далее.
Зачем? это убирает путаницу при работе с async-API и делает намерения очевидными.
Если кто не в курсе:
.editorconfig — это обычный текстовый конфиг, который сообщает вашему редактору или IDE, как форматировать и оформлять код, чтобы весь проект выглядел одинаково, независимо от личных настроек каждого разработчика.
Он работает во многих языках и IDE (Visual Studio, VS Code, JetBrains Rider и прочих), а в .NET особенно полезен, потому что встроен в Roslyn-анализаторы и настройки стиля кода.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍6🔥2
This media is not supported in your browser
VIEW IN TELEGRAM
Это расширение для Chrome очень полезно для тех, кто сидит на GitHub.
Называется SimRepo. Оно показывает похожие репозитории для любого репо, который ты открываешь.
Просто устанавливаешь расширение, и оно автоматически срабатывает каждый раз, когда ты заходишь в любой репозиторий.
👉 @KodBlog
Называется SimRepo. Оно показывает похожие репозитории для любого репо, который ты открываешь.
Просто устанавливаешь расширение, и оно автоматически срабатывает каждый раз, когда ты заходишь в любой репозиторий.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤3😁1
Сжатие запросов HttpClient с помощью GZIP
Если отправляешь большие файлы во внешний API, логично сжимать их перед отправкой. Но наивная реализация легко убивает память.
Проблемный вариант
Сжатие происходит полностью в MemoryStream, а потом уже уходит в запрос:
Использование:
Работает нормально, пока не начинаешь слать много запросов.
Вся gzip-копия каждый раз полностью живет в памяти.
Правильное решение: сжатие на лету
Переделываем CreateRequest:
Использование остается тем же, а память больше не пухнет.
Производительность (.NET 10)
По времени почти без разницы,
а вот по памяти разница огромная:
Чем больше файл, тем сильнее эффект.
PS: .NET API может автоматически распаковывать запросы. Достаточно добавить соответствующее промежуточное ПО:
👉 @KodBlog
Если отправляешь большие файлы во внешний API, логично сжимать их перед отправкой. Но наивная реализация легко убивает память.
Проблемный вариант
Сжатие происходит полностью в MemoryStream, а потом уже уходит в запрос:
internal async ValueTask<HttpRequestMessage>
CreateRequest(Uri uri, Stream source)
{
var outStream = new MemoryStream();
await using (var zip =
new GZipStream(outStream,
CompressionMode.Compress,
leaveOpen: true))
{
await source.CopyToAsync(zip);
zip.Close();
}
outStream.Position = 0;
var request = new HttpRequestMessage(
HttpMethod.Post, uri)
{
Content = new StreamContent(outStream)
};
request.Content.Headers.ContentEncoding.Add("gzip");
return request;
}
Использование:
var uri = new Uri("https://my.api/endpoint");
var file = File.OpenRead("largefile.txt");
var request = await CreateRequest(uri, file);
await httpClient.SendAsync(request);Работает нормально, пока не начинаешь слать много запросов.
Вся gzip-копия каждый раз полностью живет в памяти.
Правильное решение: сжатие на лету
public class GzipContent : HttpContent
{
private readonly Stream _source;
public GzipContent(Stream source)
{
_source = source;
Headers.ContentEncoding.Add("gzip");
}
protected override async Task
SerializeToStreamAsync(
Stream stream,
TransportContext? context)
{
await using var zip =
new GZipStream(stream,
CompressionMode.Compress,
leaveOpen: true);
await _source.CopyToAsync(zip);
}
protected override bool
TryComputeLength(out long length)
{
length = -1;
return false;
}
}
Переделываем CreateRequest:
internal ValueTask<HttpRequestMessage>
CreateRequest(Uri uri, Stream source)
{
return ValueTask.FromResult(
new HttpRequestMessage(HttpMethod.Post, uri)
{
Content = new GzipContent(source)
});
}
Использование остается тем же, а память больше не пухнет.
Производительность (.NET 10)
По времени почти без разницы,
а вот по памяти разница огромная:
| File | Method| Mean | Ratio| Allocated| Alc Rat |
|-----:|-------|-------:|-----:|---------:|--------:|
| 7KB| Memory| 633.3us| 1.00| 6.35KB| 1.00|
| 7KB| Gzip | 603.5us| 0.96| 4.77KB| 0.75|
| 200KB| Memory| 8.025ms| 1.00| 508.52KB| 1.000|
| 200KB| Gzip | 7.260ms| 0.90| 4.74KB| 0.009|
| 3MB| Memory| 94.97ms| 1.00| 8189.99KB| 1.000|
| 3MB| Gzip | 86.02ms| 0.91| 4.82KB| 0.001|
Чем больше файл, тем сильнее эффект.
PS: .NET API может автоматически распаковывать запросы. Достаточно добавить соответствующее промежуточное ПО:
builder.Services.AddRequestDecompression();
// …
app.UseRequestDecompression();
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9👌5👍3
Синтаксическое сжатие строк даёт больше вертикального пространства в редакторе. Строки без букв и цифр сжимаются на 25%, благодаря чему на экране помещается больше кода без потери читаемости.
Скоро в Visual Studio.
👉 @KodBlog
Скоро в Visual Studio.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍24🥰7❤2🤔1
Используем COPY для Экспорта/Импорта Данных в PostgreSQL
В PostgreSQL есть функция COPY, которая позволяет эффективно выполнять массовый импорт и экспорт данных в таблицу и из неё. Обычно это гораздо быстрее, чем работать через INSERT или SELECT.
В .NET провайдер Npgsql поддерживает три режима работы с COPY: бинарный, текстовый и бинарный необработанный.
1. Бинарный
В этом режиме вы используете API для чтения и записи строк и полей, которые Npgsql кодирует и декодирует. После завершения нужно вызвать метод Complete() для сохранения данных; иначе операция будет отменена при освобождении объекта записи (важно учитывать при возникновении исключений).
2. Текстовый
Данные передаются в текстовом или CSV-формате PostgreSQL. Пользователь самостоятельно форматирует строки или CSV, Npgsql лишь предоставляет методы для чтения и записи текста. Этот режим менее эффективен, чем бинарный, но удобен, если данные уже есть в CSV и высокая производительность не критична.
3. Бинарный необработанный
Данные передаются в двоичном формате без кодирования/декодирования Npgsql — это просто поток байтов .NET. Подходит для обработки больших объёмов или восстановления таблицы. Если нужно работать с отдельными значениями, используйте обычный бинарный режим.
Импорт можно отменить в любой момент, если освободить (Dispose) объект NpgsqlBinaryImporter до вызова Complete(). Экспорт отменяется вызовом метода Cancel().
👉 @KodBlog
В PostgreSQL есть функция COPY, которая позволяет эффективно выполнять массовый импорт и экспорт данных в таблицу и из неё. Обычно это гораздо быстрее, чем работать через INSERT или SELECT.
В .NET провайдер Npgsql поддерживает три режима работы с COPY: бинарный, текстовый и бинарный необработанный.
1. Бинарный
В этом режиме вы используете API для чтения и записи строк и полей, которые Npgsql кодирует и декодирует. После завершения нужно вызвать метод Complete() для сохранения данных; иначе операция будет отменена при освобождении объекта записи (важно учитывать при возникновении исключений).
// Импорт в таблицу с 2 полями (string, int)
using (var writer = conn.BeginBinaryImport(
"COPY my_table (field1, field2) FROM STDIN (FORMAT BINARY)"))
{
writer.WriteRow("Row1", 123);
writer.WriteRow("Row2", 123);
writer.Complete();
}
// Экспорт из таблицы с 2 полями
using (var rdr = conn.BeginBinaryExport(
"COPY my_table (field1, field2) TO STDOUT (FORMAT BINARY)"))
{
rdr.StartRow();
Console.WriteLine(rdr.Read<string>());
Console.WriteLine(rdr.Read<int>(NpgsqlDbType.Smallint));
rdr.StartRow();
rdr.Skip(); // пропустить поле
Console.WriteLine(rdr.IsNull); // проверить на NULL
Console.WriteLine(rdr.Read<int>());
rdr.StartRow();
// StartRow() вернёт -1, если данных больше нет
}
2. Текстовый
Данные передаются в текстовом или CSV-формате PostgreSQL. Пользователь самостоятельно форматирует строки или CSV, Npgsql лишь предоставляет методы для чтения и записи текста. Этот режим менее эффективен, чем бинарный, но удобен, если данные уже есть в CSV и высокая производительность не критична.
using (var writer = conn.BeginTextImport(
"COPY my_table (field1, field2) FROM STDIN"))
{
writer.Write("HELLO\t1\n");
writer.Write("GOODBYE\t2\n");
}
using (var reader = conn.BeginTextExport(
"COPY my_table (field1, field2) TO STDOUT"))
{
Console.WriteLine(reader.ReadLine());
Console.WriteLine(reader.ReadLine());
}
3. Бинарный необработанный
Данные передаются в двоичном формате без кодирования/декодирования Npgsql — это просто поток байтов .NET. Подходит для обработки больших объёмов или восстановления таблицы. Если нужно работать с отдельными значениями, используйте обычный бинарный режим.
int len;
var data = new byte[10000];
// Экспорт table1 в массив
using (var inStream = conn.BeginRawBinaryCopy(
"COPY table1 TO STDOUT (FORMAT BINARY)"))
{
len = inStream.Read(data, 0, data.Length); // реально читать блоками
}
// Импорт в table2
using (var outStream = conn.BeginRawBinaryCopy(
"COPY table2 FROM STDIN (FORMAT BINARY)"))
{
outStream.Write(data, 0, len);
}
Импорт можно отменить в любой момент, если освободить (Dispose) объект NpgsqlBinaryImporter до вызова Complete(). Экспорт отменяется вызовом метода Cancel().
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10👍3🔥2
Вышла подробная статья о том, как быстро прикрутить LLM к существующему ASP.NET API через Semantic Kernel.
Показан полный путь от создания Azure OpenAI ресурса и деплоя модели до сборки чат-ассистента с автогенерацией вызовов функций через Swagger, хранением контекста в EF Core и оркестрацией через KernelFactory.
Автор отдельно разбирает, как корректная OpenAPI-документация напрямую влияет на точность вызовов функций агентом, и где остаются реальные риски по безопасности и интеграции.
👉 @KodBlog
Показан полный путь от создания Azure OpenAI ресурса и деплоя модели до сборки чат-ассистента с автогенерацией вызовов функций через Swagger, хранением контекста в EF Core и оркестрацией через KernelFactory.
Автор отдельно разбирает, как корректная OpenAPI-документация напрямую влияет на точность вызовов функций агентом, и где остаются реальные риски по безопасности и интеграции.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍4❤🔥3
This media is not supported in your browser
VIEW IN TELEGRAM
Этот трюк с GitHub PR надо знать
Просто добавь
Он ищет не только баги. Он подсвечивает всё, что заслуживает второго взгляда: захардкоженные секреты, странные крипторежимы, подозрительную логику или грязный код.
Очень полезный способ быстрее проводить code review и находить то, что обычно легко пропустить.
👉 @KodBlog
Просто добавь
“0” перед словом “github” в URL любого Pull Request, и у тебя откроется полноценный PR-вьювер, который подсвечивает каждую строку diff’а цветом в зависимости от того, сколько внимания от человека она, скорее всего, требует.Он ищет не только баги. Он подсвечивает всё, что заслуживает второго взгляда: захардкоженные секреты, странные крипторежимы, подозрительную логику или грязный код.
Очень полезный способ быстрее проводить code review и находить то, что обычно легко пропустить.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤14👍5🔥1
Для тех, кто только начинает разбираться в программировании и собирает себе рабочую среду, на GitHub есть полезная подборка Awesome Uses
Это большая коллекция реальных сетапов разработчиков со всего мира с поддержкой фронтенда, бэкенда, веба и мобильной разработки, удобным просмотром через веб-страницу и фильтрацией по языкам и странам
Где подробно показывают, какое железо, софт, редакторы, плагины и терминалы используют в работе, при этом проект живой, регулярно обновляется и позволяет отправить собственную конфигурацию.
👉 @KodBlog
Это большая коллекция реальных сетапов разработчиков со всего мира с поддержкой фронтенда, бэкенда, веба и мобильной разработки, удобным просмотром через веб-страницу и фильтрацией по языкам и странам
Где подробно показывают, какое железо, софт, редакторы, плагины и терминалы используют в работе, при этом проект живой, регулярно обновляется и позволяет отправить собственную конфигурацию.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9❤2👍2
Не смешивайте CQRS и MediatR.
Экосистема .NET со временем практически приравняла CQRS к использованию MediatR. Разберём несколько распространённых заблуждений и выделим сильные стороны каждого подхода.
CQRS это архитектурный паттерн, который разделяет операции чтения и записи в приложении. Он предполагает, что модели для чтения данных должны отличаться от моделей для записи. Без привязки к конкретной реализации и без обязательных библиотек - это именно принцип проектирования.
Он появился из понимания того, что во многих приложениях, особенно со сложным доменом, требования к чтению и записи принципиально различаются. Чтение часто требует объединения данных из нескольких источников или представления их в удобном для UI виде. Запись должна обеспечивать соблюдение бизнес-правил, согласованность данных и управление состоянием домена.
Такое разделение даёт несколько преимуществ:
Оптимизированные модели чтения и записи под свои задачи.
Упрощённое сопровождение, так как чтение и запись развиваются независимо.
Более гибкое масштабирование операций чтения и записи.
Чёткая граница между доменной логикой и потребностями представления.
MediatR это реализация шаблона посредника (Mediator). Его основная цель - снизить прямые зависимости между компонентами, предоставив единую точку взаимодействия. Компоненты не знают друг о друге напрямую - они общаются через посредника.
MediatR предоставляет несколько возможностей:
Внутрипроцессный обмен сообщениями между компонентами.
Поведения конвейера (pipeline behaviors) для сквозных задач.
Обработка уведомлений по модели publish/subscribe.
Косвенность, которую вводит MediatR, — его самый часто критикуемый аспект. Она может усложнять трассировку кода, особенно для новичков. Эту проблему частично решают, размещая запросы в тех же файлах, что и обработчики.
Почему их часто используют вместе?
Связка CQRS и MediatR используется неслучайно. Модель запрос/ответ в MediatR хорошо ложится на разделение команд и запросов в CQRS. Команды и запросы можно оформлять как запросы MediatR, а их обработчики будут содержать бизнес-логику.
Пример команды в MediatR:
Использование CQRS вместе с MediatR даёт ряд преимуществ:
Единый подход к обработке команд и запросов.
Использование pipeline-поведений для логирования, валидации и обработки ошибок.
Чёткое разделение ответственности через классы обработчиков.
Упрощённое тестирование за счёт изоляции обработчиков.
Но за это удобство приходится платить дополнительной абстракцией и сложностью. Нужно описывать классы запросов и ответов, писать обработчики, код для отправки запросов и так далее. Для простых приложений это может быть избыточно.
Вопрос не в том, хороший это компромисс или плохой вообще, а в том, подходит ли он для конкретного проекта и контекста.
👉 @KodBlog
Экосистема .NET со временем практически приравняла CQRS к использованию MediatR. Разберём несколько распространённых заблуждений и выделим сильные стороны каждого подхода.
CQRS это архитектурный паттерн, который разделяет операции чтения и записи в приложении. Он предполагает, что модели для чтения данных должны отличаться от моделей для записи. Без привязки к конкретной реализации и без обязательных библиотек - это именно принцип проектирования.
Он появился из понимания того, что во многих приложениях, особенно со сложным доменом, требования к чтению и записи принципиально различаются. Чтение часто требует объединения данных из нескольких источников или представления их в удобном для UI виде. Запись должна обеспечивать соблюдение бизнес-правил, согласованность данных и управление состоянием домена.
Такое разделение даёт несколько преимуществ:
Оптимизированные модели чтения и записи под свои задачи.
Упрощённое сопровождение, так как чтение и запись развиваются независимо.
Более гибкое масштабирование операций чтения и записи.
Чёткая граница между доменной логикой и потребностями представления.
MediatR это реализация шаблона посредника (Mediator). Его основная цель - снизить прямые зависимости между компонентами, предоставив единую точку взаимодействия. Компоненты не знают друг о друге напрямую - они общаются через посредника.
MediatR предоставляет несколько возможностей:
Внутрипроцессный обмен сообщениями между компонентами.
Поведения конвейера (pipeline behaviors) для сквозных задач.
Обработка уведомлений по модели publish/subscribe.
Косвенность, которую вводит MediatR, — его самый часто критикуемый аспект. Она может усложнять трассировку кода, особенно для новичков. Эту проблему частично решают, размещая запросы в тех же файлах, что и обработчики.
Почему их часто используют вместе?
Связка CQRS и MediatR используется неслучайно. Модель запрос/ответ в MediatR хорошо ложится на разделение команд и запросов в CQRS. Команды и запросы можно оформлять как запросы MediatR, а их обработчики будут содержать бизнес-логику.
Пример команды в MediatR:
public record CreateHabit(string Name, string? Description, int Priority) : IRequest<HabitDto>;
public sealed class CreateHabitHandler(
ApplicationDbContext dbContext,
IValidator<CreateHabit> validator)
: IRequestHandler<CreateHabit, HabitDto>
{
public async Task<HabitDto> Handle(
CreateHabit request, CancellationToken ct)
{
await validator
.ValidateAndThrowAsync(request);
var habit = request.ToEntity();
dbContext.Habits.Add(habit);
await dbContext.SaveChangesAsync(ct);
return habit.ToDto();
}
}
Использование CQRS вместе с MediatR даёт ряд преимуществ:
Единый подход к обработке команд и запросов.
Использование pipeline-поведений для логирования, валидации и обработки ошибок.
Чёткое разделение ответственности через классы обработчиков.
Упрощённое тестирование за счёт изоляции обработчиков.
Но за это удобство приходится платить дополнительной абстракцией и сложностью. Нужно описывать классы запросов и ответов, писать обработчики, код для отправки запросов и так далее. Для простых приложений это может быть избыточно.
Вопрос не в том, хороший это компромисс или плохой вообще, а в том, подходит ли он для конкретного проекта и контекста.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤3
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍4🔥2
This media is not supported in your browser
VIEW IN TELEGRAM
Нашлась утилита, которая превращает историю Git-коммитов в полноценный мини-фильм прямо в терминале. Коммиты проигрываются как анимация набора текста, с подсветкой синтаксиса и динамически обновляемым деревом файлов.
По сути, можно наблюдать, как репозиторий «пишет себя сам», что делает инструмент не только полезным, но и эффектным.
Проект написан на Rust😎
Тестим
👉 @KodBlog
По сути, можно наблюдать, как репозиторий «пишет себя сам», что делает инструмент не только полезным, но и эффектным.
Проект написан на Rust
Тестим
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10🤣5🤔2
Вышел декабрьский сервис-релиз .NET 10.0.1. Microsoft опубликовала обновление 9 декабря — в нём набор исправлений по нескольким подсистемам, без каких-либо патчей безопасности. Обновили Runtime, ASP.NET Core, SDK, WinForms, EF Core, а также контейнерные образы и NuGet-пакеты.
.NET Framework в этот раз остался без апдейтов.
Microsoft рекомендует перейти на новую версию, чтобы получить актуальные фиксы и более стабильную работу стека.
Подробности — в официальном блоге Microsoft
👉 @KodBlog
.NET Framework в этот раз остался без апдейтов.
Microsoft рекомендует перейти на новую версию, чтобы получить актуальные фиксы и более стабильную работу стека.
Подробности — в официальном блоге Microsoft
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4