.NET Разработчик
6.74K subscribers
480 photos
4 videos
14 files
2.45K links
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2753. #BestPractices #SQL
Как оптимизировать SQL-запросы. Часть 6

Части 1, 2, 3, 4-5

Часть VI. Пусть БД поможет вам: измерение, поддержка и доверие оптимизатору
Планировщик запросов умнее, чем принято считать. Ваша задача — предоставить ему достоверную информацию, а затем проверить результат.

1. Прочитайте план выполнения
План выполнения — это то, как база будет выполнять ваш запрос. Прежде чем что-либо оптимизировать, посмотрите на план. В PostgreSQL команда EXPLAIN ANALYZE выполняет запрос и сообщает план и что произошло на самом деле:
EXPLAIN ANALYZE
SELECT * FROM orders WHERE total > 100;

Ознакомьтесь с ним, чтобы понять суть дорогостоящих операций: последовательное сканирование большой таблицы обычно означает отсутствие или неиспользуемый индекс, а большой разрыв между расчётным и фактическим количеством строк указывает на устаревшую статистику. План превращает оптимизацию из догадок в подтверждение. Вы исправляете то, что, по данным плана, работает медленно, без гадания.

2. Поддерживайте актуальность статистики
Планировщик выбирает между сканированием и поиском по индексу на основе статистики ваших данных — количества строк, количества уникальных значений и их распределения. Когда эта статистика устарела, планировщик делает неверные предположения и выбирает неверные планы, даже при идеальных индексах. ANALYZE обновляет статистику:
ANALYZE orders;

PostgreSQL использует автоочистку (autovacuum) для поддержания актуальности статистики, но после большой загрузки данных или массового обновления стоит самостоятельно запустить ANALYZE. Движок может оптимизировать запрос настолько хорошо, насколько это позволяют его статистические данные. Хорошая статистика гораздо важнее точной формулировки запроса.

3. Используйте подсказки запросов экономно
Подсказка запроса заставляет базу выполнять запрос по вашему алгоритму, а не по алгоритму планировщика. PostgreSQL намеренно поставляется без синтаксиса подсказок. Его философия в том, что вы должны исправлять первопричину — индексы, статистику, форму запроса — а не быть умнее планировщика. Вы можете подкрутить его с помощью настроек сессии, но рассматривайте это только как диагностику в среде разработки:
-- Только для диагностики: посмотреть, как выглядит план без последовательного сканирования
SET enable_seqscan = off;

Если вам действительно нужны подсказки, расширение pg_hint_plan добавит их — но используйте это как последнюю меру. Подсказка фиксирует решение, которое сегодня кажется правильным, но может оказаться неверным после увеличения объёма данных, так что завтра оно незаметно превратится в медленный запрос.

4. Непрерывный мониторинг и настройка
Оптимизация — не разовая задача. Объём данных растёт, шаблоны доступа меняются, и вчерашний быстрый запрос становится сегодняшним узким местом. Отслеживайте, какие запросы на самом деле обходятся дороже всего. Расширение pg_stat_statements агрегирует статистику выполнения по всей вашей рабочей нагрузке:
-- Самые медленные запросы по среднему времени 
SELECT query, calls, mean_exec_time
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;

Современный планировщик запросов уже выполняет умные переписывания — перенос предикатов вниз, изменение порядка соединений, преобразование IN в JOIN. Вы мало выиграете, вручную настраивая текст предложения WHERE. Большая выгода достигается за счёт предоставления планировщику того, что ему нужно: селективных предикатов, актуальной статистики и правильных индексов — а затем анализа плана для подтверждения.

Источник: https://antondevtips.com/blog/how-to-optimize-sql-queries-20-proven-best-practices
👍5
Please open Telegram to view this post
VIEW IN TELEGRAM
День 2754. #Оффтоп #AI
Программисты разучились думать?
Доброй субботы вам, дорогие подписчики.

Сегодня порекомендую вам видео от Фила Ранжина на хайповую тему «Программисты разучились думать – во всём виноват ИИ».

- Как ИИ влияет на когнитивные способности? Действительно ли мы меньше думаем, когда пользуемся ИИ?

- Суперпроизводительность с ИИ. Правда ли агенты сделали нас 10х инженерами?

- Мы пишем гораздо больше кода с помощью ИИ. А нужен ли весь этот код?

- Какие навыки атрофируются у разработчиков и насколько это опасно?

- Как ИИ влияет на командную работу и как на это всё смотрит бизнес?

- Что делать с тоннами нейрослопа, которые принёс тебе коллега на ревью?

- Стратегии борьбы с «ИИ-слабоумием». К чему всё это приведёт в будущем?

В общем, по-моему, довольно интересный ролик, заставляющий задуматься о своём месте в этом несущемся вперёд с дикой скоростью мире ИТ.

Приятного просмотра.

https://www.youtube.com/watch?v=hU82W64jn9I
👎3
День 2755. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.

43. Устойчивость и обработка кратковременных сбоев
«Расскажите, как бы вы реализовали устойчивость и обработку кратковременных сбоев. Приведите примеры методов и инструментов, которые вы бы использовали, чтобы приложение могло корректно обрабатывать сбои и восстанавливаться после них».

Хороший ответ
При создании отказоустойчивых приложений крайне важно предвидеть и эффективно обрабатывать кратковременные сбои. Это гарантирует, что приложение останется отзывчивым и доступным даже в неблагоприятных условиях. Вот шаги и инструменты, которые я бы использовал:
- Политики повторных попыток: пакет Microsoft.Extensions.Http.Polly обеспечивает устойчивость и обработку кратковременных сбоев путём реализации политики повторных попыток. Это помогает в ситуациях, когда операции могут иногда завершаться с ошибкой из-за временных проблем, таких как проблемы с сетевым подключением или тайм-аутом сервера:
var builder = WebApplication.CreateBuilder(args);

builder.Services
.AddHttpClient("ApiClient", c =>
{
c.BaseAddress = new Uri("https://apiurl.com");
})
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(new[]
{
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(10)
})
);

var app = builder.Build();

app.MapGet("/external-api",
async (IHttpClientFactory hcf) =>
{
var client = hcf.CreateClient("ApiClient");
var response = await client.GetAsync("/data");
return resp.IsSuccessStatusCode
? Results.Ok(await resp.Content.ReadAsStringAsync())
: Results.Problem();
});

app.Run();


- Паттерн Прерыватель Цепи (Circuit Breaker): предотвращает повторные сбои из-за одной и той же ошибки. Этот паттерн полезен для обработки случаев, когда продолжение выполнения действия может нанести системе больший вред. Polly также можно настроить для работы с ним:
.AddTransientHttpErrorPolicy(p => 
p.CircuitBreakerAsync(3, TimeSpan.FromMinutes(1)));


- Тайм-ауты: явные тайм-ауты для всех внешних HTTP-запросов гарантируют, что приложение не зависнет на неопределённое время, если сервис, от которого оно зависит, работает медленно или не отвечает.

- Резервные (Fallback) политики: для предоставления значений по умолчанию или упрощённых ответов в случае сбоя вызова внешнего сервиса:
.AddTransientHttpErrorPolicy(p => 
p.Or<TimeoutException>()
.FallbackAsync(
new HttpResponseMessage(HttpStatusCode.OK)
{ Content = new StringContent("Резервный ответ") })
);


Преимущества
- Благодаря эффективной обработке кратковременных сбоев приложение остается более доступным и отзывчивым.
- Обеспечивает более стабильный и надёжный пользовательский опыт.
- Помогает предотвратить каскадные сбои (например, перегрузку замедлившегося API запросами) из-за неполадок в одной части системы.

Часто встречающийся плохой ответ
«Нужно просто поставить повыше значение тайм-аута для внешних вызовов, чтобы дать сервисам достаточно времени для реагирования и снизить вероятность сбоев.»

Почему это неправильно
- Непонимание обработки ошибок: простое увеличение значений тайм-аута не устраняет первопричины кратковременных сбоев и может привести к ухудшению пользовательского опыта из-за увеличения времени ожидания.

- Отсутствие комплексной стратегии: этот подход не учитывает возможность внедрения комплексной стратегии повторных попыток, прерываний и резервных ответов для эффективной обработки различных типов сбоев.

- Потенциальная повышенная нагрузка на систему: более длительные тайм-ауты могут усугубить проблемы, занимая системные ресурсы на длительные периоды.

Эта ошибка обычно является результатом недостаточного понимания принципов обработки кратковременных ошибок или чрезмерно упрощённого подхода к обработке ошибок и исключений.

Источник:
https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👍5
День 2756. #ЧтоНовенького #NET11 #CSharp15
Вышли Превью 6 и 7 .NET 11

Посмотрим на самые интересные новинки в языке C#.

1. Индексаторы-расширения
Члены-расширения теперь включают индексаторы, что позволяет разработчикам добавлять доступ к существующему типу из блока расширения. Т.е. можно добавлять доступ this[…] к типу из блока расширения. Индексатор-расширение объявляется как индексатор экземпляра внутри блока расширения. В следующем примере добавляется индексация с конца к IReadOnlyList<T>, в котором определён индексатор this[int], но нет индексатора this[Index]:
using System;
using System.Collections.Generic;

IReadOnlyList<string> log = ["start", "work", "done"];

Console.WriteLine(log[^1]); // done
Console.WriteLine(log[^2]); // work

public static class ReadOnlyListExtensions
{
extension<T>(IReadOnlyList<T> list)
{
public T this[Index i] =>
list[i.GetOffset(list.Count)];
}
}


2. Метки break и continue
Теперь функции break и continue могут указывать на окружающий их цикл или оператор switch, поэтому вы можете выйти из внешней конструкции или продолжить её выполнение непосредственно из внутренней, не передавая состояние через флаг и не прибегая к goto. Добавьте метку к целевому циклу, а затем ссылайтесь на него из break или continue:
string? found = null;
outer: for (int x = 0; x < xMax; x++)
{
for (int y = 0; y < yMax; y++)
{
if (GetValue(x, y) is { } value && value == target)
{
found = value;
break outer;
}
}
}

