.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
День 1893. #УрокиРазработки
Уроки 50 Лет Разработки ПО


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

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

Чтобы упростить исследование требований, разделите пользователей на группы с разными наборами потребностей. Пользователи могут быть даже не людьми, а аппаратными устройствами или другим ПО. Нужно определить людей, которые могут предоставить требования от имени этих компонентов.
Могут быть и непрямые пользователи продукта. Например, получатели годовых отчётов компании не взаимодействуют с ПО напрямую, но они основные заинтересованные стороны. Необходимо также определить классы нежелательных пользователей, которые не должны иметь возможности пользоваться системой. Например, хакеры не являются заинтересованной стороной (не определяют требования или ограничения), но нужно предвидеть и пресекать их злые намерения.

Для каждой группы заинтересованных сторон, опишите:
1. Кто они? Чтобы все участники проекта понимали, кто это.
2. Насколько они заинтересованы? Как сильно результат проекта повлияет на группу и как их желание участвовать в проекте. Их ожидания, интересы, опасения и ограничения.
3. Какое влияние на проект они имеют? Какие решения могут и не могут принимать. Кто влияет больше всего? Особое внимание группам, которые проявляют наибольший интерес и оказывают наибольшее влияние.
4. С кем лучше говорить? Представители каждой группы, с которыми нужно работать. Они должны быть авторитетными источниками информации.
5. Где они? Проще получить информацию, если есть прямой доступ к представителям группы. Либо продумайте процесс коммуникации.
6. Что нам нужно от них? Информация, решения и данные, которые понадобятся получить от каждой группы. Некоторые группы будут накладывать ограничения на проект, например:
- финансовые, временные и ресурсные;
- применимые политики, нормы и стандарты (бизнес-правила);
- совместимость с другими продуктами, системами или интерфейсами;
- юридические или договорные;
- требования к сертификации;
- ограничения возможностей продукта (что не должно включаться в продукт).
7. Что им нужно от нас? Одних нужно проинформировать о проблемах, другим может понадобиться пересмотреть требования.
8. Как и когда с ними взаимодействовать?
9. Какие стороны наиболее важны при разрешении конфликтов? При разрешении противоречий и принятии важных решений оцените, какой результат больше всего соответствует целям проекта. Проанализируйте влияние и интересы сторон, не ждите, пока назреет первый конфликт.

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

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
👍3
День 1894. #Карьера #Продуктивность
Начинайте Работу Через Минуту После Пробуждения

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

Однако наиболее продуктивные люди в мире ничего такого не делают. Они просто просыпаются и начинают работать. Поначалу это может показаться странным и трудным – работать не до конца проснувшись. Но на самом деле это самое продуктивное время дня. Психолог Михай Чиксентмихайи в 1970х назвал это склонностью к потоку (flow proneness) – это настрой организма к работе в потоке.

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

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

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

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

Проснитесь, и сразу приступайте к самой важной работе. Не через час, не через 10-15 минут, а в течение 60-90 секунд, пока вы ещё наполовину спите. В течение нескольких минут вы полностью проснётесь и будете работать в состоянии потока. Поработайте так 1-3 часа. После этого займитесь тем, что обычно поднимает вам настроение с утра и позволяет подготовиться к рабочему дню: чашка кофе, контрастный душ, йога, прогулка. Воспринимайте это как утреннюю рутину наоборот: сначала поработать, потом восстановиться. Так мы используем самое продуктивное время после пробуждения, данное нам природой, и восстанавливаемся после этого, чтобы зарядиться энергией на оставшийся день.

И ещё два совета.
1) Подготовьтесь заранее. Составьте подробный список дел накануне вечером. Так, чтобы с утра не думать, что надо делать, а сразу приступать к делу.
2) Выделите достаточно времени. 1-3 часа на активную работу в состоянии потока, не отвлекаясь.

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

Источник: https://youtu.be/XJOsPyyYork
👍25
День 1895.
Сопоставление с Образцом и Компилятор Могут Удивлять
Сопоставление с образцом — мощная функция C#. Она позволяет сопоставлять значение с шаблоном и извлекать информацию из значения. Компилятор творит за вас волшебство, но иногда этим и поражает.

Вот такой фрагмент кода рассылки писем:
Console.WriteLine(
GetHashCode(new PostCard { Subject = "Test" }));

static int GetHashCode(Document doc)
{
return doc switch
{
PostCard { Subject: { } s, Text: { } t }
=> HashCode.Combine(s, t),
Mail { Subject: { } s }
=> s.GetHashCode(),
_ => 0,
};
}

public class Document;
public class Mail : Document
{
public string? Subject { get; set; }
}
public class PostCard : Mail
{
public string? Text { get; set; }
}

