.NET Разработчик
6.51K subscribers
427 photos
2 videos
14 files
2.04K links
Дневник сертифицированного .NET разработчика.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 1901. #ЧтоНовенького #Csharp13
Простая Обработка Асинхронных Задач по Мере их Завершения в .NET 9

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

Например, симулируем некоторую задачу Calculate, которая выдаёт результат через случайное (от 0,5 до 5 секунд) время:
async Task<int> Calculate(int order)
{
var wait = Random.Shared.Next(500, 5000);
await Task.Delay(wait);
return order;
}

И пусть у нас есть несколько таких задач:
var tasks = Enumerable.Range(1,10)
.Select(Calculate).ToList();

Если мы будем ожидать окончания всех задач, и выводить их результаты, то получим результаты в порядке очереди (от 1 до 10) вне зависимости от того, когда завершилась каждая задача, потому что мы сначала ждём завершения всех:
var results = await Task.WhenAll(tasks);
foreach (var r in results)
Console.WriteLine(r);

Более эффективно было бы ожидать завершения каждой задачи и обрабатывать её результат сразу. До сих пор существовал некоторый обходной путь, позволяющий это сделать. Примерно так:
while (tasks.Any())
{
var finished = await Task.WhenAny(tasks);
tasks.Remove(finished);
Console.WriteLine(await finished);
}

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

В .NET 9 появился новый метод, позволяющий упростить эту логику, Tasks.WhenEach, который выдаёт IAsyncEnumerable. Тогда код выше сокращается до:
await foreach (var finished 
in Task.WhenEach(tasks))
{
Console.WriteLine(await finished);
}

Теперь вы избавлены от необходимости иметь список задач в List’е (метод принимает Task<T>[], ReadOnlySpan<Task<T>>, IEnumerable<Task<T>>. Кроме того, он использует новые возможности ключевого слова params.

Аналогичная функциональность уже существует в библиотеке AsyncEx Стивена Клири. Там это метод OrderByCompletion. Поэтому можно эмулировать метод WhenEach так:
async IAsyncEnumerable<T> WhenEach(Task<T>[] tasks) {
foreach (Task<T> task in tasks.OrderByCompletion())
yield return await task;
}
}

await foreach (var task in WhenEach(tasks))
{
Console.WriteLine(await finished);
}


Источники:
-
https://youtu.be/WqXgl8EZzcs
-
https://github.com/dotnet/runtime/issues/61959
-
https://devblogs.microsoft.com/pfxteam/processing-tasks-as-they-complete/
👍40
День 1902. #ЗаметкиНаПолях
8 Способов Задать URL в Приложении
ASP.NET Core. Начало
По умолчанию приложения ASP.NET Core в .NET 8 прослушивают URL-адрес https://localhost:5000. Рассмотрим 8 различных способов изменить его.