Аналогично для continue:
row: for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
if (ShouldSkipRestOfRow(i, j)) continue row;
Process(i, j);
}
}

Метка должна быть прикреплена непосредственно к оператору for, foreach, while, do или switch, а break/continue с меткой должен находиться внутри этого оператора.

3. Шаблоны объединений сопоставляют объединение или его значение
Теперь для типов объединений используется подход сопоставления «попробуй оба варианта»: при применении шаблона к значению объединения компилятор сначала проверяет шаблон на соответствие самому экземпляру объединения, а если эта проверка не удалась, то и на соответствие содержащемуся в объединении значению. Это согласовывает семантику сопоставления с обновлённым спецификатором объединений и применяется к шаблонам типа, переменной, объявления, списка и рекурсивным шаблонам:
public record class Dog(string Name);
public record class Cat(int Lives);

public union Pet(Dog, Cat);

Pet pet = new Cat(9);

// Сопоставление с типом объединения
if (pet is Pet) // true
Console.WriteLine("Это питомец");

// Сопоставление со значением
if (pet is Cat { Lives: > 0 } cat) // true
Console.WriteLine($"У кошки {cat.Lives} жизней");


4. Полнота проверки параметров типа, ограниченных закрытым типом
Полнота проверки параметров с помощью switch теперь учитывает параметры обобщённых типов, ограниченные закрытым типом. Когда обрабатывается каждый прямой подтип закрытого базового типа, компилятор больше не предупреждает, что оператор switch не является исчерпывающим — даже когда входные данные указываются в качестве параметра типа:
public closed record class Shape;
public record class Circle(double Radius) : Shape;
public record class Square(double Side) : Shape;

static double Area<T>(T shape)
where T : Shape => shape switch
{
Circle(var r) => Math.PI * r * r,
Square(var s) => s * s
};

Компилятор решает, что T не может вводить никаких дополнительных случаев, поскольку каждый производный от Shape тип уже охвачен.

Источники:
-
https://github.com/dotnet/core/blob/main/release-notes/11.0/preview/preview6/csharp.md
-
https://github.com/dotnet/core/blob/main/release-notes/11.0/preview/preview7/csharp.md
👍12
День 2757. #ЗаметкиНаПолях #DDD
Сохраняем Богатую Доменную Модель в EF Core. Начало

С богатой доменной моделью всегда была проблема. В теории это хорошо, но EF Core нужны публичные сеттеры и публичный конструктор без параметров. ORM вроде как навязывает нам анемичную модель. Однако сейчас это не так: EF Core с удовольствием сохранит полностью инкапсулированный агрегат, и результатом станет доменная модель, которая обеспечивает соблюдение инвариантов в одном месте, а ORM будет делать свою часть работы. Проанализируем агрегат от начала до конца и рассмотрим все места, где, предположительно, EF Core и инкапсуляция могут конфликтовать.

Агрегат, который мы хотим сохранить
Наш домен - домашняя пивоварня (потому что заказы товаров уже надоели). Batch - партия брожения. Вы измеряете плотность (gravity) в AddReading(), и как только она стабилизируется, разливаете пиво по бутылкам в Bottle(). Вот модель партии, без публичных сеттеров, создающаяся через фабричный метод Start() и изменяющая состояние через методы:
public sealed class Batch
{
private readonly List<FermentationReading>
_readings = [];
private readonly List<IDomainEvent>
_events = [];
private DateTime? _bottledAt;

private Batch() { } // Для EF Core

private Batch(BatchId id,
RecipeId recipeId, Volume volume)
{
Id = id;
RecipeId = recipeId;
Volume = volume;
Status = Status.Fermenting;
}

public BatchId Id { get; private set; }
public RecipeId RecipeId { get; private set; }
public BatchStatus Status { get; private set; }
public Volume Volume { get; private set; }

public IReadOnlyCollection<FermentationReading>
Readings => _readings.AsReadOnly();
public IReadOnlyCollection<IDomainEvent>
DomainEvents => _events.AsReadOnly();

public static Batch Start(
RecipeId recipeId, Volume volume) =>
new(new BatchId(Guid.CreateVersion7()), recipeId, volume);

public void AddReading(
Gravity gravity, DateTime takenAt)
{
if (Status != Status.Fermenting)
throw new DomainException("Измерения доступны только в процессе ферментации.");

_readings.Add(new FermentationReading(gravity, takenAt));
}

public void Bottle(TimeProvider tp)
{
if (Status != Status.Fermenting)
throw new DomainException("Только ферментированная партия может быть бутилирована.");

Gravity[] lastTwo = _readings
.OrderBy(r => r.TakenAt)
.TakeLast(2)
.Select(r => r.Gravity)
.ToArray();

if (lastTwo.Length < 2 || lastTwo[0] != lastTwo[1])
throw new DomainException("Плотность должна быть одинаковой в последних 2х измерениях.");

Status = Status.Bottled;
_bottledAt = tp.GetUtcNow().UtcDateTime;
_events.Add(new BatchBottled(Id));
}

public void ClearDomainEvents() => _events.Clear();
}

public readonly record struct BatchId(Guid Value);
public readonly record struct RecipeId(Guid Value);
public readonly record struct Gravity(decimal Value);
public sealed record Volume(decimal Amount, string Unit);
public sealed record FermentationReading(decimal Gravity, DateTime TakenAt);

Дочерние сущности агрегата – записи, поэтому равенство по значению достаётся нам бесплатно.

Три особенности здесь напрямую заимствованы из архитектуры агрегатов:
1. RecipeId ссылается на другой агрегат только по ID;
2. FermentationReading — дочерняя сущность, которая существует и исчезает вместе с агрегатом (Batch);
3. _bottledAt — приватное поле без каких-либо свойств.
Каждая из особенностей, предположительно, «ломает» EF Core, поэтому далее рассмотрим их по порядку.

Продолжение следует…

Источник:
https://milanjovanovic.tech/blog/persisting-a-rich-domain-model-with-ef-core
👍9👎1
День 2758. #ЗаметкиНаПолях #DDD
Сохраняем Богатую Доменную Модель в EF Core. Продолжение

Начало

Теперь разберём, как EF Core сохраняет и загружает такой богатый агрегат (см. код в предыдущем посте).

Приватные конструкторы и приватные сеттеры просто работают
Когда EF Core материализует сущность, он не использует публичный API. Он вызывает приватный конструктор без параметров и записывает данные в свойства через их базовые поля, приватные сеттеры и т.п.. Поэтому пустой приватный конструктор - единственная уступка, которую доменная модель делает ORM. Загрузка и изменение партии выглядят обычно:
Batch batch = await context.Batches
.SingleAsync(b => b.Id == batchId);

batch.AddReading(new Gravity(1.012m), timeProvider.GetUtcNow().UtcDateTime);

await context.SaveChangesAsync();

Трекер изменений считывает те же самые базовые поля, поэтому приватные сеттеры ничего не скрывают от SaveChanges.

EF может даже использовать привязку через параметризованный конструктор, сопоставляя параметры с отображаемыми свойствами по имени и типу, независимо от того, приватные они или нет. Но это работает только для скалярных типов. Свойства навигации не могут быть привязаны через конструктор, поэтому агрегату с коллекцией по-прежнему нужен конструктор без параметров. EF также пропускает фабричные валидации при загрузке, что правильно: повторное выполнение валидации во время материализации сделало бы старые объекты недоступными для загрузки при изменении правил в будущем.

Типизированные ID и ссылки на другие агрегаты
BatchId и RecipeId — строго типизированные идентификаторы, сопоставляемые с помощью преобразования значений:
public sealed class BatchConfiguration
: IEntityTypeConfiguration<Batch>
{
public void Configure(EntityTypeBuilder<Batch> bld)
{
bld.ToTable("batches");
bld.HasKey(b => b.Id);
bld.Property(b => b.Id)
.HasConversion(id => id.Value,
value => new BatchId(value))
.ValueGeneratedNever();

bld.Property(b => b.RecipeId)
.HasConversion(id => id.Value,
value => new RecipeId(value));
}
}

ValueGeneratedNever важно: мы генерируем Guid.CreateVersion7() в фабрике, поэтому говорим EF не вмешиваться.
RecipeId не является свойством навигации для Recipe. Рецепт — это самостоятельный агрегат, но здесь он нам не нужен.

Инкапсулированная коллекция
По соглашению, EF находит поле _readings для навигации по Readings. Но его лучше настроить явно, чтобы сопоставление сохранялось даже после переименования:
// продолжение BatchConfiguration.Configure()
…
bld.HasMany<FermentationReading>("_readings")
.WithOne()
.HasForeignKey("batch_id");

bld.Navigation("_readings")
.UsePropertyAccessMode(PropertyAccessMode.Field)
.AutoInclude();

PropertyAccessMode.Field указывает EF читать и записывать поле, не обращаясь к публичному представлению Readings. AutoInclude() важно добавлять, т.к. метод Bottle() читает _readings, поэтому использование частично загруженного Batch небезопасно. WithOne() без аргументов означает, что дочерний элемент не имеет навигации обратно к Batch; внешний ключ batch_id существует только как теневое свойство.

Состояние без свойств
_bottledAt не имеет свойств, только приватное поле, и EF всё равно его сопоставляет:
bld.Property<DateTime?>("_bottledAt")
.HasColumnName("bottled_at");

Однако, чтобы отфильтровать по этому полю, придётся добавлять в запрос «костыль»:
var thisWeek = await context.Batches
.Where(b => EF.Property<DateTime?>(b, "_bottledAt") >= weekAgo)
.ToListAsync();

Поэтому, если в проекте есть фильтрация по полю, лучше добавить свойство с приватным сеттером, а если полем пользуется только агрегат, можно оставить его без свойства.

Окончание следует…

Источник:
https://milanjovanovic.tech/blog/persisting-a-rich-domain-model-with-ef-core
👎2👍1
День 2759. #ЗаметкиНаПолях #DDD
Сохраняем Богатую Доменную Модель в EF Core. Окончание

Начало
Продолжение

Продолжаем разбирать, как EF Core сохраняет и загружает богатый агрегат (см. код в предыдущем посте).

Объекты-значения: принадлежащие (Owned), комплексные (Complex) типы и преобразования
Volume — объект-значение, а многосвойственные объекты-значения отображаются как комплексные типы, хранящие свои элементы непосредственно в таблице владельца (без объединения, без отдельной идентификации):
// продолжение BatchConfiguration.Configure()
…
bld.ComplexProperty(b => b.Volume, vol =>
{
vol.Property(v => v.Amount)
.HasColumnName("vol_amount");
vol.Property(v => v.Unit)
.HasColumnName("vol_unit");
});


Комплексные типы были добавлены в EF Core 8 (без необязательных свойств и коллекций); в EF Core 10 эти ограничения сняты. В EF 8 или 9 коллекцию объектов-значений можно преобразовать в коллекцию принадлежащего типа, которая работает везде (но содержит скрытый теневой ключ, т.к. EF рассматривает их как сущности, притворяющиеся значениями). Допустим, наш Batch имел бы коллекцию объектов-значений - лог добавлений хмеля, тогда в EF 8 и 9 мы могли бы связать его с отдельной таблицей:
bld.OwnsMany(b => b.HopAdditions, hop =>
{
hop.ToTable("hop_additions");
hop.WithOwner()
.HasForeignKey("batch_id");
});


Перечисления конвертируются в простой текст, так что БД остаётся читаемой:
bld.Property(b => b.Status)
.HasConversion<string>()
.HasMaxLength(20);

Преобразованные свойства имеют один общий недостаток: LINQ работает с типом провайдера, поэтому сортировка по статусу даёт алфавитный порядок строк (Bottled, Dumped, Fermenting), а не порядок задуманного нами жизненного цикла.

События домена не должны попадать в схему
IDomainEvent не является сущностью, поэтому укажите EF игнорировать коллекцию DomainEvents:
bld.Ignore(b => b.DomainEvents);

Конечно, доменные события должны быть обработаны где-то. И лучшее место для этого – перехватчик сохранения изменений:
public class DomainEventsInterceptor(
// внедряем диспетчер доменных событий
) : SaveChangesInterceptor
{
public override async ValueTask<int> SavedChangesAsync(
SaveChangesCompletedEventData data,
int result,
CancellationToken ct = default)
{
var events = data.Context!.ChangeTracker
.Entries<Batch>()
.SelectMany(e =>
{
var ev = e.Entity.DomainEvents.ToList();
e.Entity.ClearDomainEvents();
return ev;
})
.ToList();

// … обрабатываем события с помощью диспетчера …

return await base.SavedChangesAsync(data, result, ct);
}
}


Домен не ссылается на EF Core
Все конфигурации сопоставлений находятся в BatchConfiguration, ни один из них не находится в Batch: никаких атрибутов сопоставления, никакого базового класса ORM!
DbContext находится на уровне инфраструктуры и получает всю конфигурацию из своей сборки:
public sealed class BreweryDbContext(
DbContextOptions<BreweryDbContext> opts)
: DbContext(opts)
{
public DbSet<Batch> Batches => Set<Batch>();

protected override void OnModelCreating(ModelBuilder mb)
{
mb.ApplyConfigurationsFromAssembly(
typeof(BreweryDbContext).Assembly);
}
}


Итого
Возражение о том, что «EF Core заставляет создавать анемичные модели», устарело много лет назад. Приватный конструктор, вспомогательные поля, преобразование значений и комплексные типы покрывают все потребности полностью инкапсулированного агрегата, и всё это находится в классах конфигурации, которые домен никогда не видит. Реальные ограничения сводятся к двум:
- свойства сложных типов и свойства навигации не могут быть привязаны через параметры конструктора,
- преобразованные поля или поля без свойств транслируются в тип провайдера в запросах.

ORM никогда не заставлял вашу доменную модель быть анемичной. Это задача сопоставления, выполняемая один раз для каждого агрегата.

Источник:
https://milanjovanovic.tech/blog/persisting-a-rich-domain-model-with-ef-core
👍3👎1
День 2760. #Карьера
Основные Правила Разработки ПО. Начало

Я уже не раз приводил здесь подобные советы от опытных разработчиков. Gérald Barré (aka meziantou) также составил свой список.

1. Вам платят не за код, а за решение проблем
Написание кода — не цель, а средство её достижения. Ваша ценность как инженера в понимании бизнес-проблем и предоставлении решений, которые создают ценность для пользователей и организации.
Иногда лучшее решение - отсутствие кода. Возможно, изменение процесса сработает лучше, существующий инструмент можно настроить по-другому, а может проблему и не нужно решать. Прежде чем приступать к реализации, уделите время пониманию сути проблемы, проанализируйте ситуацию, исходя из желаемого результата, и рассмотрите все возможные подходы.

2. Нет «лучшего» решения, есть только компромиссы
Разработка ПО – это принятие решений на основе неполной информации. У каждого вашего выбора есть плюсы и минусы, и то, что подходит для одного проекта, может оказаться ужасным для другого.
- Микросервисы? Зависит от размера команды, потребностей в масштабируемости и операционной сложности, которая вам под силу.
- SQL/NoSQL? Зависит от структуры данных, шаблонов запросов и требований к согласованности.
- TDD? Зависит от контекста, опыта команды и ограничений проекта.
Опытный инженер не ищет «лучшее» решение. Он оценивает компромиссы, исходя из текущих ограничений: навыков команды, сроков, бюджета, потребностей в масштабируемости и требований к сопровождению. Задокументируйте, почему вы выбрали тот или иной вариант. В будущем вы (или коллеги) оценят понимание контекста, лежащего в основе принятых решений. Всегда знайте, что именно вы оптимизируете: производительность, удобство сопровождения, время выхода на рынок, скорость работы команды и т.п.
Когда вы ходите по кругу, не находя чёткого «хорошего решения», это обычно означает, что вы недостаточно определили ограничения для проблемы. Добавьте больше ограничений: бюджет, дедлайн, размер команды, допустимый уровень технического долга, ожидаемый масштаб.

3. Чем меньше кода, тем лучше
Больше кода - больше работы по поддержке, тестированию, отладке и поиску ошибок. Лучший код часто тот, который вы не написали. Каждая строка должна оправдывать своё существование.
Прежде чем добавлять новую функцию или абстракцию, спросите себя: действительно ли это необходимо? Могу ли я решить это с помощью существующего кода? Решаю ли я существующую проблему или ту, которая, как я думаю, может возникнуть в будущем? Немедленно удаляйте неиспользуемый код. Мёртвый код создает путаницу, увеличивает нагрузку на поддержку и создаёт ложное представление о том, что система на самом деле делает.

4. Каждая зависимость — это обуза
Зависимости могут быть заброшены, содержать уязвимости безопасности, незаметно ломаться при обновлениях или приводить к появлению транзитивных зависимостей, которые вы никогда не планировали включать, а также могут препятствовать обновлению других пакетов из-за несовместимости.
Регулярно проверяйте зависимости. Удаляйте неиспользуемые. Оценивайте состояние проектов, от которых вы зависите (последние коммиты, активные сопровождающие, история безопасности). Учитывайте общую стоимость владения, а не только первоначальное удобство.

5. Выпускайте рано, итерируйте часто
Идеальность — враг завершённости. Чем дольше вы оттягиваете выпуск, тем дольше откладываете получение реальной обратной связи от пользователей. Сначала сделайте, чтоб работало, затем сделайте это правильно, а затем сделайте это быстрым.
Ранние релизы помогают проверить предположения, выяснить, что действительно нужно пользователям (в отличие от того, что вы думаете, что им нужно), и скорректировать курс. Выбрасывать код больно, но ещё больнее потратить полгода на создание неправильного продукта.
Опросы пользователей, прототипы и моки могут проверить идеи до написания кода. Иногда простой разговор показывает, что запланированная вами функция совсем не то, что нужно пользователям. Внедряйте механизмы обратной связи в процесс разработки, чтобы расти как инженер.

6. Проектируйте с учётом изменений, т.к. ПО никогда не бывает завершённым
Потребности пользователей и технологии меняются, платформы обновляются, и обнаруживаются уязвимости безопасности. Сделайте так, чтобы ПО было легко модифицировать, расширять и поддерживать. Создавайте с учётом расширяемости и ясности. Проводите рефакторинг непрерывно и постепенно, а не ждите «большой переработки». Сочетайте принцип YAGNI с продуманным проектированием, которое не загонит вас в тупик. Делайте изменения по возможности обратимыми, чтобы вы могли быстро откатить их, если возникнут проблемы. Помните о техническом долге и погашайте его постепенно, прежде чем он начнёт накапливаться.

7. Пишите код в первую очередь для людей
Код читается гораздо чаще, чем пишется. Вы можете потратить час на написание функции, но десятки разработчиков могут прочитать её сотни раз за годы. Пишите код, с которым другим разработчикам будет приятно работать.
Поддерживаемость означает чёткое именование, простую логику, хорошую структуру и соответствующие комментарии. Выбирайте ясность, а не хитрость, описательное именование, а не поясняющие комментарии. Избегайте хитрых трюков, которые экономят две строки, но требуют пяти минут для понимания. Это означает думать о разработчике, который будет отлаживать это в 2 часа ночи, когда прод лёг.

8. Сложность губит проекты — сохраняйте простоту
Умная абстракция, которой вы так гордитесь, сложный паттерн проектирования, который вы реализовали… У сложности есть цена. Каждый уровень абстракции, каждое косвенное обращение, каждый «гибкий» дизайн делают систему сложнее для отладки и анализа. Предпочитайте скучные, простые решения, которые легко понять. Создавайте системы, которые можно объяснить на доске. Абстракция должна скрывать сложность, а не создавать её. Соблюдайте принцип наименьшего удивления: код должен вести себя так, как того ожидают от него другие разработчики.
Вообще, оверинжиниринг — часть пути разработчика. Нужно пройти через это, чтобы начать ценить простоту. Но как только вы это сделаете, вы никогда не захотите вернуться назад.

9. Устраняйте причины, а не симптомы
Когда вы обнаруживаете ошибку, сопротивляйтесь желанию быстро её исправить. Уделите время, чтобы понять, почему это произошло. Временные решения приводят к хрупким системам с нагромождением исправлений.
Найдите причину. Исправьте её должным образом. Да, это займет больше времени и может потребовать рефакторинга. Но вы предотвратите связанные ошибки и построите более надёжную систему. Исправление класса ошибок в корне гораздо эффективнее, чем многократное устранение отдельных симптомов. См. также: «Я исправил ошибку. Что дальше?»

10. Поймите «почему», прежде чем двигаться дальше
Когда что-то не работает или ведёт себя иначе, чем вы ожидали, не просто меняйте что-то, пока не заработает. Это заманчиво, но опасно. Вы получаете код, который не понимаете, потенциальные скрытые ошибки и упущенные возможности для обучения.
Убедитесь, что вы понимаете, почему код вёл себя именно так. Какое предположение было неверным? Что вы неправильно поняли о фреймворке, библиотеке или языке? В чём была реальная причина неожиданного поведения? Без понимания причин вы не развиваете свои навыки и не создаёте надёжное ПО.
Уделите время исследованию. Читайте документацию. Задавайте вопросы. Проводите эксперименты для проверки своих гипотез. Полученное понимание поможет избежать подобных проблем в будущем и углубит ваши знания используемых инструментов.

11. Не влюбляйтесь в свой код
Ваш код — не отражение вашей ценности как разработчика. Это инструмент для решения проблемы, и, если есть лучший инструмент или лучший способ, вы должны быть готовы отказаться от своего.
Эмоциональная привязанность к коду вынуждает вас защищаться во время код-ревью, сопротивляться изменениям и не замечать лучших решений. Хорошие инженеры регулярно удаляют свой код, рефакторят свои проекты и признают, когда их подход был не лучшим.

Продолжение следует…

Источник:
https://www.meziantou.net/essential-rules-of-software-engineering.htm
👍10👎1
День 2761. #Карьера
Основные Правила Разработки ПО. Продолжение

Начало

12. Прежде чем судить о чужом коде, стремитесь понять его
Глядя на чужой код, вы думаете, что всё бы сделали не так. Прежде чем судить, попытайтесь понять контекст. Какие были ограничения, требования, кодовая база? Часто то, что кажется плохим решением, обретает смысл, когда вы понимаете всю историю целиком. Поймите бизнес-логику кода. Технические решения обретают больше смысла, когда вы понимаете бизнес-проблемы, которые он решает.
Но, если код действительно проблемный, критикуйте код, а не разработчика. Делайте код-ревью с эмпатией, предлагайте улучшения уважительно. Ваш код тоже когда-нибудь станет для кого-то «унаследованным беспорядком».

13. Документируйте «почему» вы принимали решения
Через полгода вы уже не вспомните, почему выбрали этот подход, а не тот. Ваши коллеги тоже. Со временем дизайн и принятые решения теряют контекст.
Документируйте важные решения, особенно когда приходится выбирать между разумными альтернативами. Используйте ADR, комментарии или проектную документацию. Объясните контекст, рассмотренные варианты и почему вы выбрали именно этот. Не обязательно формально, просто чтобы освежить память позже.
Хорошая документация — это фиксация «почему» были приняты неочевидные решения, рассмотренные компромиссы и ограничения, с которыми вы работали.
Документация имеет мультипликативный эффект: она поможет коллегам, если они застрянут, ускоряет адаптацию и предотвращает повторение ошибок. Час, потраченный на документирование, может сэкономить команде десятки часов.

14. Пишите содержательные сообщения коммитов
История коммитов - это история проекта. Хорошие сообщения коммитов помогают понять, что изменилось и почему, что значительно упрощает отладку, проверку кода и адаптацию новых сотрудников. Опишите не только, что вы исправили, но и почему. Это предоставит контекст для будущих разработчиков.
Обеспечьте чёткую коммуникацию также в пул-реквестах, тикетах и документации. Делайте пул-реквесты небольшими и управляемыми, чтобы их было легче проверять, и чтобы снизить вероятность появления ошибок. Чёткая коммуникация — отличительная черта поведения опытного специалиста, и она приносит пользу всем членам команды.
ИИ сейчас может писать сообщения коммитов, анализируя изменения в коде, но всегда проверяйте и редактируйте их, чтобы обеспечить точность и ясность.

15. Автоматизируйте всё, что можно
Установите стандарты кодирования на раннем этапе, автоматизируйте их соблюдение с помощью форматеров и линтеров и двигайтесь дальше. Будьте последовательны в стандартах.
Автоматизируйте тестирование, развёртывание, сканирование безопасности, обновление зависимостей и любые повторяющиеся задачи. CI/CD — не опция, а основа надёжной доставки.
Создавайте конвейеры с защитой: автоматический откат и всестороннее тестирование, чтобы уменьшить радиус поражения потенциальных проблем. Забудьте о «это работает на моей машине». Если это работает в конвейере, только тогда это работает везде.

16. Код-ревью улучшает не только качество
Это один из лучших способов обмена знаниями внутри команды и улучшения как кода, так и людей, которые его пишут. Джуны изучают шаблоны и практики. Сеньоры остаются в курсе того, что происходит в разных частях кодовой базы. Все узнают о новых областях, в которых они раньше не работали. Уменьшается разрозненность знаний, и команда становится более устойчивой.

17. Никогда не прекращайте учиться и подвергать сомнению предположения
Технологии развиваются быстро. Фреймворки, языки и инструменты, которые вы знаете сегодня, через 5 лет изменятся. Непрерывное обучение — это обязательное условие.
Но это не только погоня за новыми технологиями; стремитесь к пониманию. Изучите основы: алгоритмы, структуры данных, сети, безопасность и принципы проектирования. Освойте смежные навыки: коммуникацию, наставничество, управление проектами.
Поддержка работающих производственных систем предоставляет лучшие возможности для обучения. Только преодолевая трудности вы будете расти. Сталкивайтесь со сложными проблемами, устраняйте неполадки в проде и учитесь на ограничениях реального мира.

18. Обращайтесь за помощью, когда застряли
Застрять на несколько часов, потому что стесняетесь попросить о помощи, — пустая трата времени. Коллеги хотят помочь. Они сталкивались с похожими проблемами. У них разные точки зрения, они могут сразу заметить то, что вы упускаете. Просьба о помощи — это сильная сторона, а не слабость.
Ключ к успеху — задавать правильные вопросы. Покажите, что вы уже пробовали. Объясните своё текущее понимание, предоставьте контекст. Это поможет им помочь вам.

19. Оценки никогда не бывают абсолютно точными
Оценка — сложная задача. Всегда есть неизвестные факторы, неожиданные сложности и задачи, которые оказались простыми лишь на бумаге. Относитесь к оценкам как к обоснованным предположениям, а не обязательствам. Учитывайте неопределённость в своих оценках. Сравнивайте свои оценки с фактическими результатами, чтобы со временем улучшать их. Сообщайте как можно раньше, если обнаружите, что оценка неверна. Чётко сообщайте о компромиссах .
Вместо: «Это займёт 3 дня», говорите: «Думаю, что это займёт 3-5 дней, при условии отсутствия проблем с интеграцией API стороннего сервиса, с которым я раньше не работал. Я сообщу вам, как начну работу, если обнаружу сложности».

20. Вы не можете управлять тем, за чем не наблюдаете
Производственным системам необходима наблюдаемость. Иначе вы действуете вслепую, когда что-то идёт не так, а это обязательно произойдёт. Ведите логи, но не чрезмерно. Добавляйте логи, метрики, трассировки и оповещения, которые имеют значение.
Проектируйте системы с учётом возможности наблюдения с самого начала. Инструментируйте свой код для отслеживания ключевых метрик, производительности, ошибок и поведения пользователей. Убедитесь, что вы можете ответить на вопросы: «Здорова» ли система? Где узкие места? Каков пользовательский опыт? Когда начались сбои?
Совершенство в работе системы — это разница между устранением проблем за минуты, а не за часы, и между знанием о проблемах до того, как о них узнают пользователи, а не из обращений в службу поддержки. Надёжность важнее новых функций. Всегда.

Окончание следует…

Источник:
https://www.meziantou.net/essential-rules-of-software-engineering.htm
👍5👎2
День 2762. #Карьера
Основные Правила Разработки ПО. Окончание

Начало
Продолжение

21. Никогда не доверяйте пользовательскому вводу
Пользователи допускают ошибки, а злоумышленники активно пытаются взломать вашу систему. Никогда не доверяйте пользовательскому вводу, будь то из форм, API, при загрузке файлов или в параметрах URL.
Проверяйте все входные данные: типы, форматы, диапазоны и допустимые значения. Очищайте данные, чтобы предотвратить атаки инъекцией. Используйте параметризованные запросы, кодирование и проверенные библиотеки безопасности, а не разрабатывайте собственные решения.

22. Ошибки должны приводить к громкому и немедленному сбою
Тихие сбои — кошмар отладки. Когда что-то идёт не так, сделайте это очевидным. Сообщайте об ошибках быстро, громко и предоставляйте чёткие сообщения об ошибках, которые помогут быстро выявить проблему.
Не игнорируйте исключения. Не возвращайте null при сбое. Не продолжайте работу, записав в лог. Когда не выполняется предусловие, данные невалидны, необходимый сервис недоступен, остановите выполнение и чётко сообщите о проблеме.
Хорошая обработка ошибок подразумевает быстрое выявление и устранение неполадок с чёткими сообщениями, предоставлением контекста о том, что пошло не так и почему, а также упрощением отслеживания источника ошибки. Это экономит часы отладки.