Функция GetHashCode принимает Document и возвращает хэш-код. Она использует сопоставление с образцом для сопоставления типа и получения хэш-кода. Интересная часть PostCard { Subject: { } s, Text: { } t } по сути гарантирует, что Subject и Text не null. Второе условие проверяет только базовый тип (мы идём от более конкретного к более общему). В чём тут сюрприз? В том, что код выдаёт предупреждение:
Program.cs(11,9): Warning CS8602 : Dereference of a possibly null reference (Разыменование возможно нулевой ссылки).
Вот в этом месте
Mail { Subject: { } s }
=> s.GetHashCode(),

Учитывая, что мы явно проверяем, что Subject не null, почему компилятор на это жалуется? Для этого нам нужно взглянуть на сгенерированный компилятором код:
internal static GetHashCode(Document doc)
{
PostCard postCard = doc as PostCard;
string subject2;
if (postCard != null)
{
string subject = postCard.Subject;
if (subject != null)
{
string text = postCard.Text;
if (text == null)
{
Mail mail = (Mail)doc;
subject2 = mail.Subject;
goto IL_0057;
}
return HashCode.Combine(subject, text);
}
}
else
{
Mail mail = doc as Mail;
if (mail != null)
{
subject2 = mail.Subject;
if (subject2 != null)
{
goto IL_0057;
}
}
}
return 0;
IL_0057:
return subject2.GetHashCode();
}

Кажется, статический анализатор видит проблему в операторе goto в ветке PostCard. Заметьте, что subject проверяется на null, a subject2 нет.

Так что, если в вашем коде есть именно такой шаблон, возможно, вам стоит об этом знать. Оговорюсь, что случаи, когда этот код фактически выдаст исключение NullReferenceException – крайне редкие. Вот такой вымышленный пример:
public class Mail : Document
{
private string? subject;
public string? Subject
{
get
{
Console.WriteLine("Вызвали get");
var r = subject;
subject = null;
return r;
}
set => subject= value;
}
}

В этом случае при вызове
Console.WriteLine(
GetHashCode(new PostCard { Subject = "Test" }));

аксессор get свойства Mail.Subject будет вызван дважды:
1) в строке string subject = postCard.Subject;
2) в строке subject2 = mail.Subject;
И из-за нашего специального кода get выше, во втором случае get выдаст null, поэтому return subject2.GetHashCode(); выбросит NullReferenceException. Выполнив этот код, вы увидите «Вызвали get» в консоли дважды перед исключением.

Источник: https://steven-giesel.com/blogPost/9892bd03-96bf-4ab0-aacc-973a99d5f5e0/pattern-matching-and-the-compiler-can-be-surprising
👍10
День 1896. #Оффтоп #Безопасность
Бэкдор в SSH Linux

«Linux безопасен», - говорили они. «В Linux нет вирусов», - говорили они.

Зато туда едва не проник бэкдор, который мог позволить его автору получать root доступ к любой (!!!) машине под управлением Linux и в которой была установлена нужная версия библиотеки XZ Utils. Причём сделал это один из её главных контрибуторов и довольно хитро - в make-файле (которые обычно никто досконально не проверяет). Бэкдор активировался при сборке проекта. Насколько я понял, достаточно было добавить малозаметную точку в файл в очередном релизе, чтобы сборка «выбрасывала ошибку» при поиске одной из нужных библиотек, и вместо неё использовала «стандартную» с уязвимостью. После этого оставалось только ждать, пока все серверы обновят версию библиотеки и…

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

Национальная база данных уязвимостей США присвоила этой уязвимости критичность 10 из 10.

Больше подробностей об этом, а также чем процесс релиза кода в Microsoft отличается от процесса в «диком опенсорсе» смотрите в видео Dave’s Garage.
👍27
День 1897. #ЗаметкиНаПолях
5 правил для DTO

DTO — это объект передачи данных (Data Transfer Object). Его задача — передавать данные, и его можно использовать как для отправки данных, так и для их получения.

Часто передаваемые данные используют разные типы (возможно, даже разные языки программирования и стеки технологий) на каждом конце передачи. Единственное, на что вы можете рассчитывать при передаче – это данные и ничего больше. Они должны легко сериализовываться и десериализовываться в JSON, XML и т.п. Поведение сложно будет десериализовать на принимающей стороне. Отсюда первое правило для DTO.

Правило 1. DTO должны содержать только данные. Никакой логики и поведения.

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

Правило 2. DTO не обеспечивают инкапсуляцию. Им не нужны приватные/защищённые члены.

Даже несмотря на то, что DTO не нуждаются в инкапсуляции, обычно предпочитают использовать свойства C#, а не поля. По умолчанию сериализаторы и другие функции языка работают со свойствами, а не с полями (хотя вы можете это настроить).

Правило 3. DTO должны использовать свойства (а не поля).

Очевидным соглашением об именах является простое добавление «DTO» (или «Dto») в конец имени объекта. Хотя это работает и подходит для самого простого представления, во многих случаях вам следует использовать более описательное имя.

Многие распространённые типы, используемые в современных приложениях .NET, могут (и обычно должны) создаваться как DTO. К ним относятся объекты запросов и ответов API, команды и запросы в CQ(R)S, события и многое другое. В таких случаях добавьте к типу более конкретное имя (например, «CreateUserRequest» вместо просто «UserDTO»).

Правило 4. DTO следует использовать суффикс «-DTO» только в крайнем случае. Предпочитайте более описательные имена.

Некоторые объекты в приложении должны быть DTO и называться в соответствии с их конкретным использованием, а не «FooDTO».

Правило 5. Следующие модели должны быть спроектированы как DTO:
- типы запросов/ответов API,
- модели представления в MVC,
- объекты результатов запроса к БД,
- сообщения (команды, события и запросы).


Для валидации свойств DTO вы можете использовать аннотации данных, IValidatableObject, FluentValidation или ключевое слово required.

Источник: https://ardalis.com/5-rules-dtos/

См. также:
- В чём разница между DTO и POCO?
- Ваш API и Модели Представления не Должны Использовать Модели Домена
👍25
День 1898. #ЗаметкиНаПолях
Горизонтальное Масштабирование API с Помощью YARP.
Современные веб-приложения должны обслуживать растущее число пользователей и справляться с резкими скачками трафика. Балансировка нагрузки — ключевой метод решения этих проблем и улучшения масштабируемости приложения.

Существует два основных подхода:
1. Вертикальное масштабирование – обновление железа серверов: больше ядер ЦП, больше памяти, более быстрое хранилище.
2. Горизонтальное масштабирование - добавление дополнительных серверов и разумное распределение нагрузки между ними.

Добавляем обратный прокси
YARP — это высокопроизводительная библиотека обратного прокси от Microsoft. Обратный прокси находится перед вашими серверами и управляет трафиком между ними (см. картинку ниже). YARP позволяет выполнять задачи маршрутизации и преобразования входящих запросов до того, как они достигнут ваших внутренних серверов. Входящие запросы к API сначала попадают в YARP, который распределяет трафик на серверы приложений на основе стратегии балансировки нагрузки. В примере на картинке одна база данных, обслуживающая несколько экземпляров приложения.

1. Сначала установим NuGet:
Install-Package Yarp.ReverseProxy

2. Настроим необходимые сервисы и промежуточное ПО:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddReverseProxy()
.LoadFromConfig(
builder.Configuration
.GetSection("ReverseProxy"));

var app = builder.Build();

app.MapReverseProxy();

app.Run();

3. Добавим конфигурацию в appsettings.json. YARP использует маршруты (Routes) для представления входящих запросов к обратному прокси и кластеры (Clusters) для определения нижестоящих сервисов. Шаблон {**catch-all} позволяет маршрутизировать все входящие запросы.

Существуют различные стратегии балансировки нагрузки:
1. PowerOfTwoChoices - выбирает два случайных пункта назначения и из них тот, у которого меньше назначенных запросов.
2. FirstAlphabetical - выбирает первый доступный целевой сервер в алфавитном порядке.
3. LeastRequests - отправляет запросы на серверы с наименьшим количеством назначенных запросов.
4. RoundRobin - равномерно распределяет запросы по внутренним серверам.
5. Random - сервер выбирается случайным образом для каждого запроса.
Стратегию балансировки нагрузки можно настроить с помощью свойства LoadBalancingPolicy в кластере.
{
"ReverseProxy": {
"Routes": {
"api-route": {
"ClusterId": "api-cluster",
"Match": {
"Path": "{**catch-all}"
},
"Transforms": [{ "PathPattern": "{**catch-all}" }]
}
},
"Clusters": {
"api-cluster": {
"LoadBalancingPolicy": "RoundRobin",
"Destinations": {
"destination1": {
"Address": "https://api-1:8080"
},
"destination2": {
"Address": "https://api-2:8080"
},

}
}
}
}
}

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

См. также Создаём API-Шлюз для Микросервисов с Помощью YARP.

Исходный код с тестами производительности тут.

Источник: https://www.milanjovanovic.tech/blog/horizontally-scaling-aspnetcore-apis-with-yarp-load-balancing
👍12
День 1899. #УрокиРазработки
Уроки 50 Лет Разработки ПО


Урок 4. В требованиях главное особенности использования, а затем — функциональность
В индустрии ПО существует убеждение, что от 50 до 80% возможностей ПО используются редко или не используются никогда.

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

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

Меняется суть вопросов во время сбора информации. Вместо «Что вы хотите?» или «Что должна делать система?» можно спросить: «Как предполагается использовать систему?» Редкие пользователи запускают приложение ради определённой функции; подавляющее большинство стремится достичь конкретной цели. Я открываю приложение, имея определённое намерение, и выполняю последовательность шагов, вызывая функции, необходимые для решения задачи. Если всё хорошо, то я успешно решаю свою задачу и закрываю приложение.

Преимущества вариантов использования
1. Помогают пользователям задуматься о своих потребностях. Им сложно правильно сформулировать перечень функций продукта, но они легко расскажут о сценариях использования из своей жизни. По этим описаниям можно определить, какие функции должно предоставить приложение, чтобы пользователи могли решать поставленные задачи. Также учитывайте, какие ошибки могут возникнуть и как система должна их обрабатывать.
2. Помогают расставить приоритеты. Одни варианты использования будут более важными, поэтому должны быть реализованы в первую очередь. Наивысший приоритет имеет нормальный сценарий вместе с возможными нештатными ситуациями. Альтернативные сценарии имеют более низкий приоритет, их реализацию часто откладывают или отменяют вообще.
3. Более продуктивное взаимодействие с пользователем и более полное представление об ограничениях реализации, которое труднее получить, при разговоре о функциональности продукта.

Проблема пользовательских историй
Пользовательская история (user story) описывает функциональность, которая будет полезна как пользователю, так и покупателю системы. Они обычно записываются с использованием простого шаблона:
Как <тип пользователя>, я хочу <описание решаемой задачи>, чтобы я мог <достижение некой цели>.

или
Как <тип пользователя>, я хочу достичь <некая цель>, чтобы <некий результат>.


Одна из проблем пользовательских историй в том, что они не имеют внутренней организационной схемы. Множество историй, даже записанных по шаблону, мало чем отличается от вопроса: «Чего вы хотите?» Вы получаете много кусочков важной информации, перемешанных с посторонними сведениями. Требуется, чтобы кто-то отсортировал их и выявил темы, связанные с пользовательскими задачами.

Альтернативой является шаблон истории работы (job story), который ёмко и структурированно описывает потребность:
Когда <ситуация>, я хочу выполнить <задачу>, чтобы я достичь <желаемый результат>.


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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
👍11
День 1900. #ЗаметкиНаПолях
Добавляем Деконструктор в Сторонние Типы
Деконструктор — это функция языка C#, позволяющая определить метод, который будет вызываться при разделении объекта на компоненты. Это легко реализовать для ваших собственных типов, но можно добавить и для сторонних типов.

Например:
public class Point
{
public int X { get; }
public int Y { get; }

public Point(int x, int y)
{
X = x;
Y = y;
}

public void Deconstruct(out int x, out int y)
{
x = X;
y = Y;
}
}

// Использование
var point = new Point(3, 4);
var (x, y) = point;

Красиво и просто, но ваш класс должен определить метод Deconstruct. Что делать, если вы хотите деконструировать сторонний тип, который вы не можете изменить? Компилятор будет искать метод с сигнатурой Deconstruct в типе, который вы пытаетесь деконструировать, но он также будет искать и метод расширения с той же сигнатурой. Т.е. можно определить деконструктор для стороннего типа, определив метод расширения с той же сигнатурой:
public static class Extensions
{
public static void Deconstruct(
this Uri uri,
out string scheme,
out string host,
out string path,
out string query,
out string fragment)
{
scheme = uri.Scheme;
host = uri.Host;
path = uri.AbsolutePath;
query = uri.Query;
fragment = uri.Fragment;
}
}

// Использование
var url =
"https://www.google.com/search?q=dotnet#ip=1";
var (scheme, host, path, query, fragment)
= new Uri(url);

Console.WriteLine($"Scheme: {scheme}"); // https
Console.WriteLine($"Host: {host}"); // google.com
Console.WriteLine($"Path: {path}"); // /search
Console.WriteLine($"Query: {query}"); // ?q=dotnet
Console.WriteLine($"Fragment: {fragment}"); // #ip=1

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

Источник: https://steven-giesel.com/blogPost/0775d3d3-8f90-4546-95d8-71a6b1e7b0e8/equip-3rd-party-types-with-a-deconstructor
👍25
Вы разрабатываете веб-API ASP.NET Core, который должен поддерживать форматирование ответов в как в JSON, так и в XML. По умолчанию поддерживается только форматирование в JSON. Как добавить форматирование в XML?
#Quiz #ASPNET
Anonymous Quiz
15%
Добавить опцию AcceptXml = true в промежуточное ПО AddControllers в Startup.ConfigureServices
13%
Добавить NuGet пакет Microsoft.Extensions.XmlFormatters
28%
Добавить UseXmlSerializer в Startup.Configure
44%
Добавить AddXmlSerializerFormatters в Startup.ConfigureServices
👍18👎18
День 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