Существует три класса URL, которые вы можете привязать:
- Адрес обратной связи (loopback) для IPv4 и IPv6 (например, https://localhost:5000, https://127.0.0.1:5000 или https://[::1]:5000) в формате: {scheme}://{loopbackAddress}:{port}
- Конкретный IP, доступный на вашем компьютере (например, https://192.168.8.31:5005), в формате {scheme}://{IPAddress}:{port}.
- «Любой» IP для заданного порта (например, https://*:6264) в формате {scheme}://*:{port}
- В .NET 8, помимо TCP, вы также можете прослушивать запросы по именованным каналам и сокетам Unix, но это тема для отдельного поста.

Порт является необязательным. Если его не указывать, будет использован порт по умолчанию (80 или 8080 для http и 443 для https).

Выбор варианта будет зависеть от механизма развёртывания. Если у вас несколько приложений на машине, может потребоваться указать явный IP. Если одно в контейнере, то вы можете использовать «любой» адрес, но обычно используют localhost.

Замечание: В формате «любой» IP не обязательно использовать *. Подойдёт всё, что не является IP или localhost: https://*, https://+, https://mydomain или https://example.org. Все варианты прослушивают любой IP-адрес. Если вы хотите обрабатывать запросы только от определённого имени хоста, необходимо дополнительно настроить фильтрацию хостов.

После выбора подходящего варианта, необходимо установить URL, к которым привязывается ASP.NET Core при запуске. Есть следующие способы:
1. UseUrls() в Program.cs.
2. WebApplication.Urls и WebApplication.Run() в Program.cs.
3. Аргументы командной строки: параметр --urls.
4. Переменные окружения для URL: DOTNET_URLS или ASPNETCORE_URLS.
5. Переменные окружения для портов: DOTNET_HTTP_PORTS или ASPNETCORE_HTTP_PORTS (и аналогично для HTTPS).
6. В appSettings.json: свойство urls.
7. В launchSettings.json: свойство applicationUrl.
8. В KestrelServerOptions.Listen() либо в IConfiguration, используя свойство Kestrel.

Далее рассмотрим их подробно.

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

Источник:
https://andrewlock.net/8-ways-to-set-the-urls-for-an-aspnetcore-app/
👍14
День 1903. #ЗаметкиНаПолях
8 Способов Задать URL в Приложении
ASP.NET Core. Продолжение
Начало

1. UseUrls()
Первый и самый простой вариант установки URL — жёстко запрограммировать их при настройке WebApplicationBuilder с помощью UseUrls():
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.UseUrls(
"https://localhost:5003",
"https://localhost:5004");

var app = builder.Build();

app.Run();


2. WebApplication.Urls и WebApplication.Run()
WebApplication предоставляет свойство Urls:

var app = builder.Build();

app.Urls.Add("https://*:5003");
app.Run();

Аналогичного эффекта можно достичь, передав URL в app.Run():
app.Run("https://*:5003");

Однако в app.Run() вы можете предоставить только один URL.

Жёсткое кодирование URL не особенно чистое или расширяемое решение, поэтому оно используется редко. Чаще URL задают через конфигурацию приложения.

ASP.NET Core имеет расширяемую систему конфигурации, которая может загружать значения из нескольких источников конфигурации. По умолчанию ConfigurationManager, используемый в .NET 8, загружает значения из следующих мест:
- файл appsettings.json,
- необязательный файл для конкретной среды: appsettings.ENVIRONMENT.json,
- пользовательские секреты,
- переменные среды,
- аргументы командной строки
Вы можете добавить дополнительных поставщиков для загрузки из других мест. Вы можете использовать эту систему для загрузки настроек приложения, но ASP.NET Core также использует её для настройки своих внутренних компонентов.

3. Аргументы командной строки
Аргументы командной строки по умолчанию переопределяют значения всех других параметров конфигурации. Передайте переменную в качестве аргумента при запуске приложения, добавив префикс --:
dotnet run -- --urls "https://localhost:5100"

Можно передать несколько значений, разделённых точкой с запятой:
dotnet run -- --urls "https://*:5100;https://localhost:5101"

Это также работает и в среде выполнения dotnet, где вы передаёте путь к скомпилированной dll в dotnet вместо использования dotnet run:
dotnet ./WebApplication1.dll --urls "https://*:5100;https://localhost:5101"

Аналогично можно задать порты --http_ports и --https_ports:
dotnet run -- --http_ports "5100;5101" --https_ports "5003;5004"

Заметьте, что лишние -- в команде dotnet run не являются опечаткой; они необходимы для того, чтобы аргументы определённо интерпретировались как аргументы для конфигурации, а не как аргументы самой команды run.

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

Источник:
https://andrewlock.net/8-ways-to-set-the-urls-for-an-aspnetcore-app/
👍19
День 1904. #ЗаметкиНаПолях
8 Способов Задать URL в Приложении
ASP.NET Core. Продолжение
Начало
Способы 1-3

4. Переменные окружения
ASP.NET Core добавляет в систему конфигурации всё нижеследующее:
- Все переменные окружения.
- Переменные с префиксом DOTNET_ (без префикса).
- В приложениях ASP.NET Core (кроме сервисов, созданных через HostApplicationBuilder) переменные с префиксом ASPNETCORE_ (без префикса).
Таким образом, можно задать любой вариант переменной окружения для URL: URLS, ASPNETCORE_URLS или DOTNET_URLS.

Вы можете установить переменные окружения обычным способом в зависимости от вашей среды:
setx ASPNETCORE_URLS "https://localhost:5001"

PowerShell:
$Env:ASPNETCORE_URLS="https://localhost:5001"

Bash:
export ASPNETCORE_URLS="https://localhost:5001;https://localhost:5002"

Можно передавать несколько адресов (для HTTP и HTTPS), разделяя их точкой с запятой.

5. Переменные окружения для портов
В .NET 8 добавлены новые ключи конфигурации. Вместо указания URL вы указываете HTTP_PORTS и HTTPS_PORTS, и они используются для привязки любого IP-адреса. Можно указать несколько портов, используя точку с запятой. Например, следующие переменные окружения:
HTTP_PORTS=5001;5002
HTTPS_PORTS=5003

Соответствуют привязке следующих URL:
https://*:5001
https://*:5002
https://*:5003

Точно так же можно использовать префиксы ASPNETCORE_ и DOTNET_.

Замечание: если вы добавите значения и для PORTS, и для URLS, URLS будет иметь приоритет, и будет записано предупреждение:
warn: Microsoft.AspNetCore.Hosting.Diagnostics[15]
Overriding HTTP_PORTS '5002' and HTTPS_PORTS ''. Binding to values defined by URLS instead 'https://*:5003'.


В образах докера с .NET 8 переменная ASPNETCORE_HTTP_PORTS по умолчанию установлена в 8080 (в предыдущих версиях была ASPNETCORE_URLS).

6. appsettings.json
appsettings.json и файлы appsettings.<Environment>.json, специфичные для среды, включены практически в каждый шаблон приложения .NET и предоставляют простой способ добавления значений в систему конфигурации ASP.NET Core.

Используем те же самые ключи urls:
{
"urls": "https://*:5003",

}

или http_ports и https_ports:
{
"http_ports": "5001;5002",
"https_ports": "5003",

}

Если вы хотите использовать разные порты при разработке и в производстве, можно использовать appsettings.json в производстве и appsettings.Development.json для разработки.

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

Источник:
https://andrewlock.net/8-ways-to-set-the-urls-for-an-aspnetcore-app/
👍7
День 1905. #ЗаметкиНаПолях
8 Способов Задать URL в Приложении
ASP.NET Core. Продолжение
Начало
Способы 1-3
Способы 4-6

7. launchSettings.json
Помимо файла appsettings.json, большинство шаблонов проектов .NET также включают файл launchSettings.json в папке Properties. Этот файл не добавляет значений в конфигурацию, а содержит различные профили для запуска разрабатываемого приложения в dotnet run. Он управляет выпадающим списком отладки в Visual Studio.

Типичный файл содержит определения для запуска профиля из командной строки и из IIS Express:
{

"iisSettings": {

"iisExpress": {
// URL для профиля IIS Express
"applicationUrl": "https://localhost:49412",
"sslPort": 44381
}
},
"profiles": {
// профиль только HTTP
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:5005",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
// профиль HTTP и HTTPS
"https": {
// аналогично "http"

},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

Как видите, launchSettings.json также предоставляет простой способ установки дополнительных переменных окружения в environmentVariables.
Когда вы запускаете приложение из командной строки с помощью dotnet run, оно будет использовать свойства applicationUrl из команды Project: https://localhost:5005 в профиле http выше. В IISExpress - будет использовать applicationUrl из узла iisSettings.iisExpress: https://localhost:49412.

Этот файл — самый простой способ настроить среду при локальной разработке. Чтобы запустить приложение без использования launchSettings.json, нужно добавить параметр:
dotnet run --no-launch-profile

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

Источник:
https://andrewlock.net/8-ways-to-set-the-urls-for-an-aspnetcore-app/
👍11
День 1906. #ЗаметкиНаПолях
8 Способов Задать URL в Приложении
ASP.NET Core. Окончание
Начало
Способы 1-3
Способы 4-6
Способ 7

8. KestrelServerOptions.Listen()
Kestrel настроен по умолчанию в большинстве приложений ASP.NET Core. При желании вы можете настроить конечные точки для Kestrel, если вам требуется тонкая настройка, например, сертификатов HTTPS, протоколов SSL/TLS и комбинаций шифров, а также конфигураций SNI. Доступно множество вариантов конфигурации (см. документацию).

Например, вы можете использовать функции Listen(), предоставляемые KestrelServerOptions, следующим образом:
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(opts =>
{
opts.Listen(IPAddress.Loopback, port: 5002);
opts.ListenAnyIP(5003);
opts.ListenLocalhost(5004,
listenOptions => listenOptions.UseHttps());
opts.ListenAnyIP(5005,
listenOptions =>
{
listenOptions.UseHttps("testCert.pfx", "testPassword");
});
});

var app = builder.Build();

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

Но и конфигурацию Kestrel можно привязать с помощью IConfiguration, аналогично urls или http_ports. Например, приведенную выше конфигурацию Kestrel можно настроить в appsettings.json:
{
"Kestrel": {
"Endpoints": {
"HttpLoopback": {
"Url": "https://localhost:5002"
},
"HttpAny": {
"Url": "https://*:5003"
},
"HttpsDefaultCert": {
"Url": "https://localhost:5004"
},
"HttpsInlineCertFile": {
"Url": "https://*:5005",
"Certificate": {
"Path": "testCert.pfx",
"Password": "testPassword"
}
}
}
}
}

Это позволяет полностью настроить привязку вашего приложения и конфигурацию сертификата HTTPS из IConfiguration. Т.е. вы можете воспользоваться безопасным секретным хранилищем, например Key Vault, для хранения сертификатов и паролей сертификатов.
Если вы используете конфигурацию appsettings.json, указанную выше, не нужно вызывать ConfigureKestrel().

Источник: https://andrewlock.net/8-ways-to-set-the-urls-for-an-aspnetcore-app/
👍7
День 1907. #Карьера
Просить о Помощи — Основной Навык Разработчика

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

Как мы учим детей становиться взрослыми? Мы пытаемся помочь им стать теми, кем, по нашему мнению, они могут быть. Не нужно специально подталкивать их становиться инженерами, учёными или врачами. Задача родителя - научить их быть вдумчивыми, организованными и, конечно же, уметь просить о помощи.

Индивидуалисты часто теряют понимание, что мы не можем существовать сами по себе. Мне потребовалось много времени, чтобы усвоить это. Я хотел всего добиваться сам. Мне не приходило в голову, что я добьюсь большего, если другие люди помогут мне.

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

Каждый раз, когда вы застреваете, вы на самом деле карабкаетесь вверх по кривой обучения. Для тех, кто учится, есть утешение в осознании того, что при достаточной настойчивости, если они просто продолжат идти вперёд, в итоге они это преодолеют. Так учился я. Иногда это занимает много времени, но меня это устраивает.

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

Иногда вы получаете задачу, которая не соответствует вашим ограничениям: времени, ресурсам, часто знаниям и опыту. Когда вы берётесь за проблему, вы не знаете, каким будет решение. Вы тыкаетесь туда и сюда, находите её края и заполняете пробелы, пока не получите чёткое представление, на какую головоломку вы смотрите. Чем меньше у вас опыта в конкретной области, тем дольше это займёт. Такова природа обучения по ходу дела, и так мы развиваемся.

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

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

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

Источник: https://www.ramijames.com/thoughts/asking-for-help-is-a-core-skill
👍33
День 1908. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 5. Разработка требований — итеративный процесс
Тщательное изучение требований важно перед тем, как начнётся написание кода. Но в процессе разработки ПО для других людей часто могут возникать вопросы и обнаруживаться пробелы в знаниях. Приходится возвращаться к клиентам, чтобы прояснить задачи, освежить информацию в памяти и закрыть пробелы. Клиенты не всегда рады видеть вас снова, но разработка требований — итеративный и поэтапный процесс.

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

Эффективная разработка требований подразумевает постепенное уточнение требований и их деталей. Получите достаточно точную информацию о требованиях, прежде чем создавать какую-либо часть продукта, иначе придётся создавать её заново. Вот примерный процесс:
1. Разработайте предварительный список пользовательских требований. Узнайте достаточное количество подробностей о каждом из них, чтобы понять их объём и относительную важность.
2. Распределите требования по циклам разработки в зависимости от их приоритета.
3. Продолжите выявлять и уточнять детали тех требований, реализация которых запланирована в ближайшем цикле разработки.
4. Перераспределите приоритеты, добавляя любые новые требования, и двигайтесь вниз по списку приоритетов по мере разработки.
5. Вернитесь к шагу 2 и повторите.

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

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

Мы можем использовать разные методы для выявления некоторых возникающих требований, например, создать несколько представлений требований.
1) Вместо записи сценариев использования и историй нарисуйте несколько картинок. Визуальные модели описывают требования на более высоком уровне абстракции, позволяют отвлечься от деталей и увидеть более широкую картину рабочего процесса и взаимосвязей.
2) Тесты на ранней стадии помогут обнаружить неясности и ошибки в требованиях, либо отсутствующие требования, например необработанные исключения. Можно заметить, что некоторые требования не нужны, если нельзя придумать тесты, требующие их реализации. Это легло в основу разработки через тестирование.
3) Благодаря прототипам пользователи получают нечто осязаемое. Инкрементное прототипирование ускоряет обсуждение требований и помогает пользователям находить ошибки и упущения в требованиях до того, как на создание продукта будет затрачено слишком много усилий.

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

Итого
Формирование полезного набора требований любого рода требует терпения для переосмысления, повторных попыток и накопления знаний, необходимых для создания правильного продукта.

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
👍10
Паттерн Репозиторий отделяет код приложения от кода ...
#Quiz #DesignPatterns
Anonymous Quiz
93%
доступа к данным
1%
авторизации
5%
логики представления
1%
ведения жунрала
👍1
День 1909. #ЧтоНовенького
OpenTelemetry SDK от Elastic с Открытым Кодом для .NET
Elastic объявили об выпуске альфа-версии пакета OpenTelemetry SDK для .NET. Он настраивает сбор трассировки, метрик и логов, а также гарантирует, что экспортёр OTLP включен по умолчанию. Проект имеет открытый исходный код.

Чтобы начать работу с Elastic OpenTelemetry, необходимо добавить в проект ссылку на пакет NuGet Elastic OpenTelemetry:
<PackageReference Include="Elastic.OpenTelemetry" Version="1.0.0-alpha.1" />

Пакет добавляет транзитивную зависимость от OpenTelemetry SDK. Поэтому нет необходимости добавлять в проект OpenTelemetry SDK.

Чтобы воспользоваться преимуществами инструментария OpenTelemetry SDK для ASP.NET Core, разработчикам также следует добавить пакет NuGet OpenTelemetry.Instrumentation.AspNetCore. Он включает поддержку сбора инструментов (трассировок и метрик) для запросов, обрабатываемых конечными точками ASP.NET Core. Пакет SDK OpenTelemetry содержит методы расширения IServiceCollection для включения и настройки поставщиков трассировки, метрик и логов. Elastic переопределяет регистрацию SDK по умолчанию.

Как минимум необходимо настроить две переменные окружения: OTEL_EXPORTER_OTLP_ENDPOINT и OTEL_EXPORTER_OTLP_HEADERS. Elastic автоматически позволит экспортировать сигналы телеметрии с помощью экспортёра OTLP. Для экспортёра OTLP требуется настроить хотя бы одну конечную точку.

Текущая альфа-версия дистрибутива ограничена в возможностях. Elastic сообщает, что их цель — оценить пригодность дизайна API и простоту использования. Однако они подчёркивают, что он подходит не для всех сценариев применения, поэтому не рекомендуют его использовать в производстве.

Код полностью открыт и доступен на GitHub.

Источник: https://www.infoq.com/news/2024/04/elastics-open-telemetry-net/
👍13
День 1910. #ВопросыНаСобеседовании #ASP.NET #Architecture
Самые часто задаваемые вопросы на собеседовании по C#

31. Как бы вы подошли к обработке ошибок и отладке в распределённом приложении .NET Core с несколькими микросервисами?

Обработка ошибок и отладка в таком приложении может оказаться сложной задачей. Можно использовать несколько стратегий:

1. Централизованные логи.
Все микросервисы должны отправлять свои логи (естественно, структурированные) в централизованное место, где они сопоставляются и индексируются. Это позволит искать и визуализировать логи всех сервисов в одном месте.

2. Использование идентификаторов корреляции.
Идентификаторы корреляции (CorrelationId) — это уникальные идентификаторы, присваиваемые запросу. Затем этот идентификатор передаётся всем сервисам, участвующим в обработке этого запроса. Это позволяет отслеживать всю цепочку запросов и ответов.

3. Проверки работоспособности.
Проверки работоспособности можно реализовать для мониторинга состояния микросервисов. Они могут сообщать такие показатели, как время безотказной работы, загрузка ЦП, использование памяти и т. д.

4. Промежуточное ПО для обработки исключений.
В архитектуре микросервисов ошибки должны обрабатываться на уровне сервиса. Каждый сервис должен обрабатывать свои собственные исключения и возвращать подходящее сообщение об ошибке или код ответа.
Можно создать промежуточное ПО, которое обрабатывает каждый запрос микросервиса. Если во время выполнения запроса возникает исключение, это промежуточное ПО перехватит исключение и ответит подходящим сообщением об ошибке.
См. также «Улучшенная Обработка Исключений в ASP.NET Core 8.»

5. Использовать распределённую систему трассировки.
Она собирает данные и показатели от каждого микросервиса, а затем сопоставляет эти данные в комплексный визуальный обзор производительности системы. См. документацию.

Источник: https://dev.to/bytehide/net-core-interview-question-answers-4bc1
👍21
День 1911. #ЗаметкиНаПолях
Реализуем Пессимистическую Блокировку в EF Core. Начало

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

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

Вот упрощённый фрагмент кода, иллюстрирующий проблему с билетами:
public async Task Checkout(ShopCart cart)
{
await using var trans = await
context.BeginTransactionAsync();

var order = new Order();
foreach (CartItem item in cart.Items)
{
// Проверяем доступность билета
// Что, если два запроса проверят его в одно время?
var ticket = await ticketRepo.GetAsync(
item.TicketId);

ticket.UpdateQuantity(item.Quantity);
order.Add(ticket, item.Quantity, item.Price);
}
orderRepo.Insert(order);

await context.SaveChangesAsync();
await trans.CommitAsync();

}

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

Поскольку EF Core не предлагает пессимистическую блокировку напрямую, немного углубимся в старый добрый SQL. Заменим вызов GetAsync для получения билета на GetWithLockAsync:
public async Task<Ticket> GetWithLockAsync(Guid id)
{
return await context
.Tickets
.FromSql(
$@"SELECT id, event_id, price, quantity
FROM tickets WHERE id = {id}
FOR UPDATE NOWAIT")
.SingleAsync();
}

FOR UPDATE NOWAIT - суть пессимистической блокировки в PostgreSQL (и Oracle). Он сообщает базе данных: «Захвати эту строку, заблокируй её для меня, и, если она уже заблокирована, прямо сейчас выдай ошибку».

Вызов GetWithLockAsync нужно обернуть в try-catch, чтобы корректно обрабатывать сбои блокировки, либо повторяя попытку, либо уведомляя пользователя.

Поскольку в EF Core нет встроенного способа добавления подсказок к запросам, приходится писать чистый SQL-запрос. Мы можем использовать оператор SELECT FOR UPDATE, чтобы получить блокировку на уровне выбранных строк. Любые конкурирующие транзакции будут заблокированы до тех пор, пока текущая транзакция не снимет блокировку. Это очень простой способ реализовать пессимистическую блокировку.

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

Источник:
https://www.milanjovanovic.tech/blog/a-clever-way-to-implement-pessimistic-locking-in-ef-core
👍40
День 1912. #ЗаметкиНаПолях
Реализуем Пессимистическую Блокировку в EF Core. Окончание

Начало

Варианты блокировки и когда их использовать
Чтобы операция не ждала, пока другие транзакции освободят заблокированные строки, вы можете объединить FOR UPDATE с:
- NO WAIT — вместо ожидания снятия блокировки, сообщает об ошибке, если строку невозможно заблокировать.
- SKIP LOCKED — пропускает любые выбранные строки, которые нельзя заблокировать. Заметьте, что в этом случае вы будете получать противоречивые результаты из БД. Однако это может быть полезно, чтобы избежать конфликта блокировок, когда несколько потребителей обращаются к таблице, похожей на очередь. Реализация паттерна Outbox является отличным примером.

В SQL Server для аналогичного эффекта можно использовать подсказку запроса WITH (UPDLOCK, READPAST). Однако SQL Server блокирует все строки, которые ему необходимо прочитать, чтобы получить нужную. Таким образом, если не определить индекс для прямого доступа к строке, все предшествующие строки будут заблокированы. Допустим, есть таблица TBL с полем id. Вы хотите заблокировать строку с id=10. Необходимо определить индекс для id (или других полей, по которым выбираете):
CREATE INDEX TBLINDEX ON TBL ( id );

А затем запросить блокировку только тех нужных строк:
SELECT * FROM TBL WITH (UPDLOCK, INDEX(TBLINDEX)) WHERE id=10;


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

Когда транзакция начинается на уровне изоляции Serializable, БД блокирует все данные, к которым может получить доступ транзакция. Эти блокировки удерживаются до тех пор, пока вся транзакция не будет зафиксирована или отменена. Любая другая транзакция, пытающаяся получить доступ к заблокированным данным, будет заблокирована до тех пор, пока первая транзакция не снимет блокировки.

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

Пессимистическая блокировка с помощью SELECT FOR UPDATE явно блокирует определённые строки, которые необходимо изменить. Другие транзакции, пытающиеся получить доступ к заблокированным строкам, блокируются до тех пор, пока блокировка не будет снята. Т.к. вы блокируете меньше ресурсов, вероятность взаимоблокировок снижается.

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

Итого
И сериализуемые транзакции, и пессимистическая блокировка с помощью SELECT FOR UPDATE — отличные варианты обеспечения согласованности данных. При выборе учитывайте требуемый уровень изоляции, потенциальное влияние на производительность и вероятность взаимоблокировок.

Источник: https://www.milanjovanovic.tech/blog/a-clever-way-to-implement-pessimistic-locking-in-ef-core
👍18
День 1913. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 6. Agile-требования не отличаются от других. Начало

Многие компании, занимающиеся разработкой ПО, используют методы Agile. Бизнес-аналитики и владельцы продуктов иногда применяют термин «Agile-требования». Но они не отличаются от требований в проектах с другим подходом.

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

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

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

3. Детальность документации
Agile-методы полагаются на доступность документации в нужный момент. Благодаря тесному сотрудничеству клиентов с разработчиками в Agile-проектах требования могут содержать меньше деталей, чем в традиционных проектах. Заинтересованные стороны могут сообщить все необходимые детали, когда это действительно потребуется, на встречах и в соответствующей документации. Некоторые пользовательские истории могут содержать мало подробностей, а сложные или важные функции могут прорабатываться более детально. Но не стоит чрезмерно полагаться только на устное общение. Каждой проектной группе необходимо создать достаточный объём документации, позволяющей решать задачи поддержки и совершенствования системы, но при этом не тратить время на запись информации, которую никто не будет использовать.

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

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
👍3
Разожжём вечный спор вечером в пятницу с подачи Ника)))
Не учитывая кодстайл компании, какой вариант лично вам больше по душе (см. картинку в первом комментарии)?
Anonymous Poll
32%
1
11%
2
9%
3
49%
4
👍2
День 1914. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 6. Agile-требования не отличаются от других. Окончание

Начало

5. Итоговое оформление требований
В общем случае пользовательские истории похожи на сценарии использования. Разница лишь в том, насколько тщательно вы их детализируете и записываете ли информацию. В традиционном проекте бизнес-аналитик может разработать набор функциональных требований на основе сценариев использования. Agile-команды конкретизируют каждую пользовательскую историю, определяя критерии приёмки и тесты, которые покажут, правильно ли разработчики реализовали её.
Функциональные требования и соответствующие им тесты — альтернативные варианты представления одной и той же информации. Требования определяют, что создавать; тесты описывают, как выяснить, демонстрирует ли система ожидаемое поведение. Наилучшие результаты получаются, когда разные люди пишут требования и тесты на основе одного и того же источника информации, например сценария использования.
Можно прибегнуть к альтернативной стратегии — записывать детали пользовательской истории в форме приёмочных тестов, которые затем проверяет тестировщик. Когда создаётся несколько представлений требований, нестыковки между ними помогают выявить проблемы. Если вы создаёте только одно представление, то независимо от выбранного метода будете вынуждены доверять его точности.

6. Расстановка приоритетов
Расстановка приоритетов учитывает относительную ценность каждого требования для клиента по сравнению с усилиями, риском и стоимостью его реализации. Традиционные проекты могут определить приоритеты требований на раннем этапе и в дальнейшем почти не пересматривать их. В Agile-проекте задачи приоритизируются непрерывно. Вы должны постоянно выбирать, что добавить в ближайшие итерации, а что может быть вообще исключено. На самом деле все команды, а не только те, кто практикует Agile, должны управлять оставшимися задачами, чтобы как можно быстрее передать клиенту максимально ценный для него продукт.

Есть ли разница?
Большинству клиентов неинтересно, как создаются приложения. Они хотят, чтобы продукты отвечали их потребностям, были эффективными, удобными и легко расширяемыми, а также удовлетворяли другие ожидания в отношении качества. Большинство методов разработки требований и управления ими в традиционных проектах в равной степени применимы и к Agile-проектам. Любая команда должна адаптировать методы разработки, чтобы они соответствовали их целям, культуре, среде и ограничениям.

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

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

Успешный переход требует развития нового мышления. Роль бизнес-аналитика в Agile-проекте заключается не столько в том, чтобы заранее определить, что будет сделано, сколько в постоянном пересмотре условий в процессе разработки. Происходит постоянная оценка того, что должна или не должна делать команда, — компромиссов, необходимых для максимизации доставляемой ценности.

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
День 1915. #Оффтоп
Сегодня порекомендую очередное видео с ютуба, чтобы расслабиться после 6-дневной рабочей недели.
Мне всё больше и больше нравится этот чувак, ThePrimeTime)))

На этот раз он читает статью из 2016 года, где автор рассказывает о своей маме, программистке на COBOL для мейнфреймов. Рассказ об эпохе раннего программирования, где разработчики сталкивались с такими задачами, о которых нам не приходится и думать сейчас. Проблема в том, что до сих пор (!!!) есть крупные компании (например, банки), которые в своё время создали себе огромные системы на COBOL, и до сих пор поддерживают их за огромные деньги, потому что переписать это с нуля либо в принципе невозможно, либо стоит нереально дорого.

Мудрость дня: «То, что вы сейчас пишете либо будет выкинуто в течение полугода, либо останется жить на десятилетия.»

В общем, приятного просмотра https://youtu.be/qNytWcW38us.
👍6
День 1916. #ЧтоНовенького
Новый Формат Файлов Решений в Visual Studio
В превью версии Visual Studio 17.10 добавлен новый формат файла решения с расширением файла .slnx.

На протяжении десятилетий файл решения .sln основывался на собственном формате файла от Microsoft, многословном, трудночитаемом с кучей GUID. Вот пример:
# Visual Studio Version 17
VisualStudioVersion = 17.10.34814.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App", "App\App.csproj", "{F95781B3-A973-4D19-9585-974DA143E6A1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{6EC5A7A1-CA0C-42AC-BB97-19EF01B3C68C}"
EndProject

Global
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {85AD0F43-BB53-4378-94F7-8A7DF5A990D5}
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{06F979AA-0ABF-4B86-91D5-7E32B7F2A081} = {6C6714E6-D117-405B-9FFD-C4AF0643EA29}
{F95781B3-A973-4D19-9585-974DA143E6A1} = {6EC5A7A1-CA0C-42AC-BB97-19EF01B3C68C}
EndGlobalSection

EndGlobal

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

Оптимизированный формат файлов .slnx – отличное изменение:
<Solution>
<Folder Name="/Test/">
<Project Path="UnitTests\UnitTests.csproj" Type="Classic C#" />
</Folder>
<Folder Name="/Application/">
<Project Path="App\App.csproj" Type="Classic C#" />
</Folder>
</Solution>

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

Изменение перекликается с более ранним упрощением Microsoft формата файла проекта .csproj, включающим теперь только важную информацию, которую IDE не может вывести сама. Конечно, при изменении стандартных настроек конфигурации в файл будут добавлены дополнительные строки.

Пока формат в предварительной версии и рекомендуется избегать его использования в производственной среде. Он всё ещё может подвергаться изменениям на основе отзывов сообщества.

Чтобы попробовать его в Visual Studio 2022 17.10 превью 3 или более поздней версии, в меню Tools > Options > Preview Features (Инструменты > Параметры > Предварительные функции) найдите Use Solution File Persistence Model. Затем в меню File > Save YourSolution.sln As… (Файл > Сохранить YourSolution.sln как…) выберите тип файла .slnx. Заметьте, что этот тип файлов пока не связан с Visual Studio в Windows, поэтому привязку придётся настроить вручную.

Источник: https://blog.ndepend.com/slnx-the-new-net-solution-xml-file-format/
👍24👎1
День 1917. #ЗаметкиНаПолях
Обмен Сообщениями с Помощью MassTransit

Создание распределённых приложений – дело непростое. Есть ряд потенциальных проблем, которые необходимо учитывать. Что делать, если в сети произошел сбой или сервис неожиданно прекратил работу? Способ взаимодействия частей распределённой системы становится критически важным.

Синхронная связь, когда сервисы напрямую обращаются друг к другу, хрупка. Она создаёт тесную связь, делая всё приложение уязвимым для отдельных точек отказа. Мы можем использовать распределённый обмен сообщениями. Одна из известных утилит для этого в мире .NET – MassTransit.

Шаблон обмена сообщениями запрос-ответ
Шаблон запрос-ответ аналогичен традиционному вызову функции, но по сети. Одна служба, запрашивающая сторона, отправляет сообщение запроса и ожидает соответствующего ответного сообщения.

Достоинства

1. Слабая связь: сервисам не требуются знания друг о друге, а только контракты сообщений. Это упрощает внесение изменений и масштабирование.
2. Прозрачность местоположения: запрашивающей стороне не нужно знать, где находится отвечающая сторона, что обеспечивает повышенную гибкость.

Недостатки
1. Задержка: накладные расходы на обмен сообщениями добавляют дополнительную задержку.
2. Сложность: внедрение системы обмена сообщениями и управление дополнительной инфраструктурой могут повысить сложность проекта.

MassTransit поддерживает шаблон запрос-ответ «из коробки». Мы можем использовать клиент запросов для отправки запросов и ожидания ответа. Клиент запроса является асинхронным. По умолчанию запрос также будет иметь тайм-аут 30 секунд, чтобы не допустить слишком длительного ожидания ответа.

Например, у нас система обработки заказов и необходимо получить статус заказа из сервиса управления заказами. С помощью MassTransit создадим клиента, реализующего IRequestClient<T>, для инициирования запроса:
public class RequestController :
Controller
{
IRequestClient<StatusRequest> _client;


public async Task<IActionResult>
Get(string id, CancellationToken ct)
{
var response = await
_client.GetResponse<StatusResponse>(
new { id }, ct);

return Ok(response.Message);
}
}

Он отправит на шину сообщение GetOrderStatusRequest:
public record StatusRequest(string OrderId);

И будет ожидать ответа:
public record StatusResponse(
string OrderId,
short StatusCode,
string StatusText
}

В сервисе управления заказами ответчик (IConsumer<T>) будет прослушивать сообщения StatusRequest. Он получает запрос, возможно запрашивает БД и отправляет сообщение StatusResponse обратно на шину. Клиент будет ждать этого ответа и затем сможет обработать его соответствующим образом:
public class StatusRequestConsumer :
IConsumer<StatusRequest>
{
public async Task Consume(
ConsumeContext<StatusRequest> ctx)
{

await ctx
.RespondAsync<StatusResponse>(new
{
// задаём свойства ответа
});
}
}

Полный код примера есть в документации MassTransit.

Итого
Шаблон запрос-ответ — мощный инструмент при обмене сообщениями между сервисами. MassTransit значительно упрощает реализацию, гарантируя надежную доставку запросов и ответов.
Мы можем использовать запрос-ответ для реализации связи между модулями в модульном монолите. Однако не стоит доводить это до крайности, иначе система может страдать от увеличения задержки.

Источник: https://www.milanjovanovic.tech/blog/request-response-messaging-pattern-with-masstransit
👍10