23. Сначала измерьте, потом оптимизируйте
Преждевременная оптимизация — корень всех зол. Разработчики часто тратят время на оптимизацию кода, который на самом деле не является узким местом, усложняя его без существенного повышения производительности.
Перед оптимизацией профилируйте приложение. Выявите реальные, а не предполагаемые узкие места. Вы можете обнаружить, что медленная часть вовсе не там, где вы думали. Оптимизируйте проблемные области и снова проведите измерения, чтобы проверить улучшение.
Не игнорируйте производительность полностью. Пишите достаточно эффективный код изначально, но не жертвуйте ясностью и удобством сопровождения ради микрооптимизаций. Знайте, что вы оптимизируете: задержку, пропускную способность, память или время разработки.

24. Меняйте по одной вещи за раз
При отладке или оптимизации сопротивляйтесь желанию изменять несколько вещей одновременно. Так вы не будете знать, какое именно изменение исправило ошибку или улучшило производительность. Внесите одно изменение, измерьте результат, а затем продолжайте. Такой дисциплинированный подход может показаться медленнее, но он экономит время, предоставляя чёткие причинно-следственные связи. Вы построите мысленную модель того, что действительно важно, и избежите разочарования от отмены целого ряда изменений, потому что одно из них сломало что-то другое.
Аналогично при рефакторинге изменяйте либо структуру, либо поведение, но не то и другое одновременно. При развертывании выпускайте по одной функции за раз, чтобы изолировать проблемы.

25. Создавайте API, которые легко использовать правильно и сложно неправильно
Хороший дизайн предотвращает ошибки до их возникновения. При проектировании функции, класса или интерфейса сервиса подумайте о том, как они будут использоваться и как их можно использовать неправильно.
Используйте типы для обеспечения ограничений (обязательность, допустимые диапазоны, разрешённые состояния). Сделайте недопустимые состояния непредставимыми. Используйте чёткие имена, указывающие на назначение и поведение. Предоставляйте хорошие значения по умолчанию. Сделайте распространённые случаи простыми, а сложные — возможными. Возвращайте содержательные ошибки, которые помогут разработчикам правильно использовать API.
Отдавайте предпочтение композиции перед наследованием. Композиция более гибкая и создаёт меньше проблем со связностью. Минимизируйте связь между компонентами и максимизируйте их согласованность. Каждый модуль должен иметь чёткое назначение.

26. Будьте хорошим человеком важнее технических навыков
Разработка — командный вид спорта. Техническое мастерство ничего не значит, если с вами трудно работать, вы неэффективно общаетесь или не поддерживаете своих коллег.
Будьте добры, терпеливы, делитесь заслугами, признавайте ошибки, помогайте другим расти, больше слушайте, уважайте разные точки зрения и стили работы. У каждого есть трудности, которых вы не видите. Ваше отношение и навыки сотрудничества важнее для вашей карьеры, чем любые технические навыки.
Поддерживайте коллег, когда у них возникают трудности. Отмечайте их успехи. Создавайте среду, где люди чувствуют себя комфортно, задавая вопросы, признавая, что чего-то не знают, или высказывая свои опасения. Это укрепляет доверие и побуждает людей сообщать о проблемах на ранних стадиях, а не скрывать их.

27. Думайте о последствиях, а не только о решениях
Большинство инженеров оптимизируют решения. Архитекторы оптимизируют последствия. Обе роли важны, но они решают разные классы проблем.
Сеньоры задаются вопросом: «Как построить это хорошо?» Архитекторы: «Что произойдёт, если мы ошибаемся?» Этот сдвиг в перспективе меняет то, что вы оптимизируете. Вместо того чтобы фокусироваться на локальной производительности или качестве реализации, минимизируйте возможный ущерб, оптимизируйте обратимость и долгосрочные эксплуатационные расходы.
Ваш прогресс как архитектора – это учитывание последствий необратимых решений, компромиссов в выборе инструментов, ограничений в выборе возможностей и побочных эффектов. Уменьшайте сложность не только внутри функции, но и в командах и организации в целом.
Архитектура — это не рисование диаграмм. Это выбор того, с какими последствиями вы готовы смириться, и принятие ответственности за этот выбор.

Итого
Это не строгие законы, а рекомендации, основанные на опыте, ошибках и извлечённых уроках. Возьмите то, что вам близко, адаптируйте к своему контексту и помните, что разработка ПО — это в равной степени код, люди и коммуникация.
Лучшие инженеры — не те, кто пишет самый умный код, а те, кто приносит пользу, эффективно сотрудничает, постоянно учится и делает лучше всех вокруг себя. Развивайте свои принципы, основываясь на собственном опыте. Каждый проект, каждая команда и каждое испытание учат вас чему-то новому.

Источник:
https://www.meziantou.net/essential-rules-of-software-engineering.htm
👍6👎1
День 2763.
DotNext 2026

В этом году в очередной раз поеду на конференцию DotNext. На этот раз простым слушателем. Она пройдёт в Москве 25 и 26 сентября. В этом году её сдвинули с обычных четверга-пятницы на пятницу-субботу, так что отпроситься с работы стало проще 😉

Сильно изменилась и программа (расписание, кстати, тут). Раньше основной фокус докладов был на практических нюансах разработки. Всегда можно было найти в расписании доклады по DDD, EF, перекладыванию JSON-чиков, асинхронной коммуникации в приложении или особенностях БД (чаще всего Postgres). Некоторые даже упрекали конференцию в некотором застое, мол, доклады почти одинаковые из года в год. Так вот, в этот раз это совсем не так. AI ворвался на DotNext с ноги, даже создали отдельный топик «Practical AI», и он второй по популярности из 5 топиков программы. Я, например, для себя отметил «Когда код пишут не только люди: как меняется разработка в эпоху agentic development lifecycle» от Дмитрия Иванова и «Как закрывать весь SDLC с помощью агента» от Павла Федотовского.

Но это не значит, что ИИ захватил всё.

Больше всего докладов в топике «Архитектура». Некоторые будут без записи, поэтому, наверное, их стоит посетить лично, а остальные посмотреть потом. Из интересного, на мой взгляд, тут мастер-класс «Распилим монолит» от Алексея Лосева, «Модульность без микросервисов в ASP.NET из коробки» от Станислава Выщепана (да, темы схожие, но тут «у кого чего болит» - у меня на работе «big pile of mud» в каком-то смысле, который надо распиливать) и доклад о DomainEvents в DDD от Дениса Цветцих.

Топик «Internals» вобрал в себя доклады про «кишочки». Там, например, можно послушать Марка Шевченко про «Размеченные объединения в C# 15» или Юрия Малича про сравнение производительности и подводные камни Native AOT и JIT. Я скорей всего схожу на доклад Николая Савенко «Как выбрать инструмент для трейсов в .NET?»

Топик «Best practices» никуда не делся. Здесь меня заинтересовали доклады «Очень быстрые интеграционные тесты на EF Core + PostgreSQL» Сергея Булавского и про практику применения появившихся в C# 14 Extension Members от Виктора Дзицкого.

Ну и наконец, пара докладов есть в топике «Расширяем горизонты». Тут можно отвлечься от технических деталей и послушать о том, реально ли сделать свою IDE или почему в космосе пока нет дата-центров.

В общем, посмотреть и послушать много чего есть (я перечислил от силы четверть). И в этом году докладов стало больше. Если раньше я иногда терялся, на какой доклад пойти, когда их было 3 параллельно, то теперь их стало 4 в каждом слоте! А даже если не понравится совсем ничего из предложенного, можно посетить стенды партнёров конференции и поучаствовать в конкурсах, прикупить (или выиграть) мерча, а возможно и найти новое место работы.

Так что приезжайте тоже, скучно не будет. Ну и, конечно, если встретите меня, подходите, рад буду со всеми пообщаться в офлайне.

PS: если решили купить билет, напишите в личку 😉
👍7
День 2764. #ЧтоНовенького #NET11
Вышли Превью 6 и 7 .NET 11
Посмотрим на самые интересные новинки в библиотеках.

1. Асинхронная валидация в DataAnnotations
System.ComponentModel.DataAnnotations теперь поддерживает асинхронную валидацию. Правило валидации, требующее операций ввода-вывода — поиск в базе данных, вызов удалённого API — теперь может выполняться без блокировки потока.
Способы выражения асинхронного правила:
- Наследовать от AsyncValidationAttribute и переопределить его метод IsValidAsync.
- Реализовать IAsyncValidatableObject в модели.
- Вызвать новые методы Validator.ValidateObjectAsync, TryValidateObjectAsync, ValidatePropertyAsync и ValidateValueAsync.

Microsoft.Extensions.Options получает соответствующую поддержку: конфигурация приложения может проверяться асинхронно, в том числе при запуске, с помощью нового IAsyncStartupValidator.

2. System.Text.Json сериализует типы-объединения
Сериализатор распознаёт объединение через новый тип контракта JsonTypeInfoKind.Union, читает и записывает активный вариант и поддерживает как сериализатор на основе рефлексии, так и генератор исходного кода. Новые API JsonUnionAttribute, JsonUnionCaseInfo и классификаторы типов (JsonTypeClassifier и JsonSerializerOptions.TypeClassifiers) позволяют настраивать способ обнаружения и именования вариантов.

3. Настройка трассировки действий с помощью правил
Новый API настройки трассировки в Microsoft.Extensions.Diagnostics позволяет включать и выключать трассировку действий с помощью правил вместо ручной настройки экземпляров ActivityListener. Вызовите AddTracing и укажите, какие источники действий, операции и слушатели следует включить или отключить. Правила также могут управляться из конфигурации, поэтому трассировку можно настраивать без повторного развёртывания:
builder.Services.AddTracing(trc =>
{
trc.EnableTracing(sourceName: "MyCompany.Orders");
trc.DisableTracing(sourceName: "MyCompany.Orders", operationName: "HealthCheck");
});

Также добавлена ActivitySourceFactory, а у класса ActivitySource убран модификатор sealed, что предоставляет фабричное создание и обновляемых слушателей.

4. Запуск приостановленных процессов и их поиск по идентификатору
System.Diagnostics.Process добавляет более тонкий контроль над запуском и поиском процессов:
- ProcessStartInfo.StartSuspended запускает процесс в приостановленном состоянии в Windows. Используйте его в паре с SafeProcessHandle.Resume, чтобы он продолжил работу после завершения какой-либо настройки, например подключения отладчика или настройки объектов заданий.
- Process.TryGetProcessById возвращает false вместо исключения, если процесса с заданным идентификатором не существует.
- SafeProcessHandle.Open и TryOpen открывают дескриптор существующего процесса по идентификатору.

5. Десятичные типы с плавающей запятой по IEEE 754
System.Numerics получает три десятичных типа с плавающей запятой IEEE 754-2019 — Decimal32, Decimal64 и Decimal128 — с точностью до 7, 16 и 34 знаков после запятой соответственно. В отличие от System.Decimal, они используют кодировку обмена двоичными целыми и десятичными числами IEEE (BID), и поддерживают специальные значения IEEE, такие как бесконечность и NaN. Они поддерживают обобщённые математические операции через IDecimalFloatingPointIeee754<TSelf>, поэтому обобщённые числовые алгоритмы могут использовать их наряду с существующими типами с плавающей запятой.
Типы включают в себя API для парсинга, преобразований, арифметических и сравнительных операторов, округления, умножения-сложения, квантования, а также двоичного и десятичного кодирования. Базовые арифметические операции проверяются с точностью до бита по тестовым векторам библиотеки Intel Decimal Floating-Point Math Library. Функции более высокого уровня, такие как трансцендентные числа, не являются точными в .NET 11, но планируется сделать их точными в будущей версии.

6. Частичный парсинг чисел
INumberBase<TSelf>.TryParsePartial добавляет универсальные перегрузки для парсинга чисел, которые сообщают, сколько входных данных было обработано. Это позволяет парсерам для таких форматов, как CSV, анализировать одно числовое поле и продолжать обработку оставшихся входных данных без их копирования или обработки разделителя как ошибки парсинга:
using System;
using System.Globalization;

ReadOnlySpan<char> input = "123; 456";
if (int.TryParsePartial(
input,
NumberStyles.Integer,
CultureInfo.InvariantCulture,
out int value,
out int charsConsumed))
{
Console.WriteLine(value); // 123
input = input[charsConsumed..];
Console.WriteLine(input); // ; 456
}


7. Сжатие HTTP-запросов
System.Net.Http получает три обёртки над HttpContent, которые сжимают тела запросов в сети — GZipCompressedContent, BrotliCompressedContent и ZstandardCompressedContent в пространстве имен System.Net.Http. Каждая обёртка устанавливает соответствующий заголовок Content-Encoding, очищает Content-Length и передаёт сжатые байты потоком по мере сериализации запроса. Для каждого алгоритма предоставляются два конструктора: один принимает CompressionLevel для быстрой настройки скорости/размера, а другой принимает полный тип параметров алгоритма (ZLibCompressionOptions, BrotliCompressionOptions или ZstandardCompressionOptions) для более точной настройки. Сжатие запросов является необязательным: используйте его только в том случае, если вы знаете, что сервер поддерживает выбранный кодировщик содержимого, поскольку HttpClient не согласовывает сжатие содержимого запроса автоматически.

8. Поддержка паролей для ZIP-архивов
System.IO.Compression теперь читает и записывает защищённые паролем ZIP-файлы. ZipArchiveEntry получает перегрузки Open и OpenAsync, принимающие пароль типа ReadOnlySpan<char>, ZipArchive.CreateEntry получает перегрузки, принимающие пароль и ZipEncryptionMethod (ZipCrypto, Aes128, Aes192, Aes256), а новое свойство ZipArchiveEntry.EncryptionMethod отображает алгоритм, используемый существующей записью.

9. Фабрики HybridCache с поддержкой параметров
Microsoft.Extensions.Caching.Hybrid.HybridCache добавляет перегрузки GetOrCreateAsync, чей фабричный метод обратного вызова получает изменяемый HybridCacheEntryContext в дополнение к состоянию и токену отмены. Теперь фабрика может настраивать параметры Expiration, LocalCacheExpiration, Flags и новое свойство LocalSize (HybridCacheEntryOptions.LocalSize, используемое для MemoryCache.Size) в зависимости от только что полученного значения — например, предоставляя более длительный срок жизни (TTL) для большого вычисленного результата.

Источники:
-
https://github.com/dotnet/core/blob/main/release-notes/11.0/preview/preview6/libraries.md
-
https://github.com/dotnet/core/blob/main/release-notes/11.0/preview/preview7/libraries.md
👍5
День 2765. #ЗаметкиНаПолях
Ограничьте Возможности NuGet-пакетов в Вашем Проекте

При добавлении NuGet-пакета в проект принято думать о нём только как о библиотеке времени выполнения. В действительности пакеты также могут импортировать свойства/таргеты MSBuild и анализаторы Roslyn. Эти ресурсы запускаются во время восстановления или сборки. Описание общих рисков безопасности и лучших практик можно найти тут.

Т.е. пакет может выполнять код на вашем компьютере или в CI без вашего ведома. Если вам нужна только часть ресурсов пакета, вы можете ограничить импорт, используя IncludeAssets и ExcludeAssets в PackageReference.

Почему это важно
Пакеты NuGet могут включать:
- файлы build, buildMultitargeting и buildTransitive (свойства и таргеты MSBuild);
- анализаторы (включая генераторы кода);
- библиотеки времени выполнения и компиляции.
Если вам не нужна логика сборки или анализаторы из пакета, их блокировка уменьшает поверхность атаки и делает сборки более предсказуемыми. Если вы читали последние новости об атаках на цепочки поставок, вы знаете, что любой код, работающий на вашей машине или в CI, представляет потенциальный риск. По умолчанию импортируются все ресурсы из пакета, поэтому важно помнить об этом и принимать меры при необходимости.

Включайте только то, что вам нужно
Наиболее безопасный подход часто заключается в разрешении только необходимых вам ресурсов:
<ItemGroup>
<PackageReference Include="Some.Package" Version="1.2.3">
<IncludeAssets>compile;runtime</IncludeAssets>
</PackageReference>
</ItemGroup>

При такой конфигурации NuGet не импортирует анализаторы и таргеты сборки из пакета.

Исключение определённых типов ресурсов
Если вы предпочитаете сохранить поведение по умолчанию и удалить только некоторые группы ресурсов, используйте ExcludeAssets:
<ItemGroup>
<PackageReference Include="Some.Package" Version="1.2.3">
<ExcludeAssets>analyzers;build;buildTransitive</ExcludeAssets>
</PackageReference>
</ItemGroup>

Это полезно, когда вам по-прежнему нужны другие ресурсы, такие как contentFiles.

Распространённые сценарии
Включать только ресурсы среды выполнения/библиотеки:
<PackageReference Include="Some.Package" Version="1.2.3">
<IncludeAssets>runtime;compile</IncludeAssets>
</PackageReference>


Включать библиотеку времени выполнения, отключить анализаторы:
<PackageReference Include="Some.Package" Version="1.2.3">
<ExcludeAssets>analyzers</ExcludeAssets>
</PackageReference>


Оставить анализаторы, отключить таргеты сборки:
<PackageReference Include="Some.Package" Version="1.2.3">
<ExcludeAssets>build;buildMultitargeting;buildTransitive</ExcludeAssets>
</PackageReference>


Использовать пакет только для сборки:
<PackageReference Include="Some.Package" Version="1.2.3" PrivateAssets="all">
<IncludeAssets>build;buildMultitargeting;buildTransitive</IncludeAssets>
</PackageReference>

Параметр "PrivateAssets="all" предотвращает транзитивное распространение зависимости на проекты, которые ссылаются на ваш проект.

Примечания и оговорки
- Для корректной работы некоторых пакетов требуются анализаторы или таргеты сборки. Тестируйте после изменения фильтров ресурсов.
- Предпочтительнее явно указывать и документировать выбор критически важных зависимостей в файлах .csproj.
- Сочетайте это с файлами блокировки пакетов и сопоставлением источников пакетов для более надёжной защиты цепочки поставок.

Источник:
https://www.meziantou.net/limit-what-nuget-packages-can-do-in-your-project.htm
👍3
День 2766. #ЧтоНовенького #NET11
Вышли Превью 6 и 7 .NET 11
Посмотрим на самые интересные новинки в ASP.NET Core.

1. Асинхронная проверка для минимальных API
Теперь минимальные API поддерживают асинхронные валидаторы. Самый простой способ добавить асинхронное правило — это пользовательский атрибут валидации. Наследуйте от AsyncValidationAttribute и реализуйте IsValidAsync, чтобы запрашивать БД или удаленный API без блокировки потока. Синхронный метод IsValid также является абстрактным; выбрасывайте исключение, если атрибут проверяется только асинхронно.
Для проверки, охватывающей несколько свойств или весь объект, реализуйте IAsyncValidatableObject и возвращайте результаты в виде IAsyncEnumerable<ValidationResult>. Поскольку IAsyncValidatableObject наследует IValidatableObject, также реализуйте синхронный метод Validate. Если тип проверяется только асинхронно, выбрасывайте исключение из Validate, чтобы его проверка не пропускалась молча синхронными API.
Валидаторы выполняются параллельно, где это возможно: асинхронные атрибуты одного и того же члена запускаются одновременно, элементы коллекции проверяются параллельно, и фреймворк сохраняет существующий порядок проверки: член, тип, IValidatableObject.

2. Автоматическая защита от межсайтовых запросов (CSRF)
Приложения, созданные с помощью WebApplication.CreateBuilder, теперь автоматически отклоняют небезопасные межсайтовые запросы на основе заголовков Sec-Fetch-Site и Origin браузера. Эта легковесная защита от CSRF включается без какой-либо настройки и применяется к минимальным API, MVC, Razor Pages и Blazor. Разрешаются запросы из одного источника, инициированные пользователем переходы и запросы от клиентов, не использующих браузер, в то время как межсайтовый запрос браузера, пытающийся обработать форму, отклоняется. Эту защиту можно использовать вместо или вместе с существующей системой защиты от подделки запросов на основе токенов. Поскольку она работает автоматически, проектам Blazor Web App больше не требуется вызывать app.UseAntiforgery().
Чтобы отключить защиту конечной точки, используйте метод .DisableAntiforgery() (для минимальных API) или [IgnoreAntiforgeryToken] (для MVC); чтобы отключить эту функцию для всего приложения, установите ключ конфигурации DisableCsrfProtection. Для полного контроля над решением о доверии зарегистрируйте собственную реализацию ICsrfProtection.

