На Хабре вышла статья о том, как с помощью EF Core и PostgreSQL избежать deadlock и oversell при работе со стоками. Автор разбирает подход с сортировкой обновлений по ключам, использованием ExecuteUpdateAsync, повторными попытками (retry), триггерами, очередями и батчами.
В статье есть бенчмарки, примеры кода и ссылка на репозиторий на GitHub.
Читать подробнее: habr.com/ru/articles/955714/
👉 @KodBlog
В статье есть бенчмарки, примеры кода и ссылка на репозиторий на GitHub.
Читать подробнее: habr.com/ru/articles/955714/
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤2
В превью C# 14 появились partial-конструкторы и partial-события.
Каждый из них должен состоять ровно из одного объявления-определения и одного объявления-реализации.
Реализация partial-события обязана содержать add и remove аксессоры.
👉 @KodBlog
Каждый из них должен состоять ровно из одного объявления-определения и одного объявления-реализации.
Реализация partial-события обязана содержать add и remove аксессоры.
Please open Telegram to view this post
VIEW IN TELEGRAM
👎8👍4🤔2
На GitHub появился классный репозиторий с чётко структурированным материалом по system design. В нём собраны:
- основы проектирования крупных систем,
- типичные вопросы и решения для собеседований,
- карточки Anki для запоминания ключевых концепций.
Сохрани, чтобы не потерять: https://github.com/donnemartin/system-design-primer
👉 @KodBlog
- основы проектирования крупных систем,
- типичные вопросы и решения для собеседований,
- карточки Anki для запоминания ключевых концепций.
Сохрани, чтобы не потерять: https://github.com/donnemartin/system-design-primer
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤3
Ты правильно называешь свои DTO?
Какую схему именования выбрать
Когда ты делаешь Web API, твои эндпоинты принимают и отдают данные.
Частая практика — добавлять к таким моделям суффикс Dto
Но вот в чём проблема:
DTO часто смешивают входные и выходные данные в одном классе.
Со временем такие классы разрастаются и становятся непонятными.
Мой вариант: использовать суффиксы Request и Response вместо Dto:
• CreateUserRequest —> для входных данных
• UserResponse —> для выходных
Почему так лучше:
Чёткое назначение —> сразу видно, модель для входа или для выхода.
Масштабируемость —> изменения в Response не ломают Request.
Поддерживаемость —> не надо гадать, что вообще делает UserDto.
Совет: выбери один подход и используй его последовательно во всём проекте.
А ты как называешь свои модели Request/Response или Dto? Делись опытом
👉 @KodBlog
Какую схему именования выбрать
Когда ты делаешь Web API, твои эндпоинты принимают и отдают данные.
Частая практика — добавлять к таким моделям суффикс Dto
Но вот в чём проблема:
DTO часто смешивают входные и выходные данные в одном классе.
Со временем такие классы разрастаются и становятся непонятными.
Мой вариант: использовать суффиксы Request и Response вместо Dto:
• CreateUserRequest —> для входных данных
• UserResponse —> для выходных
Почему так лучше:
Чёткое назначение —> сразу видно, модель для входа или для выхода.
Масштабируемость —> изменения в Response не ломают Request.
Поддерживаемость —> не надо гадать, что вообще делает UserDto.
Совет: выбери один подход и используй его последовательно во всём проекте.
А ты как называешь свои модели Request/Response или Dto? Делись опытом
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10👍8🤣3🔥2
Деревья выражений в C# помогают создавать быстрые и эффективные методы преобразования данных, ускоряя работу с объектами и запросами в Entity Framework Core. В статье раскрываются механизмы мэппинга и построения сложных фильтров через IQueryable.
Читать подробнее: https://habr.com/ru/articles/932110/
👉 @KodBlog
Читать подробнее: https://habr.com/ru/articles/932110/
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4❤3
Экономим память как профи: 5 продвинутых техник
Если вы работаете с .NET, то знаете, под капотом куча мощных инструментов, про которые редко говорят. Вот 5 API, которые реально экономят память и ускоряют код.
1. CollectionsMarshal.AsSpan() — прямой доступ к списку
Обычно List<T> это удобная, но безопасная обёртка над массивом.
Если нужен Span без копирования:
Работает напрямую с внутренним массивом списка, без ToArray().
Важно: при изменении размера списка Span станет недействительным.
Когда использовать: большие буферы, короткие циклы.
Выигрыш: до 2 раз меньше памяти.
2. CollectionsMarshal.GetValueRefOrNullRef() — доступ к словарю без двойного поиска
Обычный способ вызывает поиск ключа дважды:
Лучше так:
Получаем ссылку прямо на значение без повторного поиска.
Когда использовать: большие словари, горячие циклы.
Выигрыш: примерно в 2 раза быстрее.
3. GC.AllocateUninitializedArray<T>() — массив без обнуления
.NET по умолчанию обнуляет массив:
Если вы всё равно перезаписываете элементы - то это лишняя работа.
Быстрее:
Когда использовать: если массив заполняется сразу.
Выигрыш: ~15% быстрее на больших массивах.
4. ArrayPool<T>.Shared и IMemoryOwner — аренда массивов
Постоянные аллокации убивают GC:
Используем пул:
Когда использовать: сетевые серверы, конвейеры, потоковые операции.
Выигрыш: до 1000 раз меньше аллокаций.
5. ObjectPool<T> — переиспользование дорогих объектов
Создание тяжёлых объектов вроде StringBuilder дорого:
Пулим:
Когда использовать: логирование, сериализация, временные буферы.
Выигрыш: до 4 раз быстрее, в 50 раз меньше памяти.
Если вы когда-нибудь заглядывали в свой профилировщик и задавались вопросом, почему так много времени тратится на сборку мусора или копирование памяти, эти приёмы помогут вам это исправить. Они требуют аккуратности, но дают настоящую производительность.
👉 @KodBlog
Если вы работаете с .NET, то знаете, под капотом куча мощных инструментов, про которые редко говорят. Вот 5 API, которые реально экономят память и ускоряют код.
1. CollectionsMarshal.AsSpan() — прямой доступ к списку
Обычно List<T> это удобная, но безопасная обёртка над массивом.
Если нужен Span без копирования:
using System.Runtime.InteropServices;
var numbers = new List<int> { 1, 2, 3, 4, 5 };
Span<int> span = CollectionsMarshal.AsSpan(numbers);
Работает напрямую с внутренним массивом списка, без ToArray().
Важно: при изменении размера списка Span станет недействительным.
Когда использовать: большие буферы, короткие циклы.
Выигрыш: до 2 раз меньше памяти.
2. CollectionsMarshal.GetValueRefOrNullRef() — доступ к словарю без двойного поиска
Обычный способ вызывает поиск ключа дважды:
if (dict.TryGetValue("foo", out var value)) {
value++;
dict["foo"] = value;
}Лучше так:
using System.Runtime.InteropServices;
ref int valueRef = ref CollectionsMarshal.GetValueRefOrNullRef(dict, "foo");
if (!Unsafe.IsNullRef(ref valueRef))
valueRef++;
Получаем ссылку прямо на значение без повторного поиска.
Когда использовать: большие словари, горячие циклы.
Выигрыш: примерно в 2 раза быстрее.
3. GC.AllocateUninitializedArray<T>() — массив без обнуления
.NET по умолчанию обнуляет массив:
int[] arr = new int[1000];
Если вы всё равно перезаписываете элементы - то это лишняя работа.
Быстрее:
int[] arr = GC.AllocateUninitializedArray<int>(1000);
for (int i = 0; i < arr.Length; i++) arr[i] = i;
Когда использовать: если массив заполняется сразу.
Выигрыш: ~15% быстрее на больших массивах.
4. ArrayPool<T>.Shared и IMemoryOwner — аренда массивов
Постоянные аллокации убивают GC:
for (int i = 0; i < 1000; i++) {
var buffer = new byte[1024];
DoSomething(buffer);
}Используем пул:
using System.Buffers;
for (int i = 0; i < 1000; i++) {
using IMemoryOwner<byte> owner = MemoryPool<byte>.Shared.Rent(1024);
DoSomething(owner.Memory.Span);
}
Когда использовать: сетевые серверы, конвейеры, потоковые операции.
Выигрыш: до 1000 раз меньше аллокаций.
5. ObjectPool<T> — переиспользование дорогих объектов
Создание тяжёлых объектов вроде StringBuilder дорого:
for (int i = 0; i < 100; i++) {
var sb = new StringBuilder(1024);
sb.Append("Hello ").Append(i);
Console.WriteLine(sb.ToString());
}Пулим:
using Microsoft.Extensions.ObjectPool;
var provider = new DefaultObjectPoolProvider();
var pool = provider.CreateStringBuilderPool();
for (int i = 0; i < 100; i++) {
var sb = pool.Get();
sb.Clear();
sb.Append("Hello ").Append(i);
Console.WriteLine(sb.ToString());
pool.Return(sb);
}
Когда использовать: логирование, сериализация, временные буферы.
Выигрыш: до 4 раз быстрее, в 50 раз меньше памяти.
Если вы когда-нибудь заглядывали в свой профилировщик и задавались вопросом, почему так много времени тратится на сборку мусора или копирование памяти, эти приёмы помогут вам это исправить. Они требуют аккуратности, но дают настоящую производительность.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍9
Rider 2025.3 EAP 6 уже доступен!
В этом билде появилась поддержка ASP. NET и обнаружения проблем с базой данных прямо в окне Monitoring🔥
Теперь, когда ты запускаешь или отлаживаешь приложение, мониторинг автоматически определяет:
Медленные или чрезмерно частые запросы к БД
Слишком большие результаты выборок
Медленные действия MVC или обработчики Razor-страниц
…и многое другое
Подробнее — здесь
👉 @KodBlog
В этом билде появилась поддержка ASP. NET и обнаружения проблем с базой данных прямо в окне Monitoring
Теперь, когда ты запускаешь или отлаживаешь приложение, мониторинг автоматически определяет:
Медленные или чрезмерно частые запросы к БД
Слишком большие результаты выборок
Медленные действия MVC или обработчики Razor-страниц
…и многое другое
Подробнее — здесь
Please open Telegram to view this post
VIEW IN TELEGRAM
❤14
This media is not supported in your browser
VIEW IN TELEGRAM
Задумывался, почему приложение ощущается «тормозным», хотя с пропускной способностью вроде всё ок? Дело в том, что latency и throughput описывают два совершенно разных аспекта производительности.
Latency — это задержка на один пакет. То, что ощущает пользователь, когда нажимает кнопку. Это отзывчивость системы. Это время, за которое один запрос проходит путь от сервера до конечного устройства. В это входит время обработки на сервере, ожидание в очереди, распространение по сети, задержка при передаче и последний участок соединения до устройства пользователя.
Throughput — это объём данных в секунду. Не скорость отдельного пакета, а то, сколько пакетов проходит через «трубу» за определённый промежуток времени. Throughput — это ёмкость системы. Высокий throughput означает, что система справляется с нагрузкой, не захлёбываясь.
👉 @KodBlog
Latency — это задержка на один пакет. То, что ощущает пользователь, когда нажимает кнопку. Это отзывчивость системы. Это время, за которое один запрос проходит путь от сервера до конечного устройства. В это входит время обработки на сервере, ожидание в очереди, распространение по сети, задержка при передаче и последний участок соединения до устройства пользователя.
Throughput — это объём данных в секунду. Не скорость отдельного пакета, а то, сколько пакетов проходит через «трубу» за определённый промежуток времени. Throughput — это ёмкость системы. Высокий throughput означает, что система справляется с нагрузкой, не захлёбываясь.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6❤3👏2
C# и LINQ = функциональный коктейль для кодинга
Если объяснить максимально просто — функциональный стиль позволяет писать код так, как ты о нём говоришь.
«Отфильтровать пользователей старше 30»
«Сгруппировать пользователей по школе»
«Показать все имена пользователей»
В отличие от процедурного подхода, где нужно думать и о том, что делать, и как именно это сделать — управлять переменными, добавлять и удалять элементы из списков, сортировать результаты и так далее.
В LINQ больше двух десятков операторов, но чтобы начать, достаточно пары базовых:
Select — выбрать нужные данные
Where — отфильтровать записи
Any — проверить, существует ли хотя бы один элемент
GroupBy — сгруппировать совпадающие элементы
ToList — собрать результат в List<T>
ToDictionary — собрать результат в Dictionary<K,V>
👉 @KodBlog
Если объяснить максимально просто — функциональный стиль позволяет писать код так, как ты о нём говоришь.
«Отфильтровать пользователей старше 30»
«Сгруппировать пользователей по школе»
«Показать все имена пользователей»
В отличие от процедурного подхода, где нужно думать и о том, что делать, и как именно это сделать — управлять переменными, добавлять и удалять элементы из списков, сортировать результаты и так далее.
В LINQ больше двух десятков операторов, но чтобы начать, достаточно пары базовых:
Select — выбрать нужные данные
Where — отфильтровать записи
Any — проверить, существует ли хотя бы один элемент
GroupBy — сгруппировать совпадающие элементы
ToList — собрать результат в List<T>
ToDictionary — собрать результат в Dictionary<K,V>
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥22❤2
Одно небольшое изменение = запрос в 400 раз быстрее
Вот что было сделано, чтобы добиться такого прироста.
Реализовывалась пагинация с курсором через EF и Postgres.
Но нужно было сделать запрос быстрее.
Как ускорить SQL-запрос?
Добавить индекс, и всё должно полететь.
Так ведь?
Не совсем…
Был создан составной индекс по нужным колонкам, чтобы ускорить выборку.
НО ЗАПРОС СТАЛ МЕДЛЕННЕЕ!!!
Пришлось разбираться, почему индекс не используется.
Оказалось, что дело в сравнении кортежей — это и решило проблему.
Но непонятно было, как перенести это в запрос EF Core.
К счастью, провайдер Postgres поддерживает это через кастомную функцию.
👉 @KodBlog
Вот что было сделано, чтобы добиться такого прироста.
Реализовывалась пагинация с курсором через EF и Postgres.
Но нужно было сделать запрос быстрее.
Как ускорить SQL-запрос?
Добавить индекс, и всё должно полететь.
Так ведь?
Не совсем…
Был создан составной индекс по нужным колонкам, чтобы ускорить выборку.
НО ЗАПРОС СТАЛ МЕДЛЕННЕЕ!!!
Пришлось разбираться, почему индекс не используется.
Оказалось, что дело в сравнении кортежей — это и решило проблему.
Но непонятно было, как перенести это в запрос EF Core.
К счастью, провайдер Postgres поддерживает это через кастомную функцию.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7👍3
Настройка batch size в Entity Framework
По умолчанию для провайдера SQL Server batch size равен 42.
Это значит, что при вставке 100 строк произойдёт 5 отдельных запросов к базе.
Я недавно гонял простой бенчмарк в .NET, и вот что получилось:
даже если уменьшить количество раунд-трипов, batch size 1000 оказался самым медленным, а вот 200 показал себя лучше всего — некий "sweet spot".
Вывод простой: тестируйте на своих сценариях.
Оптимальный размер batch может отличаться в зависимости от типа нагрузки и объёмов данных.
👉 @KodBlog
По умолчанию для провайдера SQL Server batch size равен 42.
Это значит, что при вставке 100 строк произойдёт 5 отдельных запросов к базе.
Я недавно гонял простой бенчмарк в .NET, и вот что получилось:
даже если уменьшить количество раунд-трипов, batch size 1000 оказался самым медленным, а вот 200 показал себя лучше всего — некий "sweet spot".
Вывод простой: тестируйте на своих сценариях.
Оптимальный размер batch может отличаться в зависимости от типа нагрузки и объёмов данных.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1
В Entity Framework 10 есть анализатор, который выдает предупреждение, если выполняется конкатенация внутри вызова «сырого» SQL-метода, как показано ниже
👉 @KodBlog
var users = context.Users
.FromSqlRaw("SELECT * FROM Users WHERE [" + fieldName + "] IS NULL");
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤3🥴1
Планирование в Visual Studio теперь доступно в публичной превью-версии
Это обновление добавляет новый способ, с помощью которого Copilot может разруливать сложные, многошаговые задачи. Благодаря Planning Copilot теперь может изучать твой код, разбивать большие задачи на подзадачи и выполнять их пошагово, в итоге ты получаешь более предсказуемые результаты и лучше понимаешь, что именно он делает.
Попробуй в Visual Studio 2022 версии 17.14:
зайди в Tools → Options → Copilot → Enable Planning и включи функцию.
После этого расскажи, что думаешь.
👉 @KodBlog
Это обновление добавляет новый способ, с помощью которого Copilot может разруливать сложные, многошаговые задачи. Благодаря Planning Copilot теперь может изучать твой код, разбивать большие задачи на подзадачи и выполнять их пошагово, в итоге ты получаешь более предсказуемые результаты и лучше понимаешь, что именно он делает.
Мы уже видим обнадёживающие результаты: улучшилась успешность и стабильность работы разных моделей. Но это только начало
Попробуй в Visual Studio 2022 версии 17.14:
зайди в Tools → Options → Copilot → Enable Planning и включи функцию.
После этого расскажи, что думаешь.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1🥴1
6 Шагов для Правильной Настройки Нового .NET-проекта
1. Задай единый стиль кода
Первое, что я добавляю в проект — файл .editorconfig.
Он заставляет всех в команде придерживаться одинакового форматирования и соглашений об именах, чтобы не было разношерстных отступов и случайных неймингов.
Создать его можно прямо в Visual Studio.
Стандартная конфигурация - хороший старт, но можно подстроить под вкусы команды.
Положи файл в корень решения, чтобы все проекты следовали тем же правилам. При желании можно переопределить настройки в отдельных подпапках, добавив туда свой .editorconfig.
Вот два примера .editorconfig, которые можно использовать:
- из репозитория .NET runtime
- вариант для обычных .NET-проектов
2. Централизуй настройки сборки
Дальше я добавляю файл Directory.Build.props в корень решения. Он позволяет задавать настройки сборки, общие для всех проектов.
Пример:
Так .csproj файлы остаются чистыми и единообразными, поэтому не нужно повторять эти свойства в каждом проекте.
Если потом захочешь подключить статические анализаторы или поменять параметры сборки, то делается это в одном месте.
Фишка в том, что .csproj файлы становятся почти пустыми, кроме ссылок на NuGet-пакеты.
3. Централизуй управление пакетами
Когда решение разрастается, версии NuGet-пакетов в разных проектах начинают расходиться и чёрт, это превращается в кошмар😬
Тут помогает централизованное управление пакетами.
Создай в корне файл Directory.Packages.props:
Теперь, когда подключаешь пакет в проекте, указываешь только имя, без версии:
Все версии хранятся централизованно. Обновлять зависимости становится проще, а рассинхрон версий между проектами исчезает.
Если нужно, можно переопределить версию в конкретном проекте.
4. Подключи статический анализ кода
Статический анализ помогает ловить потенциальные баги и следить за качеством кода. В .NET уже есть встроенные анализаторы, но я обычно добавляю SonarAnalyzer.CSharp, ведь он делает проверку глубже.
Установи пакет:
И добавь его как глобальную зависимость в Directory.Build.props:
Дополнительно настрой сборку:
Теперь билд упадет, если будут серьезные проблемы с качеством кода = неплохая страховка.
Если какие-то правила не подходят, можно понизить их уровень до none в .editorconfig.
5. Настрой локальную оркестрацию
Чтобы у всех разработчиков среда была одинаковой, стоит настроить контейнерную оркестрацию.
Один из вариантов это .NET Aspire
.NET Aspire добавляет сервис-дискавери, телеметрию и упрощает конфигурацию. Все это встроено прямо в .NET-проекты.
Пример добавления проекта и Postgres-ресурса:
Aspire под капотом использует Docker, но разработчику с ним работать приятнее.
В любом случае цель одна.
6. Автоматизируй сборку через CI
В конце я настраиваю простой workflow в GitHub Actions, чтобы проверять каждый коммит.
👉 @KodBlog
1. Задай единый стиль кода
Первое, что я добавляю в проект — файл .editorconfig.
Он заставляет всех в команде придерживаться одинакового форматирования и соглашений об именах, чтобы не было разношерстных отступов и случайных неймингов.
Создать его можно прямо в Visual Studio.
Стандартная конфигурация - хороший старт, но можно подстроить под вкусы команды.
Положи файл в корень решения, чтобы все проекты следовали тем же правилам. При желании можно переопределить настройки в отдельных подпапках, добавив туда свой .editorconfig.
Вот два примера .editorconfig, которые можно использовать:
- из репозитория .NET runtime
- вариант для обычных .NET-проектов
2. Централизуй настройки сборки
Дальше я добавляю файл Directory.Build.props в корень решения. Он позволяет задавать настройки сборки, общие для всех проектов.
Пример:
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>
Так .csproj файлы остаются чистыми и единообразными, поэтому не нужно повторять эти свойства в каждом проекте.
Если потом захочешь подключить статические анализаторы или поменять параметры сборки, то делается это в одном месте.
Фишка в том, что .csproj файлы становятся почти пустыми, кроме ссылок на NuGet-пакеты.
3. Централизуй управление пакетами
Когда решение разрастается, версии NuGet-пакетов в разных проектах начинают расходиться и чёрт, это превращается в кошмар
Тут помогает централизованное управление пакетами.
Создай в корне файл Directory.Packages.props:
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
<PackageVersion Include="SonarAnalyzer.CSharp" Version="10.15.0.120848" />
</ItemGroup>
</Project>
Теперь, когда подключаешь пакет в проекте, указываешь только имя, без версии:
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
Все версии хранятся централизованно. Обновлять зависимости становится проще, а рассинхрон версий между проектами исчезает.
Если нужно, можно переопределить версию в конкретном проекте.
4. Подключи статический анализ кода
Статический анализ помогает ловить потенциальные баги и следить за качеством кода. В .NET уже есть встроенные анализаторы, но я обычно добавляю SonarAnalyzer.CSharp, ведь он делает проверку глубже.
Установи пакет:
Install-Package SonarAnalyzer.CSharpИ добавь его как глобальную зависимость в Directory.Build.props:
<ItemGroup>
<PackageReference Include="SonarAnalyzer.CSharp" />
</ItemGroup>
Дополнительно настрой сборку:
<Project>
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AnalysisLevel>latest</AnalysisLevel>
<AnalysisMode>All</AnalysisMode>
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
</Project>
Теперь билд упадет, если будут серьезные проблемы с качеством кода = неплохая страховка.
Если какие-то правила не подходят, можно понизить их уровень до none в .editorconfig.
5. Настрой локальную оркестрацию
Чтобы у всех разработчиков среда была одинаковой, стоит настроить контейнерную оркестрацию.
Один из вариантов это .NET Aspire
.NET Aspire добавляет сервис-дискавери, телеметрию и упрощает конфигурацию. Все это встроено прямо в .NET-проекты.
Пример добавления проекта и Postgres-ресурса:
var postgres = builder.AddPostgres("demo-db");
builder.AddProject<WebApi>("webapi")
.WithReference(postgres)
.WaitFor(postgres);
builder.Build().Run();Aspire под капотом использует Docker, но разработчику с ним работать приятнее.
В любом случае цель одна.
6. Автоматизируй сборку через CI
В конце я настраиваю простой workflow в GitHub Actions, чтобы проверять каждый коммит.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍17❤10❤🔥1
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9
Многие приложения сильно завязаны на реляционные базы данных. Твое приложение, скорее всего, использует сложные запросы, ограничения на данные и прочие приятные фишки реляционных баз. А значит, большая часть поведения твоего приложения напрямую зависит от того, как именно ведет себя база данных.
Поэтому я стараюсь тестировать код на настоящей базе, а не на какой-нибудь подделке. Раньше это было проблемой — большинство СУБД сложно поднимать и автоматизировать. Сейчас всё проще. Используй TestContainers🙄
TestContainers — это библиотека, которая поднимает Docker-контейнеры для тестов. Более того, в ней уже есть готовые конфигурации для популярных баз данных.
Сначала ставим библиотеку через NuGet. Потом настраиваем первый контейнер. Например, для Microsoft SQL Server:
Этот код скачает Docker-образ Microsoft SQL Server и запустит контейнер. Потом ты можешь запросить строку подключения и гонять тесты против реальной базы. В примере ниже создается таблица Dogs, вставляется запись и читается обратно. Тест, как ни странно, упадет. Почему? Подсказка в конце поста
Очистка контейнера:
Контейнеры автоматически очищаются
Интересный момент: если ты убьешь процесс, который гоняет тесты, что станет с контейнерами?
Запусти тест, потом посмотри, какие контейнеры сейчас активны:
Результат будет примерно такой:
Как видно, поднялось два контейнера. Потом можно убить тестовый процесс и наблюдать за docker ps — контейнеры вскоре исчезнут.
Это и есть сила TestContainers. Контейнер testcontainers/ryuk следит за всеми тестовыми контейнерами и убивает их (вместе с собой), если теряет связь с тестовым процессом.
У TestContainers уже есть готовая поддержка множества баз данных. Можно поискать наследников класса TestcontainerDatabase. Аналогично для систем обмена сообщениями — они наследуются от TestcontainerMessageBroker. Полный список есть в документации.
Если готовые конфиги тебе не подходят, можно поднять любой образ вручную:
Есть и стратегии ожидания, чтобы убедиться, что контейнер полностью запущен и готов к работе.
P.S. Почему упал Assert? Потому что тип datetime в SQL Server имеет странные правила округления. Он может незаметно подправить значение даты. Так что снова повторим, тестируй на настоящей базе!
👉 @KodBlog
Поэтому я стараюсь тестировать код на настоящей базе, а не на какой-нибудь подделке. Раньше это было проблемой — большинство СУБД сложно поднимать и автоматизировать. Сейчас всё проще. Используй TestContainers
TestContainers — это библиотека, которая поднимает Docker-контейнеры для тестов. Более того, в ней уже есть готовые конфигурации для популярных баз данных.
Сначала ставим библиотеку через NuGet. Потом настраиваем первый контейнер. Например, для Microsoft SQL Server:
// Конфигурируем базу, которую хотим поднять
var dbConfig = new MsSqlTestcontainerConfiguration
{
Password = "Test1234",
Database = "TestDB"
};
// Создаем контейнер с этой конфигурацией
var testContainer = new TestcontainersBuilder<MsSqlTestcontainer>()
.WithDatabase(dbConfig)
// Если не указать образ, MsSqlTestcontainerConfiguration выберет дефолтный
.WithImage("mcr.microsoft.com/mssql/server:2022-latest")
.Build();
// Запускаем контейнер
testContainer.StartAsync().Wait();
Этот код скачает Docker-образ Microsoft SQL Server и запустит контейнер. Потом ты можешь запросить строку подключения и гонять тесты против реальной базы. В примере ниже создается таблица Dogs, вставляется запись и читается обратно. Тест, как ни странно, упадет. Почему? Подсказка в конце поста
class Dog
{
public DateTime BirthDate { get; set; }
public string Name { get; set; }
}
using (var db = new SqlConnection(testContainer.ConnectionString))
{
// Для примера я использую Dapper
db.Execute(@"CREATE TABLE Dogs(
BirthDate DATETIME NOT NULL,
Name VARCHAR(MAX) NOT NULL
)");
var bornAt = new DateTime(2022, 12, 22, 12, 59, 59, 999);
db.Execute(@"INSERT Dogs(BirthDate,Name) VALUES(@BirthDate, @Name)", new
{
BirthDate = bornAt,
Name = "Joe"
});
var dog = db.Query<Dog>(@"SELECT * FROM Dogs").First();
// Этот assert упадет. Почему? Именно поэтому нужно тестировать на реальной базе.
Assert.AreEqual(bornAt, dog.BirthDate);
}
Очистка контейнера:
// Чистим за собой
testContainer.DisposeAsync();
Контейнеры автоматически очищаются
Интересный момент: если ты убьешь процесс, который гоняет тесты, что станет с контейнерами?
Запусти тест, потом посмотри, какие контейнеры сейчас активны:
docker psРезультат будет примерно такой:
roman@gamlor /tmp> docker ps
CONTAINER ID IMAGE COMMAND STATUS PORTS NAMES
86a1da367f12 mcr.microsoft.com/mssql/server:2017... "/opt/mssql/bi..." Up 6s 49180->1433/tcp busy_bohr
5cec99b15206 testcontainers/ryuk:0.3.4 "/app" Up 7s 49179->8080/tcp
testcontainers-ryuk...
Как видно, поднялось два контейнера. Потом можно убить тестовый процесс и наблюдать за docker ps — контейнеры вскоре исчезнут.
Это и есть сила TestContainers. Контейнер testcontainers/ryuk следит за всеми тестовыми контейнерами и убивает их (вместе с собой), если теряет связь с тестовым процессом.
У TestContainers уже есть готовая поддержка множества баз данных. Можно поискать наследников класса TestcontainerDatabase. Аналогично для систем обмена сообщениями — они наследуются от TestcontainerMessageBroker. Полный список есть в документации.
Если готовые конфиги тебе не подходят, можно поднять любой образ вручную:
var someContainer = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage("some-image")
.WithEnvironment("USER", "test-user")
.WithCommand("-flag-one -flag-two")
.Build();
Есть и стратегии ожидания, чтобы убедиться, что контейнер полностью запущен и готов к работе.
P.S. Почему упал Assert? Потому что тип datetime в SQL Server имеет странные правила округления. Он может незаметно подправить значение даты. Так что снова повторим, тестируй на настоящей базе!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11👏1
Каждое приложение, которое ты используешь, общается с сервером через API.
Много лет стандартом был REST:
- роуты, которые представляют ресурсы (/users, /posts)
- классические методы: GET, POST, PUT, DELETE
Работает отлично, но когда приложение разрастается, начинаются проблемы:
Overfetching — сервер отдает больше данных, чем нужно. (Ты просишь имя, а получаешь весь профиль).
Underfetching — чтобы отобразить один экран, приходится делать несколько запросов. (Ты запрашиваешь посты, а комментарии приходят отдельно).
Жесткость — если меняется UI, приходится менять и бэкенд.
Главная проблема в подходе:
фронтенд мыслит экранами, а бэкенд — ресурсами.
Именно поэтому появился GraphQL — язык, который описывает, как клиент может запрашивать и структурировать данные с сервера.
Он решает три ключевые задачи:
1. Один endpoint — меньше связности между фронтендом и бэкендом.
2 Гибкий запрос — клиент сам определяет, какие данные ему нужны.
3. Типизированная схема — валидация, автодополнение и автогенерация документации.
На практике REST отлично подходит для стабильных эндпоинтов, а GraphQL для динамических вьюх и составных данных.
👉 @KodBlog
Много лет стандартом был REST:
- роуты, которые представляют ресурсы (/users, /posts)
- классические методы: GET, POST, PUT, DELETE
Работает отлично, но когда приложение разрастается, начинаются проблемы:
Overfetching — сервер отдает больше данных, чем нужно. (Ты просишь имя, а получаешь весь профиль).
Underfetching — чтобы отобразить один экран, приходится делать несколько запросов. (Ты запрашиваешь посты, а комментарии приходят отдельно).
Жесткость — если меняется UI, приходится менять и бэкенд.
Главная проблема в подходе:
фронтенд мыслит экранами, а бэкенд — ресурсами.
Именно поэтому появился GraphQL — язык, который описывает, как клиент может запрашивать и структурировать данные с сервера.
Он решает три ключевые задачи:
1. Один endpoint — меньше связности между фронтендом и бэкендом.
2 Гибкий запрос — клиент сам определяет, какие данные ему нужны.
3. Типизированная схема — валидация, автодополнение и автогенерация документации.
На практике REST отлично подходит для стабильных эндпоинтов, а GraphQL для динамических вьюх и составных данных.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍4