Паттерн Стратегия представляет шаблон проектирования, который определяет набор алгоритмов, инкапсулирует каждый из них и обеспечивает их взаимозаменяемость. В зависимости от ситуации мы можем легко заменить один используемый алгоритм другим. При этом замена алгоритма происходит независимо от объекта, который использует данный алгоритм.
Когда использовать стратегию?
✅ Когда есть несколько родственных классов, которые отличаются поведением. Можно задать один основной класс, а разные варианты поведения вынести в отдельные классы и при необходимости их применять
✅ Когда необходимо обеспечить выбор из нескольких вариантов алгоритмов, которые можно легко менять в зависимости от условий
✅ Когда необходимо менять поведение объектов на стадии выполнения программы
✅ Когда класс, применяющий определенную функциональность, ничего не должен знать о ее реализации
Формальное определение паттерна на языке C# может выглядеть следующим образом:
Интерфейс IStrategy, который определяет метод Algorithm(). Это общий интерфейс для всех реализующих его алгоритмов. Вместо интерфейса здесь также можно было бы использовать абстрактный класс.
Классы ConcreteStrategy1 и ConcreteStrategy, которые реализуют интерфейс IStrategy, предоставляя свою версию метода Algorithm(). Подобных классов-реализаций может быть множество.
Класс Context хранит ссылку на объект IStrategy и связан с интерфейсом IStrategy отношением агрегации.
В данном случае объект IStrategy заключена в свойстве ContextStrategy, хотя также для нее можно было бы определить приватную переменную, а для динамической установки использовать специальный метод.
Теперь рассмотрим конкретный пример. Существуют различные легковые машины, которые используют разные источники энергии: электричество, бензин, газ и так далее. Есть гибридные автомобили. В целом они похожи и отличаются преимущественно видом источника энергии. Не говоря уже о том, что мы можем изменить применяемый источник энергии, модифицировав автомобиль. И в данном случае вполне можно применить паттерн стратегию:
В данном случае в качестве IStrategy выступает интерфейс IMovable, определяющий метод Move(). А реализующий этот интерфейс семейство алгоритмов представлено классами ElectricMove и PetroleMove. И данные алгоритмы использует класс Car.
👉 @KodBlog
Когда использовать стратегию?
Формальное определение паттерна на языке C# может выглядеть следующим образом:
public interface IStrategy
{
void Algorithm();
}
public class ConcreteStrategy1 : IStrategy
{
public void Algorithm()
{}
}
public class ConcreteStrategy2 : IStrategy
{
public void Algorithm()
{}
}
public class Context
{
public IStrategy ContextStrategy { get; set; }
public Context(IStrategy _strategy)
{
ContextStrategy = _strategy;
}
public void ExecuteAlgorithm()
{
ContextStrategy.Algorithm();
}
}
Интерфейс IStrategy, который определяет метод Algorithm(). Это общий интерфейс для всех реализующих его алгоритмов. Вместо интерфейса здесь также можно было бы использовать абстрактный класс.
Классы ConcreteStrategy1 и ConcreteStrategy, которые реализуют интерфейс IStrategy, предоставляя свою версию метода Algorithm(). Подобных классов-реализаций может быть множество.
Класс Context хранит ссылку на объект IStrategy и связан с интерфейсом IStrategy отношением агрегации.
В данном случае объект IStrategy заключена в свойстве ContextStrategy, хотя также для нее можно было бы определить приватную переменную, а для динамической установки использовать специальный метод.
Теперь рассмотрим конкретный пример. Существуют различные легковые машины, которые используют разные источники энергии: электричество, бензин, газ и так далее. Есть гибридные автомобили. В целом они похожи и отличаются преимущественно видом источника энергии. Не говоря уже о том, что мы можем изменить применяемый источник энергии, модифицировав автомобиль. И в данном случае вполне можно применить паттерн стратегию:
class Program
{
static void Main(string[] args)
{
Car auto = new Car(4, "Volvo", new PetrolMove());
auto.Move();
auto.Movable = new ElectricMove();
auto.Move();
Console.ReadLine();
}
}
interface IMovable
{
void Move();
}
class PetrolMove : IMovable
{
public void Move()
{
Console.WriteLine("Перемещение на бензине");
}
}
class ElectricMove : IMovable
{
public void Move()
{
Console.WriteLine("Перемещение на электричестве");
}
}
class Car
{
protected int passengers; // кол-во пассажиров
protected string model; // модель автомобиля
public Car(int num, string model, IMovable mov)
{
this.passengers = num;
this.model = model;
Movable = mov;
}
public IMovable Movable { private get; set; }
public void Move()
{
Movable.Move();
}
}
В данном случае в качестве IStrategy выступает интерфейс IMovable, определяющий метод Move(). А реализующий этот интерфейс семейство алгоритмов представлено классами ElectricMove и PetroleMove. И данные алгоритмы использует класс Car.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤2
Я вижу эту ошибку почти в каждом кодбейсе, который ревьюю.
Разрабы делают async-вызовы строго по одному:
Каждый
3 вызова по 200 мс каждый = 600 мс суммарно.
Надо освоить
Теперь общее ожидание = время самой медленной задачи. 600 мс превращаются примерно в 200 мс.
Я использую
* несколько API-запросов, которые не зависят друг от друга
* сбор данных для дашборда из разных источников
* отправка нотификаций в несколько каналов (email, SMS, push)
* инвалидация кэша по нескольким ключам
Почему мне это нравится:
* никаких доп. потоков, просто более умное планирование
* async I/O ждет ответы и не блокирует
* одно простое изменение дает огромный прирост по перформансу
* работает только если задачи реально независимы
* если Task B зависит от результата Task A, параллелить нельзя
* если задач тысячи, я добавляю
👉 @KodBlog
Разрабы делают async-вызовы строго по одному:
var user = await GetUserAsync();
var orders = await GetOrdersAsync();
var stats = await GetStatsAsync();
Каждый
await ждет завершения перед тем, как стартанет следующий.3 вызова по 200 мс каждый = 600 мс суммарно.
Надо освоить
Task.WhenAll().var userTask = GetUserAsync();
var ordersTask = GetOrdersAsync();
var statsTask = GetStatsAsync();
await Task.WhenAll(userTask, ordersTask, statsTask);
Теперь общее ожидание = время самой медленной задачи. 600 мс превращаются примерно в 200 мс.
Я использую
Task.WhenAll(), когда у меня:* несколько API-запросов, которые не зависят друг от друга
* сбор данных для дашборда из разных источников
* отправка нотификаций в несколько каналов (email, SMS, push)
* инвалидация кэша по нескольким ключам
Почему мне это нравится:
* никаких доп. потоков, просто более умное планирование
* async I/O ждет ответы и не блокирует
* одно простое изменение дает огромный прирост по перформансу
* работает только если задачи реально независимы
* если Task B зависит от результата Task A, параллелить нельзя
* если задач тысячи, я добавляю
SemaphoreSlim для ограничения параллелизмаPlease open Telegram to view this post
VIEW IN TELEGRAM
👍24❤5
Самый быстрый способ перестать ненавидеть регулярки: https://regex101.com/
Собирай, тестируй и дебажь regex пошагово, с объяснениями.
👉 @KodBlog
Собирай, тестируй и дебажь regex пошагово, с объяснениями.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8❤6👍4
Операторы break и continue с метками в C# 👀
Сейчас это champion proposal
👉 @KodBlog
Сейчас это champion proposal
outer: for (int x = 0; x < xMax; x++)
{
for (int y = 0; y < yMax; y++)
{
if (ShouldSkipRest(x, y))
continue outer;
if (ShouldExitAll(x, y))
break outer;
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
🤯22👏6👍1
Одна строка кода для ускорения EF Core
Когда EF Core загружает связанные сущности через
Пример:
При 100 постах, у каждого по 10 комментариев, в каждом по 10 реакций — вместо 11 100 записей получаем результирующий набор на 10 000 строк.
Добавьте
Query Splitting это не хак и не костыль. Это правильный способ работы с EF Core в read-heavy сценариях.
👉 @KodBlog
Когда EF Core загружает связанные сущности через
Include(), он генерирует один огромный SQL-запрос с множеством JOIN. Результат? Тысячи дублирующихся строк, гигабайты трафика и медленная работа.Пример:
var posts = await context.Posts
.Include(p => p.Comments)
.ThenInclude(c => c.Reactions)
.ToListAsync();
При 100 постах, у каждого по 10 комментариев, в каждом по 10 реакций — вместо 11 100 записей получаем результирующий набор на 10 000 строк.
Добавьте
.AsSplitQuery() — и EF Core разобьёт один большой запрос на несколько маленьких:var posts = await context.Posts
.AsSplitQuery() // ← магия здесь
.Include(p => p.Comments)
.ThenInclude(c => c.Reactions)
.ToListAsync();
Query Splitting это не хак и не костыль. Это правильный способ работы с EF Core в read-heavy сценариях.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🤯6❤2🔥1😁1
Публиковать доменные события до или после завершения транзакции?
По сути ты выбираешь между немедленной консистентностью и eventual consistency.
Я всегда публикую доменные события после коммита транзакции, желательно через Outbox.
А ты бы что выбрал и почему?
С EF interceptors можно творить реально интересные штуки.
Публикация доменных событий это всего лишь один из кейсов.
👉 @KodBlog
По сути ты выбираешь между немедленной консистентностью и eventual consistency.
Я всегда публикую доменные события после коммита транзакции, желательно через Outbox.
А ты бы что выбрал и почему?
С EF interceptors можно творить реально интересные штуки.
Публикация доменных событий это всего лишь один из кейсов.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
В .NET 10 JIT умеет убирать аллокацию из-за boxing при итерации по интерфейсам.
Но вот что еще круче: если GetEnumerator реализован через yield return, JIT в некоторых случаях тоже может это оптимизировать!
Классический пример: перебор RepeatedField<T>
Бенчмарк
👉 @KodBlog
Но вот что еще круче: если GetEnumerator реализован через yield return, JIT в некоторых случаях тоже может это оптимизировать!
Классический пример: перебор RepeatedField<T>
public IEnumerator<T> GetEnumerator()
{
for (int i = 0; i < this.count; ++i)
yield return this.array[i];
}
Бенчмарк
private readonly RepeatedField<int> _repeatedField
= GenerateData();
private static RepeatedField<int> GenerateData()
{
var result = new RepeatedField<int>();
result.AddRange(Enumerable.Range(1, 100));
return result;
}
[Benchmark]
public int Foreach_Over_RepeatedField()
{
int result = 0;
foreach (var x:int in _repeatedField)
{
result += x;
}
return result;
}
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10🔥3
.NET Reverse Proxy в 3 строки кода.
Этого достаточно, чтобы поднять YARP. Понятно, что настройки прокси нужно отдельно задать через appsettings.
Но все равно впечатляет, насколько легко быстро получить рабочий прокси.
Пробовал уже YARP?
Балансировка в YARP так же проста: по сути, просто обновляешь proxy settings.
Вот что нужно, чтобы стартануть
👉 @KodBlog
Этого достаточно, чтобы поднять YARP. Понятно, что настройки прокси нужно отдельно задать через appsettings.
Но все равно впечатляет, насколько легко быстро получить рабочий прокси.
Пробовал уже YARP?
Балансировка в YARP так же проста: по сути, просто обновляешь proxy settings.
Вот что нужно, чтобы стартануть
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4❤2🤨1
Please open Telegram to view this post
VIEW IN TELEGRAM
GitHub
csharplang/proposals/collection-expression-arguments.md at main · dotnet/csharplang
The official repo for the design of the C# programming language - dotnet/csharplang
👎12🤯6👍5❤2
Совет по C#:
❌ if (x == null) { x = new List<string>(); }
✅ x ??= new List<string>();
Результат тот же, но в одну строку.
??= присваивает значение только если слева null.
Доступно начиная с C# 8.0.
Если вы предпочитате первый подход, это не значит что вы плохой разработчик😜
👉 @KodBlog
Результат тот же, но в одну строку.
??= присваивает значение только если слева null.
Доступно начиная с C# 8.0.
Если вы предпочитате первый подход, это не значит что вы плохой разработчик
Please open Telegram to view this post
VIEW IN TELEGRAM
❤21👍12😁4🥴2
Группировка данных
ToLookup это LINQ-метод, который группирует элементы по ключу. Похож на GroupBy, но есть пару нюансов, из-за которых он прям удобен в некоторых кейсах.
Он строит ILookup<TKey, TElement> это по сути готовая, неизменяемая коллекция групп. В каждой группе лежат элементы с одинаковым ключом.
Достаёшь группу через индексатор
ToLookup vs GroupBy:
GroupBy возвращает
ToLookup делает всё сразу и держит результат в памяти:
➡️ сгруппировал один раз при вызове
➡️ потом дергаешь группы сколько угодно, ничего не пересчитывается
➡️ но память под всю структуру выделяется сразу
Когда ToLookup заходит лучше всего
Используй, если нужно:
➡️ много раз обращаться к одним и тем же группам
➡️ не ловить лишние пересчёты группировки
➡️ получать пустую выборку вместо исключений при неизвестном ключе
➡️ иметь неизменяемую структуру
Не лучший выбор, если:
➡️ данных очень много и жалко память
➡️ нужна ленивость и потоковая обработка
➡️ группировка нужна один раз, тогда проще и дешевле GroupBy
👉 @KodBlog
ToLookup это LINQ-метод, который группирует элементы по ключу. Похож на GroupBy, но есть пару нюансов, из-за которых он прям удобен в некоторых кейсах.
Он строит ILookup<TKey, TElement> это по сути готовая, неизменяемая коллекция групп. В каждой группе лежат элементы с одинаковым ключом.
var users = new[]
{
new { Name = "Anna", Department = "Dev" },
new { Name = "Boris", Department = "Dev" },
new { Name = "Clara", Department = "QA" }
};
var lookup = users.ToLookup(u => u.Department);
foreach (var user in lookup["Dev"])
{
Console.WriteLine(user.Name); // Anna, Boris
}
Достаёшь группу через индексатор
lookup["Dev"]. Если такого ключа нет, вернётся пустая последовательность (без исключения).ToLookup vs GroupBy:
GroupBy возвращает
IEnumerable<IGrouping<TKey, TElement>> и работает с отложенным выполнением. То есть группировка реально происходит при переборе, и при повторном переборе может считаться заново.ToLookup делает всё сразу и держит результат в памяти:
Когда ToLookup заходит лучше всего
Используй, если нужно:
Не лучший выбор, если:
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍6
Забавный трюк для .NET: как в рантайме узнать, в каком файле и на какой строке объявлен метод
Почему не подходят
Потому что они работают только на месте вызова и требуют менять сигнатуры методов. А если тебе нужно взять локацию *любого*
Идея: Используем Portable PDB: в PDB лежит маппинг IL → исходники (файл, строки, колонки) через sequence points.
Настройка
Обычно portable PDB генерятся по умолчанию, но можно явно:
▪️ отдельным файлом:
▪️ или вшить в сборку:
Реализация (суть)
Читаем PE, ищем embedded pdb, если нет — пытаемся открыть
Минимальный скелет:
Пример использования:
Важный нюанс: в проде PDB могут быть не задеплоены специально (безопасность/размер/политики), так что всегда держим
Источник
@KodBlog
Почему не подходят
[CallerMemberName]/[CallerFilePath]/[CallerLineNumber]?Потому что они работают только на месте вызова и требуют менять сигнатуры методов. А если тебе нужно взять локацию *любого*
MethodInfo (например, для снапшот-тестов или логов) — это уже мимо. Идея: Используем Portable PDB: в PDB лежит маппинг IL → исходники (файл, строки, колонки) через sequence points.
Настройка
Обычно portable PDB генерятся по умолчанию, но можно явно:
<PropertyGroup>
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup>
<DebugType>embedded</DebugType>
</PropertyGroup>
Реализация (суть)
Читаем PE, ищем embedded pdb, если нет — пытаемся открыть
.pdb рядом. Потом по MetadataToken метода достаем MethodDebugInformation, берем первый sequence point и вытаскиваем Document.Name (путь к файлу) + строку/колонку.Минимальный скелет:
// NuGet: System.Reflection.Metadata (9.*)
public static (string FilePath, SequencePoint SequencePoint)? GetMethodLocation(this MethodInfo methodInfo)
{
var location = methodInfo.DeclaringType?.Assembly.Location;
if (string.IsNullOrEmpty(location)) return null;
using var fs = File.OpenRead(location);
using var reader = new PEReader(fs);
var pdbProvider = reader.ReadDebugDirectory()
.Where(e => e.Type == DebugDirectoryEntryType.EmbeddedPortablePdb)
.Select(e => reader.ReadEmbeddedPortablePdbDebugDirectoryData(e))
.FirstOrDefault();
try
{
if (pdbProvider is null &&
!reader.TryOpenAssociatedPortablePdb(location, File.OpenRead, out pdbProvider, out _))
return null;
var pdbReader = pdbProvider!.GetMetadataReader();
var methodHandle = MetadataTokens.MethodDefinitionHandle(methodInfo.MetadataToken);
var mdi = pdbReader.GetMethodDebugInformation(methodHandle);
if (mdi.SequencePointsBlob.IsNil) return null;
var sp = mdi.GetSequencePoints().FirstOrDefault();
if (sp.Document.IsNil) return null;
var doc = pdbReader.GetDocument(sp.Document);
var filePath = pdbReader.GetString(doc.Name);
return (filePath, sp);
}
finally
{
pdbProvider?.Dispose();
}
}
Пример использования:
var method = typeof(Program).GetMethod(nameof(SampleMethod))!;
var loc = method.GetMethodLocation();
Console.WriteLine($"{loc?.FilePath}:{loc?.SequencePoint.StartLine}");
Важный нюанс: в проде PDB могут быть не задеплоены специально (безопасность/размер/политики), так что всегда держим
null-кейсы и не рассчитываем на 100% наличие. Источник
@KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤1
Расширяющий метод Entity Framework вместо TagWith.
👉 @KodBlog
public static class QueryableExtensions
{
public static IQueryable<T> TagWithDebugInfo<T>(
this IQueryable<T> query,
[CallerMemberName] string memberName = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0)
{
var debugInfo = $"Caller: {memberName}, File: {filePath}, Line: {lineNumber}";
// также можно добавить другую информацию, которую вы хотите, например, userId и т. д.
return query.TagWith(debugInfo); //delegate to built in TagWith
}
}
//usage sample
var query = context.YourDbSet
.Where(x => x.SomeProperty == someValue)
.TagWithDebugInfo();
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8👏3🔥2
Пожалуйста, пересмотрите решение и всё-таки разрешите асинхронную валидацию моделей 👀
Сейчас это висит в майлстоуне “.NET 11 Planning”, очень хотелось бы, чтобы фичу реально довели до реализации.
@KodBlog
Сейчас это висит в майлстоуне “.NET 11 Planning”, очень хотелось бы, чтобы фичу реально довели до реализации.
@KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8💯5😐4❤🔥2
This media is not supported in your browser
VIEW IN TELEGRAM
Проектировать схемы БД становится сильно проще
Это бесплатный дизайнер схем, который работает на 100% локально, даже офлайн. Можно собирать как простые, так и сложные схемы, и ничего не улетает в облако.
Поддерживает импорт существующих схем, визуальное проектирование с нуля и экспорт результата в разные форматы и даже в код.
https://relatedb.com/
👉 @KodBlog
Это бесплатный дизайнер схем, который работает на 100% локально, даже офлайн. Можно собирать как простые, так и сложные схемы, и ничего не улетает в облако.
Поддерживает импорт существующих схем, визуальное проектирование с нуля и экспорт результата в разные форматы и даже в код.
https://relatedb.com/
Please open Telegram to view this post
VIEW IN TELEGRAM
❤18🔥3
OpenTelemetry + .NET: трейсы, метрики и логи в SigNoz
В статье показано развёртывание через docker-compose, генерация быстрых/медленных/ошибочных запросов и просмотр коррелированных трейсов, метрик и логов в SigNoz. Описаны настройки .NET (ResourceBuilder, ActivitySource, авто‑инструментирование, OTLP) и практические советы; исходники в репо.
👉 @KodBlog
В статье показано развёртывание через docker-compose, генерация быстрых/медленных/ошибочных запросов и просмотр коррелированных трейсов, метрик и логов в SigNoz. Описаны настройки .NET (ResourceBuilder, ActivitySource, авто‑инструментирование, OTLP) и практические советы; исходники в репо.
Please open Telegram to view this post
VIEW IN TELEGRAM
Хабр
Наблюдаемость .NET-сервисов с помощью OpenTelemetry (traces/metrics/logs). Практический пример
Привет, Хабр! TL;DR: Поднимаем стенд в docker-compose (gateway + api + postgres + otel-collector + SigNoz/ClickHouse). Делаем 3 запроса: быстрый / медленный / с исключением. Смотрим в SigNoz трейсы...
Хватит дергать DateTime.UtcNow прямо в бизнес-логике.
Строчка выглядит безобидно, но именно из-за нее тесты потом превращаются в кошмар
Вот типичная ловушка:
* DateTime.UtcNow > user.SubscriptionEnd
Вроде ок, да?
Но как только пытаешься это протестировать, вылезает проблема:
- ты не контролируешь DateTime.UtcNow
- тесты становятся флаки или обрастают костылями
- бизнес-правила оказываются привязаны к инфраструктуре
Есть варианты лучше:
1. Передавать DateTime параметром. Самый простой путь, отлично заходит для маленьких методов.
2. Вынести время в интерфейс IClock. Масштабируется лучше и дружит с тестами.
Когда ты инвертируешь зависимость от времени, доменная логика становится:
✅ проще тестировать
✅ более детерминированной
✅ с нормальным разделением ответственности
В следующий раз, когда пишешь условие, завязанное на время, остановись и спроси:
“Мне нужно будет это тестировать?”
Если да, выноси часы наружу.
@KodBlog
Строчка выглядит безобидно, но именно из-за нее тесты потом превращаются в кошмар
Вот типичная ловушка:
* DateTime.UtcNow > user.SubscriptionEnd
Вроде ок, да?
Но как только пытаешься это протестировать, вылезает проблема:
- ты не контролируешь DateTime.UtcNow
- тесты становятся флаки или обрастают костылями
- бизнес-правила оказываются привязаны к инфраструктуре
Есть варианты лучше:
1. Передавать DateTime параметром. Самый простой путь, отлично заходит для маленьких методов.
2. Вынести время в интерфейс IClock. Масштабируется лучше и дружит с тестами.
Когда ты инвертируешь зависимость от времени, доменная логика становится:
В следующий раз, когда пишешь условие, завязанное на время, остановись и спроси:
“Мне нужно будет это тестировать?”
Если да, выноси часы наружу.
@KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🥴9🔥3❤1👍1
Не пиши в SQL = NULL или != NULL
Для NULL всегда используй IS NULL / IS NOT NULL
NULL это “нет значения / неизвестно”, и обычные операторы сравнения (=, !=, <, >) с ним не работают, потому что NULL не равен, не больше и не меньше вообще ничего.
= / != сравнивают значения. А NULL это не значение, а отсутствие значения. Поэтому IS / IS NOT проверяют именно наличие или отсутствие значения.
👉 @KodBlog
Для NULL всегда используй IS NULL / IS NOT NULL
NULL это “нет значения / неизвестно”, и обычные операторы сравнения (=, !=, <, >) с ним не работают, потому что NULL не равен, не больше и не меньше вообще ничего.
= / != сравнивают значения. А NULL это не значение, а отсутствие значения. Поэтому IS / IS NOT проверяют именно наличие или отсутствие значения.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍23🔥3🥴3
Лучший конфиг для HttpClient это тот, который ты не копируешь в каждом сервисе.
В .NET ты настраиваешь все один раз через IHttpClientFactory (и Aspire, если используешь):
* service discovery для вызовов сервис-сервис
* дефолтную resiliency-настройку, чтобы кратковременные фейлы не ломали тебе жизнь
Дальше просто инжектишь клиент и пишешь бизнес-код.
- полный разбор
👉 @KodBlog
В .NET ты настраиваешь все один раз через IHttpClientFactory (и Aspire, если используешь):
* service discovery для вызовов сервис-сервис
* дефолтную resiliency-настройку, чтобы кратковременные фейлы не ломали тебе жизнь
Дальше просто инжектишь клиент и пишешь бизнес-код.
- полный разбор
Please open Telegram to view this post
VIEW IN TELEGRAM
👏6
25 Claude Code skills для .NET-разрабов
Покрывает штуки, которые реально важны и неочевидны (типа: как тестировать транзакционные письма в ASP.NET Core?) и вообще помогает улучшать вывод LLM (типы!).
github.com/Aaronontheweb/dotnet-skills?tab=readme-ov-file
Все это про то, чтобы Claude делал вещи нормально без того, чтобы ты на него орал.
👉 @KodBlog
Покрывает штуки, которые реально важны и неочевидны (типа: как тестировать транзакционные письма в ASP.NET Core?) и вообще помогает улучшать вывод LLM (типы!).
github.com/Aaronontheweb/dotnet-skills?tab=readme-ov-file
Все это про то, чтобы Claude делал вещи нормально без того, чтобы ты на него орал.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍2🥴2