3. OpenAPI 3.2 по умолчанию
Сгенерированные документы OpenAPI теперь по умолчанию ориентированы на OpenAPI 3.2 – новейшую версию спецификации. Изменяется только версия по умолчанию; ваш код генерации и конфигурация остаются неизменными. Укажите версию OpenAPI явно, чтобы она была ориентирована на более раннюю версию для инструментов, которые ещё не используют 3.2.

4. Объединения в ASP.NET Core
Поскольку ASP.NET Core использует System.Text.Json для JSON, типы-объединения работают как тела JSON-запросов и возвращаемые типы без какой-либо специфической конфигурации:
- Минимальные API — параметры тела запроса и возвращаемые типы, включая Task<Union>, IAsyncEnumerable<Union> и Results<T1, T2>;
- MVC и Razor Pages — в телах JSON-запросов и ответах;
- SignalR — параметры методов хаба, возвращаемые значения и элементы потока при использовании протокола JSON Hub;
- Blazor — параметры компонентов, аргументы JavaScript-interop и результаты, а также сохраняемое состояния компонента.
Для OpenAPI конечная точка, возвращающая объединение, описывается схемой anyOf, в которой перечислены все типы вариантов. Сторонние генераторы, такие как Swashbuckle и NSwag, пока не распознают объединения и будут создавать свою схему по умолчанию.
В этом превью действуют некоторые ограничения. Поддерживаются только тела запросов и ответы в формате JSON. Привязка объединения к строке запроса, значениям маршрута, заголовкам или полям формы пока недоступна. Когда несколько вариантов сериализуются в один и тот же формат JSON, их можно различить с помощью классификатора [JsonUnion] (из System.Text.Json). Объединения в SignalR требуют протокола JSON Hub; Протоколы MessagePack и Newtonsoft.Json не поддерживают объединения.

5. Атрибут «прерыватель цепи» для конечных точек
Новый атрибут [ShortCircuit] помечает конечную точку для запуска сразу после маршрутизации, пропуская остальную часть конвейера промежуточного ПО. Это атрибутная форма существующего соглашения о конечных точках ShortCircuit(), поэтому его можно применять непосредственно к контроллерам и действиям MVC. Это полезно для конечных точек, которым не требуется аутентификация, CORS или другое промежуточное ПО, например, health-check или ответ robots.txt. Это позволяет избежать затрат на запуск этого промежуточного ПО. Конечная точка по-прежнему запускается и выдаёт свой ответ; можно передать необязательный код состояния, например [ShortCircuit(404)].

6. Встроенная локализация валидации
Microsoft.Extensions.Validation теперь локализует сообщения валидации и отображаемые имена. Пакет Microsoft.Extensions.Validation.Localization, доступный только в предварительной версии, и его API AddValidationLocalization<TResource>()/IValidationLocalizer удалены; локализация теперь активируется автоматически, как только регистрируется IStringLocalizerFactory, и поиск подходящей локализации инициируется генератором кода валидации в вашей сборке.
builder.Services.AddLocalization();
builder.Services.AddValidation();
…
[ValidatableType]
public class CustomerModel
{
// ключ ресурса для имени
[Display(Name = "CustomerName")]
// ключ ресурса для сообщения об ошибке
[Required(ErrorMessage = "NameRequired")]
public string? Name { get; set; }
}

Ключи определяются на основе собственных ресурсов модели, а в случае промаха используется встроенное сообщение атрибута. Атрибуты, которые уже локализуются (через ErrorMessageResourceType или [Display(ResourceType = …)]), полностью обходят конвейер локализации валидации. Пользовательский атрибут, которому необходимо подставить свои значения в шаблон сообщения, может реализовать интерфейс IValidationMessageFormatter.
К валидации минимальных API и Blazor применяются те же правила локализации, поэтому сообщение локализуется одинаково независимо от того, где используется модель.

Источники:
-
https://github.com/dotnet/core/blob/main/release-notes/11.0/preview/preview6/aspnetcore.md
-
https://github.com/dotnet/core/blob/main/release-notes/11.0/preview/preview7/aspnetcore.md
👍3
День 2767. #ЗаметкиНаПолях
TimeProvider — Конец Нетестируемого DateTime.Now. Начало

DateTime.Now — один из тех вызовов, который кажется безобидным, пока вы не попытаетесь написать тест вокруг него. Это статическое свойство, которое считывает системные часы, и нет способа контролировать то, что оно возвращает, без обёртывания его во что-то, что можно внедрить. Начиная с .NET 8, мы можем перестать переизобретать одну и ту же абстракцию и использовать вместо неё TimeProvider.

Как было раньше
Стандартным решением для установки определённого времени в коде всегда было определение интерфейса, который оборачивает часы, и внедрение его в качестве зависимости, которую можно заменить фиктивной реализацией в тестах, зависящих от даты/времени:
public interface IDateTimeProvider
{
DateTimeOffset UtcNow { get; }
}

public class SystemDateTimeProvider : IDateTimeProvider
{
public DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
}

В коде вы регистрируете SystemDateTimeProvider. В тестах — FakeDateTimeProvider с устанавливаемым свойством и передаёте любое время, необходимое тесту. Это работает и несложно, но каждая команда делает это немного по-своему: ISystemClock, IClock, IDateTimeProvider, ITimeService или сразу несколько вариантов в одной кодовой базе, потому что, а почему бы и нет? ASP.NET Core одно время поставлял свой ISystemClock в Microsoft.AspNetCore.Authentication.

Встроенное решение: TimeProvider
.NET 8 представил TimeProvider, абстрактный класс в пространстве имен System. Он является частью базовой библиотеки классов (поэтому зависимость NuGet не требуется). Идея та же: вместо прямого вызова DateTime.UtcNow вы вызываете tp.GetUtcNow(). В коде используется TimeProvider.System. В тестах используется фиктивный объект. Основные методы:
// Текущее время UTC (DateTimeOffset, а не DateTime)
DateTimeOffset utcNow = tp.GetUtcNow();

// Местное время
DateTimeOffset localNow = tp.GetLocalNow();

// Местный часовой пояс
TimeZoneInfo zone = tp.LocalTimeZone;

// Высокоточные метки времени
long start = tp.GetTimestamp();
// ... do work ...
TimeSpan elapsed = tp.GetElapsedTime(start);

Заметьте, что TimeProvider работает с DateTimeOffset (содержащим информацию о смещении часового пояса), а не с DateTime.

В коде используется TimeProvider.System как синглтон. Он считывает данные с системных часов, как и DateTimeOffset.UtcNow. Его необходимо зарегистрировать при запуске системы:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddSingleton<TimeProvider>(TimeProvider.System);

А затем можно внедрять везде, где вашему коду нужно текущее время:
public class SubscriptionReminderService
{
private TimeProvider _tp;
private ISubscriptionRepository _repo;

public SubscriptionReminderService(
TimeProvider tp,
ISubscriptionRepository repo)
{
_tp = tp;
_repo = repo;
}

public async Task<IEnumerable<Subscription>>
GetExpiringSubsAsync()
{
var now = _tp.GetUtcNow();
var cutoff = now.AddDays(7);

return await
_repo.GetExpiringSubsAsync(cutoff);
}
}

Теперь часы являются зависимостью, а зависимости можно заменять. Например, в тестах.

Окончание следует…

Источник:
https://blog.maartenballiauw.be/posts/2026-07-22-timeprovider-and-the-end-of-untestable-datetime-now/
День 2768. #ЗаметкиНаПолях
TimeProvider — Конец Нетестируемого DateTime.Now. Окончание

Начало

Тестирование с FakeTimeProvider
.NET предоставляет класс FakeTimeProvider в отдельном NuGet-пакете:
dotnet add package Microsoft.Extensions.TimeProvider.Testing


Вы можете задать начальную точку или оставить значение по умолчанию, если вас не интересует точное значение:
// Определённый момент времени
var fakeTime = new FakeTimeProvider(
new DateTimeOffset(2026, 8, 29, 0, 0, 0, TimeSpan.Zero));
// По умолчанию
var fakeTime = new FakeTimeProvider();


В предыдущем примере SubscriptionReminderService принимает TimeProvider (абстрактный базовый класс), куда также можно напрямую передать FakeTimeProvider:
using Microsoft.Extensions.Time.Testing;

[Fact]
public async Task GetExpiringSubs_ReturnsSubsWithin7Days()
{
var fakeTime = new FakeTimeProvider(
new DateTimeOffset(2026, 9, 1, 0, 0, 0, TimeSpan.Zero));

var repo = new InMemorySubscriptionRepository(new[]
{
new Subscription {
Id = 1,
Expires = new DateTimeOffset(2026, 9, 6, 0, 0, 0, TimeSpan.Zero) },
new Subscription {
Id = 2,
Expires = new DateTimeOffset(2026, 9, 8, 0, 0, 0, TimeSpan.Zero) },
});

var service =
new SubscriptionReminderService(fakeTime, repo);

var expiring = await service.GetExpiringSubsAsync();

Assert.Single(expiring);
Assert.Equal(1, expiring.First().Id);
}

1я подписка истекает 6 сентября (через 6 дней), вторая – 8 сентября (не входит в 7-дневное окно истекающих подписок). Тест детерминирован вне зависимости от того, когда он выполняется.

Перемотка времени вперед
Настоящая мощь проявляется в сценариях, связанных с течением времени. У класса FakeTimeProvider есть метод Advance(TimeSpan delta), который переводит часы вперёд на указанную величину:
[Fact]
public async Task GetExpiringSubs_AfterTimeAdvances_CapturesMoreSubscriptions()
{
var fakeTime = new FakeTimeProvider(
new DateTimeOffset(2026, 9, 1, 0, 0, 0, TimeSpan.Zero));

var repos = new InMemorySubscriptionRepository(new[]
{
new Subscription {
Id = 1,
Expires = new DateTimeOffset(2026, 9, 6, 0, 0, 0, TimeSpan.Zero) },
new Subscription {
Id = 2,
Expires = new DateTimeOffset(2026, 9, 8, 0, 0, 0, TimeSpan.Zero) },
});

var service =
new SubscriptionReminderService(fakeTime, repo);

// 1 сентября только 1я подписка в истекающих
var check1 = await service.GetExpiringSubsAsync();
Assert.Single(check1);

// 5 сентября - обе
fakeTime.Advance(TimeSpan.FromDays(5));
var check2 = await service.GetExpiringSubsAsync();
Assert.Equal(2, check2.Count());
}

Если нужно перейти к точному моменту времени, это сделает метод SetUtcNow(DateTimeOffset utcNow). Оба метода также запускают любые ожидающие таймеры, время срабатывания которых попадает в диапазон перемотки (подробности ниже).

Эти методы особенно полезны для тестирования логики, которая накапливает состояние с течением времени: истечение срока действия кэша, задержка повторной попытки, вычисления с использованием скользящего окна. Вы точно контролируете, когда наступит «сейчас», без фактического ожидания.

Таймеры
TimeProvider также предоставляет метод CreateTimer(), который возвращает ITimer из System.Threading:
ITimer timer = tp.CreateTimer(
callback: state => DoSomeWork(),
state: null,
dueTime: TimeSpan.FromMinutes(5),
period: TimeSpan.FromMinutes(5));

Он идентичен System.Threading.Timer (те же параметры, тот же метод Change(TimeSpan dueTime, TimeSpan period) для перепланирования), но основан на абстракции. В FakeTimeProvider вызов Advance(), приводящий к истечению времени таймера, запустит обратный вызов таймера (в примере выше – DoSomeWork()). Не нужно использовать Thread.Sleep или Task.Delay.
Это важно, если у вас есть фоновые сервисы или рабочие процессы, которые выполняют периодическую работу с помощью таймеров. Как только они принимают TimeProvider, весь цикл планирования становится тестируемым.

Источник:
https://blog.maartenballiauw.be/posts/2026-07-22-timeprovider-and-the-end-of-untestable-datetime-now/
👍6
День 2769. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.

44. Распределённая трассировка
«Как бы вы реализовали распределённую трассировку для мониторинга и отладки взаимодействия микросервисов? Приведите примеры инструментов и методологий, которые вы бы использовали».

Хороший ответ
Реализация распределённой трассировки включает в себя интеграцию инструментов, которые могут отслеживать ход выполнения и производительность запросов по мере их прохождения через микросервисы. Вот как можно её настроить.

1. Выбрать систему распределённой трассировки, совместимую с .NET, например, OpenTelemetry, которая стала стандартом для обеспечения наблюдаемости в облачных приложениях. Она поддерживает трассировку, метрики и журналы.
2. Добавить необходимые пакеты OpenTelemetry в проект.
3. Настроить трассировку в Program.cs, убедившись, что она захватывает как входящие, так и исходящие запросы для всестороннего отслеживания взаимодействия между микросервисами:
var builder = WebApplication.CreateBuilder(args);

// Добавляем OpenTelemetry
builder.Services.AddOpenTelemetryTracing(trc =>
{
trc.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter(opts => {
opts.Endpoint =
new Uri("http://your-tracing-backend:4317");
});
});

var app = builder.Build();

app.MapGet("/api/data", async (HttpClient client) =>
{
// Пример исходящего запроса
var response = await client.GetStringAsync(
"http://another-service/api/data");

return Results.Ok(response);
});

app.Run();

4. Убедиться, что все части приложения, включая внешние библиотеки и зависимости, настроены на отправку трассировок. Это может включать настройку дополнительных инструментов для баз данных или очередей сообщений, если они не охватываются автоматически.
5. Использовать соответствующие заголовки для распространения контекстов трассировки по HTTP-запросам для поддержки распределённой трассировки через границы. Обычно это обрабатывается библиотеками OpenTelemetry, но следует проверить полноту их работы.
6. Использовать инструменты, вроде Jaeger, Zipkin или коммерческие решения, такие как Dynatrace или DataDog, которые могут визуализировать данные трассировки и помогать в отладке и мониторинге производительности.
7. Убедиться, что бэкенд трассировки настроен на обработку объёма данных трассировки, генерируемых нашими сервисами, и может масштабироваться по мере необходимости.

Преимущества
- Улучшенная отладка: Быстрое определение, какая часть взаимодействия сервисов завершилась с ошибкой или вызывала задержки.
- Оптимизация производительности: Анализ данных трассировки для оптимизации производительности отдельных микросервисов и системы в целом.
- Улучшенная наблюдаемость: представление о поведении приложений и о том, как сервисы взаимодействуют в производственной среде.

Часто встречающийся плохой ответ
«Я бы просто логировал запросы в каждом сервисе. Затем можно сопоставить эти логи и отследить, как обрабатываются запросы в сервисах.»

Почему это неправильно
- Отсутствие масштабируемости и эффективности: Использование журналов для ручной трассировки запросов не масштабируемо и может быть крайне неэффективным, особенно в сложных или высоконагруженных средах.

- Отсутствие полноты: Журналы не предоставляют структурированной, коррелированной информации, которую предлагают специализированные инструменты трассировки. Они могут упускать важные детали взаимодействия или не содержать контекста, необходимого для эффективной отладки.

- Ресурсоёмкость: Зависимость исключительно от журналов при трассировке может привести к значительным накладным расходам как на хранение, так и на обработку, влияя на производительность системы.

Эта ошибка обычно возникает из-за недостаточного понимания специализированных инструментов и методов распределённой трассировки или из-за недооценки сложности, связанной с ручной трассировкой распределённых транзакций. Это подчёркивает необходимость интегрированных автоматизированных решений для трассировки в современных архитектурах приложений.

Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👍4
День 2770. #BestPractices
Лучшие Практики Безопасности REST API в
ASP.NET Core. Начало
Неважно, насколько чиста ваша архитектура или насколько быстро выполняются запросы — если злоумышленник может прочитать заказы другого пользователя или подделать токен, всё это не имеет значения. Большинство проблем с безопасностью API возникают из-за игнорирования основ:
- устаревший NuGet-пакет с уязвимостью,
- отсутствие валидации,
- слабая настройка аутентификации,
- слишком либеральная политика CORS,
- секрет, внесённый в систему контроля версий.
Хорошая новость в том, что ASP.NET Core из коробки предоставляет почти всё необходимое для устранения проблем.

1. HTTPS везде
Каждый запрос к API должен передаваться по зашифрованному соединению. Без HTTPS токены, пароли и личные данные передаются в открытом виде, и любой, кто находится на пути следования по сети, может их прочитать. Обычный HTTP также открывает двери для атак с понижением уровня безопасности, когда злоумышленник заставляет клиента использовать небезопасное соединение. ASP.NET Core предоставляет два инструмента. UseHttpsRedirection перенаправляет HTTP-запросы на HTTPS, а HSTS (HTTP Strict Transport Security) указывает браузерам подключаться только по HTTPS. Совместимые браузеры вообще отказываются взаимодействовать с вашим API по HTTP, что блокирует атаки с понижением уровня безопасности:
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHsts(opts =>
{
opts.MaxAge = TimeSpan.FromDays(365);
opts.IncludeSubDomains = true;
opts.Preload = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
app.UseHsts();

app.UseHttpsRedirection();
// …

HSTS пропускается в среде разработки, поскольку там часто используется http://localhost.

2. Аутентификация с помощью токенов, а не сессий
Предпочтительнее использовать аутентификацию с помощью токенов без сохранения состояния, чем серверные сессии. Сессия хранит состояние аутентификации в памяти сервера или в общем хранилище, что привязывает каждого пользователя к серверу и затрудняет горизонтальное масштабирование. Токен содержит подтверждение личности, поэтому любой экземпляр вашего API может проверить его без поиска пользователя в базе. Для большинства API подойдёт токены JWT bearer, часто выдаваемые поставщиком идентификации через OAuth 2.0 или OpenID Connect:
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opts =>
{
opts.Authority = "https://my-idp.com";
opts.Audience = "my-api";
});

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

Клиент отправляет токен в заголовке Authorization: Bearer <token> в каждом запросе. Ваш API проверяет его и считывает идентификационные данные пользователя из утверждений токена — никакого хранилища сессий, никаких «липких» сессий, никакого масштабируемого состояния на стороне сервера. Это делает ваш API «не сохраняющим состояние» и упрощает горизонтальное масштабирование.
См. также про референтные токены и отзыв токенов.

3. Проверка подписи JWT, издателя, аудитории и срока действия
Токен безопасен только после проверки того, кто его выпустил, для кого он предназначен, что он не истёк и что его подпись действительна. Настройте эти проверки явно через TokenValidationParameters, а не доверяйте значениям по умолчанию фреймворка:
.AddJwtBearer(opts =>
{
opts.TokenValidationParameters =
new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "https://my-idp.com",

ValidateAudience = true,
ValidAudience = "my-api",

ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),

ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(30)
};
});

Каждый флаг закрывает лазейку:
- ValidateIssuerSigningKey подтверждает подпись — никогда не отключайте этот параметр, иначе кто угодно сможет подделать токен;
- ValidateIssuer и ValidateAudience гарантируют, что токен получен от вашего поставщика идентификации и предназначен для вашего API, а не для какого-то другого сервиса;
- ValidateLifetime отклоняет просроченные токены.
- ClockSkew - существует для того, чтобы допускать небольшие расхождения во времени между серверами, но его значение по умолчанию составляет целых 5 минут, поэтому просроченный токен может приниматься ещё до 5 минут. Сокращение параметра до 30 секунд или даже до 0 ужесточает контроль за истечением срока действия токенов, при условии синхронизации часов ваших серверов (с помощью NTP).

Продолжение следует…

Источник:
https://antondevtips.com/blog/rest-api-security-best-practices-in-aspnetcore
👍10