.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
День 1614. #ЧтоНовенького
Новинки Превью 5 .NET 8
Продолжаем рассматривать, что нового появилось в превью 5 8й версии .NET.

1. IHttpSysRequestTimingFeature
Новый интерфейс IHttpSysRequestTimingFeature предоставляет подробные данные о временных метках, связанных с обработкой запросов при использовании сервера HTTP.sys. Раньше информация о запросе HTTP.sys предоставлялась через интерфейс IHttpSysRequestInfoFeature. С добавлением IHttpSysRequestTimingFeature мы движемся к более чётко определённым API, которые обеспечивают лучший доступ к данным о времени запроса.
namespace Microsoft.AspNetCore.Server.HttpSys
{
public interface IHttpSysRequestTimingFeature
{
ReadOnlySpan<long> Timestamps { get; }
bool TryGetTimestamp(
HttpSysRequestTimingType timestampType,
out long timestamp);
bool TryGetElapsedTime(
HttpSysRequestTimingType startingTimestampType,
HttpSysRequestTimingType endingTimestampType,
out TimeSpan elapsed);
}
}

Свойство Timestamps предоставляет доступ ко всем временным меткам HTTP.sys. Временные метки получаются с помощью QueryPerformanceCounter, а частота временных меток может быть определена с помощью QueryPerformanceFrequency.

Метод TryGetTimestamp извлекает метку времени для предоставленного типа времени, а метод TryGetElapsedTime даёт время, прошедшее между двумя указанными временами.

Это усовершенствование предоставляет разработчикам:
- Более детальное понимание различных этапов обработки запросов.
- Возможности точной диагностики производительности.
- Улучшенный доступ к данным о времени запросов HTTP.sys и контроль над ними.

2. Имя хоста в ITlsHandshakeFeature
Имя хоста Server Name Indication (SNI) теперь доступно в интерфейсе ITlsHandshakeFeature.

/// <summary>
/// Gets the host name from the "server_name" (SNI) extension of the client hello if present.
/// See <see href="https://www.rfc-editor.org/rfc/rfc6066#section-3">RFC 6066</see>.
/// </summary>
string? HostName => null;

SNI является частью процесса TLS-рукопожатия и позволяет клиентам указывать имя хоста, к которому они пытаются подключиться. Это позволяет серверам, на которых размещается несколько виртуальных хостов или доменов, предоставлять правильный сертификат безопасности во время процесса установки связи, следовательно, позволяет работать нескольким HTTPS-сайтам (или другим сервисам поверх TLS) на одном IP-адресе без использования одного и того же сертификата для всех сайтов. Обычно SNI обрабатывается только в стеке TLS и используется для выбора соответствующего сертификата, но теперь раскрытие имени хоста позволяет другим компонентам приложения использовать эту информацию для диагностики, ограничения скорости, маршрутизации и т. д.

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

Источник: https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-5/#servers
👍8
День 1615. #ЗаметкиНаПолях
Разбираем и Оптимизируем SQL-Запросы. Начало
Сегодня разберём, как SQL-запросы выполняются механизмом базы данных и как мы можем использовать эти знания для оптимизации запросов.

Порядок выполнения SQL
— это фактическая последовательность, в которой механизм БД обрабатывает различные компоненты запроса. Он может серьёзно отличаться от порядка написания запроса. Так ядро БД может свести к минимуму дисковый ввод-вывод, эффективно использовать индексы и избежать ненужных операций. Рассмотрим запрос:
SELECT customers.name,
COUNT(order_id) as Total_orders,
SUM(order_amount) as total_spent
FROM customers
JOIN orders ON customers.id = orders.customer_id
WHERE order_date >= '2023-01-01'
GROUP BY customers.name
HAVING total_spent >= 1000
ORDER BY customers.name
LIMIT 100;

Порядок выполнения этого запроса следующий:
1. FROM: определение таблиц, задействованных в запросе (customers и orders).
2. JOIN: выполнение операции соединения на основе условия соединения (customers.id = orders.customer_id).
3. WHERE: применение условия фильтрации к объединённой таблице (order_date >= '2023-01-01'), которое отбирает только заказы, сделанные 1 января 2023 года или позже.
4. GROUP BY: группировка строк по указанным столбцам (customers.name).
5. HAVING: фильтрация групп по условию (total_spent >= 1000), при котором выбираются только клиенты с общей потраченной суммой 1000 и более.
6. SELECT: выбор столбцов и агрегатных функций из каждой группы (customers.name, COUNT(order_id) и SUM(order_amount)).
7. ORDER BY: сортировка строк по указанным столбцам (customers.name).
8. LIMIT: ограничение результата максимум 100 строками.

Запросы SARGABLE
SARGABLE (Searched ARGUment ABLE) - это запрос, который может использовать индексы для более быстрого выполнения. Запрос SARGABLE использует операторы и функции, которые могут использовать преимущества индексов. Например, использование операторов равенства (=), неравенства (<>, !=), диапазона (BETWEEN) или членства (IN) в индексированных столбцах может сделать запрос пригодным для поиска.
Запрос не является SARGABLE, если он использует операторы или функции, которые препятствуют использованию индекса или требуют полного сканирования таблицы. Например, использование операторов отрицания (NOT), подстановочных знаков (LIKE) или арифметических операций (+, -, *, /) в индексированных столбцах может сделать запрос недоступным для поиска.

Чтобы написать SARGABLE-запрос, обычно нужно ИЗБЕГАТЬ использования следующих конструкций в предложении WHERE:
1. Функций для индексированных столбцов: UPPER(), LOWER(), SUBSTRING() и т. д.
2. Арифметических операций над индексированными столбцами: столбец + 1 > 10, столбец * 2 < 20 и т. д.
3. Операторов отрицания для индексированных столбцов: NOT IN, NOT LIKE, NOT EXISTS и т. д.
4. Ведущих подстановочных знаков в индексированных столбцах: LIKE '%abc', LIKE '%xyz%' и т. д.
5. Неявных преобразований типов, которые могут повлиять на использование индекса.

Замечание: некоторые СУБД могут эффективно обрабатывать такие запросы и использовать индексы, а также некоторые виды индексов специально предназначены для таких запросов, но в общем случае полагаться на это не стоит.

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

Источник:
https://dev.to/kanani_nirav/secret-to-optimizing-sql-queries-understand-the-sql-execution-order-28m1
👍22
День 1615. #ЗаметкиНаПолях
Разбираем и Оптимизируем SQL-Запросы. Окончание
Начало

Настройка производительности на уровне БД
Повышение производительности в порядке выполнения SQL включает в себя оптимизацию шагов, выполняемых ядром БД для обработки и выполнения SQL-запросов. Вот несколько способов повысить производительность в порядке выполнения SQL:

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

2. Используйте покрывающие индексы: по возможности добавьте в индекс все столбцы, которые требуются для выполнения запроса. Тогда ядро БД для выполнения запроса просканирует только индекс (INDEX ONLY SCAN в плане выполнения запроса), вообще не обращаясь к таблице. Например, если у вас есть индекс таблицы клиентов по дате рождения, а в запросе нужно вывести имена клиентов определённого года рождения, имеет смысл добавить поля имени в индекс. Это несколько увеличит накладные расходы на индекс и его обновление, зато запрос, извлекающий только имя по дате рождения, будет сканировать только индекс и выполняться значительно быстрее. В противном случае имя клиента должно будет извлекаться из таблицы по ссылке для каждой строки индекса (INDEX SCAN в плане выполнения запроса), что обычно быстрее полного сканирования таблицы (FULL TABLE SCAN), но медленнее сканирования только индекса.

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

4. Ограничьте размер набора результатов: используйте предложение LIMIT, чтобы ограничить количество строк, возвращаемых запросом. Это может уменьшить объём обрабатываемых данных и сократить время ответа на запрос.

5. Избегайте ненужной сортировки и группировки: устраните ненужные операции сортировки и группировки, добавляя их только при необходимости. Этого можно добиться путем тщательного анализа запроса и удаления ненужных предложений ORDER BY и GROUP BY.

6. Используйте раннюю фильтрация в предложении WHERE: применяйте условия фильтрации как можно раньше в порядке выполнения запроса с помощью предложения WHERE. Это уменьшит количество строк, обрабатываемых на последующих этапах, повышая производительность. Также используйте наиболее ограничивающие условия первыми в предложении WHERE.

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

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

9. Используйте инструменты оптимизации запросов: инструменты или подсказки оптимизатора запросов для конкретной базы данных помогут механизму базы данных создавать эффективные планы выполнения. Эти инструменты могут предоставить информацию, рекомендации и статистику для повышения производительности.

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

Источник: https://dev.to/kanani_nirav/secret-to-optimizing-sql-queries-understand-the-sql-execution-order-28m1
👍12
День 1617. #ЗаметкиНаПолях
Отслеживание Кликов по Ссылке на HTML-Странице
Существует множество способов отслеживать, когда пользователь щёлкает по ссылке на HTML-странице. Большинство из них требуют JS-скрипта и регистрации события click для элемента <a>. Совсем недавно в элемент <a> был добавлен атрибут ping. Он позволяет указать URL-адрес, на который будет отправлен ping-запрос при нажатии на ссылку. Таким образом отпадает необходимость в JS-скрипте.

Атрибут ping хорошо поддерживается. Только Firefox отключил эту функцию по умолчанию. Кроме того, блокировщики рекламы могут заблокировать ping-запрос (так же как они блокируют некоторые JS-скрипты). Поэтому, глобальная поддержка функции составляет около 93%.

Использование атрибута ping даёт некоторые преимущества:
- работает, когда JavaScript отключен,
- ping-запросы являются фоновыми, т.е., когда пользователь покидает страницу, запрос ping не будет прерван.
- тело ответа на ping-запрос игнорируется; браузеру разрешено немедленно завершать соединение, когда сервер отправляет тело ответа. Это помогает экономить ресурсы сервера и браузера.
- ping-запросы не кэшируются, так как это POST-запрос, поэтому каждый ping приводит к запросу на сервер.

Вот пример использования атрибута ping:
<a href="/demo" ping="/ping">Demo</a>

Вы можете обработать этот ping-запрос в вашем приложении ASP.NET Core application. Например:
app.MapPost("/ping", 
([FromHeader(Name = "ping-from")]string from,
[FromHeader(Name = "ping-to")] string to) =>
{
// обрабатываем ping-запрос
return Results.Ok();
});

В примере выше заголовки ping-from и ping-to выдают исходный URL и запрашиваемый URL (указанный в атрибуте href) соответственно.

Если вам нужно различить несколько ссылок на одной странице, можно использовать параметр строки запроса. Например, если у вас есть две ссылки на одной странице, вы можете использовать <a ping="/ping?id=Link1"> и <a ping="/ping?id=Link2">, чтобы различать их.

Источник: https://www.meziantou.net/tracking-click-on-anchors-in-an-html-page.htm
👍17
День 1618. #ЗаметкиНаПолях
LINQ: Select.Where или Where.Select?
LINQ — очень мощный инструмент для запросов данных. Поскольку большинство функций построены на основе IEnumerable<T> и в большинстве случаев он также возвращает IEnumerable<T>, очень легко объединять несколько функций в цепочку. Возникает вопрос: есть ли разница между Select.Where или Where.Select?

Сначала замечу, что далее мы рассматриваем только коллекции «в памяти», и не IQueryable<T>. Там порядок зависит в основном от провайдера, поэтому Entity Framework и LINQ to SQL могут давать разные результаты.

Рассмотрим следующий пример кода:
var users = GetAllUsers();

// Вариант 1
users.Where(u => u.IsStudent)
.Select(u =>
new { u.FirstName, u.LastName, u.IsStudent });

// Вариант 2
users.Select(u =>
new { u.FirstName, u.LastName, u.IsStudent })
.Where(u => u.IsStudent);

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

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

А теперь рассмотрим такой пример:
// вариант 1
users.Where(u => u.IsStudent && u.Age > 30);

// вариант 2
users.Where(u => u.IsStudent)
.Where(u => u.Age > 30);

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

Если всё же не обойтись без нескольких предложений Where, имеет смысл поставить лучший фильтр в начале, либо попробовать реорганизовать все фильтры в один метод:
private bool IsStudentAndOlderThan30(User user)
{
return user.IsStudent && user.Age > 30;
}

users.Where(IsStudentAndOlderThan30);

Источник: https://steven-giesel.com/blogPost/57ed9867-4afd-4d02-9f35-e0941bc6f715
👍21
День 1619. #ЗаметкиНаПолях
Время в .NET. Начало
Время – важная часть наших программ. Отслеживание часовых поясов и тестирование кода, зависящего от времени, - вечная головная боль разработчиков.

История
DateTime была основной структурой для хранения даты и времени в .NET, начиная с версии 1.1. У неё есть большой недостаток - отсутствие часового пояса. Чтобы решить эту проблему, было добавлено свойство Kind с возможными значениями: Local (по умолчанию), Utc или Unspecified.

Для конвертации в UTC нужно вызвать метод ToUniversalTime() или использовать свойство DateTime.UtcNow. Как .NET понимает разницу в часовых поясах, если DateTime не предоставляет эту информацию? Метод ToUniversalTime берёт часовой пояс из операционной системы, что может приводить к проблемам. Если создать локальное время на сервере в одном часовом поясе, а потом переместить его на другой сервер (например, на клиента API), то результат вызова ToUniversalTime на этих серверах будут разным.

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

.NET ожидает, что информация о часовом поясе будет связана со свойством Kind во время восстановления даты и времени: DateTimeKind.Local с TimeZoneInfo.Local, DateTimeKind.Utc с TimeZoneInfo.Utc и DateTimeKind.Unspecified в остальных случаях. Пользовательский часовой пояс, как рекомендует Microsoft, требует использования DateTimeKind.Unspecified:
var nowInNY = DateTime.Now;
var nyTZ = TimeZoneInfo
.FindSystemTimeZoneById("Eastern Standard Time");
nowInNY = DateTime
.SpecifyKind(nowInNY, DateTimeKind.Unspecified);
var utc = TimeZoneInfo
.ConvertTimeToUtc(nowInNY, nyTZ);

Альтернативным решением будет создавать и хранить дату только в UTC:
var utc = DateTime.UtcNow;
и преобразовывать её в локальную:
var localTime = utc.ToLocalTime();
или в нужный часовой пояс:
var nyTime = TimeZoneInfo
.ConvertTimeFromUtc(utc, nyTZ);

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

В .NET 2 была введена структура DateTimeOffset. Она состоит из:
- структуры DateTime
- свойства Offset, хранящего смещение относительно UTC.

Однако проблема с серверами в разных часовых поясах никак не решается через DateTimeOffset.Now. Всё дело в переходе на летнее время. Для разных дат смещение в одном и том же часовом поясе будет разным. Кроме того, правила перехода на летнее время могут меняться (нам ли в РФ не знать). А значит невозможно определить происхождение даты и времени, просто взглянув на значение смещения. Поэтому для международного ПО лучше хранить часовой пояс для возможного пересчёта смещения по новым правилам. Для этого подходит класс TimeZoneInfo.

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

Источник:
https://www.infoq.com/articles/dotnet-unit-tests-time-timezone/
👍18
День 1620. #ЗаметкиНаПолях #ЧтоНовенького
Время в .NET. Окончание
Начало

Юнит-Тесты
Для тестирования важно иметь возможность имитировать вызов метода объекта с пользовательской реализацией. Поскольку DateTime и DateTimeOffset не имеют интерфейсов, можно создать пользовательскую абстракцию, а затем имитировать её во время тестов. Например, следующий интерфейс может предоставить абстракцию для DateTimeOffset:
public interface ISystemClock
{
DateTimeOffset UtcNow { get; }
}

Аналогичный подход использовался внутри Microsoft, где один и тот же код был добавлен как минимум в четыре различных области .NET.

В .NET 8 были добавлены долгожданные абстракции времени: абстрактный класс TimeProvider и интерфейс ITimer. Они не безупречны, но это значительный прогресс.

Недостатки TimeProvider
1. Абстрактный класс получился громоздким. Не зная внутренних деталей класса, зависящего от TimeProvider, невозможно решить, какие его члены надо имитировать: GetUtcNow(), GetLocalNow(), CreateTimer(...) или все сразу. Разработчики предложили разбить новый тип на небольшие интерфейсы, но идея была отвергнута.

2. Экземпляр TimeProvider можно создать с помощью статического члена TimeProvider.System. Его легко использовать, хотя он не сильно отличается от DateTime.Now. В дальнейшем это приведёт к проблемам с написанием юнит-тестов, поэтому ожидается, что разработчики будут внедрять абстрактный TimeProvider в свои классы.

Преимущества TimeProvider и ITimer
1. Юнит-тесты сервисов, зависящих от времени, стали более универсальными. TimeProvider был добавлен в BCL и поддерживается в широком спектре сред выполнения .NET.

2. Команда Microsoft исправила в новой реализации старую ошибку, DateTime.Now было ошибочно введено как свойство вместо функции DateTime.Now(), поскольку по рекомендации Microsoft свойства не должны иметь побочных эффектов. TimeProvider правильно использует функции и методы: GetUtcNow(), GetLocalNow(), GetTimestamp() и т. д.

3. Можно тестировать события временных рядов с помощью функций TimeProvider.CreateTimer(...) и Timer.Change(...). Это особенно важно для вызовов функций Task.Delay(...) и Task.WaitAsync(...), которые теперь также принимают аргумент TimeProvider.

4. Планируется создать FakeTimeProvider как часть .NET для дальнейшего упрощения юнит-тестирования. Возможно, тогда пункт 2 из недостатков потеряет актуальность.

Стивен Тауб, инженер-программист Microsoft, надеется, что «в будущем почти никто не будет использовать ничего, кроме TimeProvider.System, в производственной среде. В отличие от многих других абстракций, эта особенная: она существует исключительно для возможности тестирования.»

Итого
Добавление класса TimeProvider в 4м превью .NET 8 определяет стандартизированную и унифицированную абстракцию для управления временем. Хотя он имеет ряд незначительных недостатков, команды Microsoft уже пометили свои интерфейсы времени ISystemClock как устаревшие и теперь выступают за внедрение TimeProvider.

Источник: https://www.infoq.com/articles/dotnet-unit-tests-time-timezone/
👍7
День 1621. #Оффтоп
Продолжая тему времени последних двух дней, сегодня порекомендую вам интересное видео от нашего старого знакомого системного инженера Microsoft, Дейва Пламера. В новом видео он рассказывает, почему системные часы Windows до недавних версий системы не отображали секунды.

https://youtu.be/qe1ltXdKMow

Лично я, помимо просто интересной истории и некоторых деталей реализации Windows, почерпнул для себя полезную идею о том, как правильно отображать «живое время» на сайте. В общем, не буду спойлерить, посмотрите, мне понравилось.
👍7
День 1622. #ProjectManagement
Управляем Здоровьем Команды, Техническим Долгом и Предотвращаем Переписывание с Нуля. Начало
Обычно бывает так: опытная команда блестящих инженеров запускает новый продукт, клиентам нравится, фичи релизятся как из автомата. А спустя какое-то время внезапно продуктивность падает, выпускать новые функции всё сложнее, клиенты недовольны, инженеры устают от работы больше, чем когда-либо прежде, и призывают переписать всё с нуля. Время паники. Что случилось?

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

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

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

Хорошая практика №1: постоянные обзоры архитектуры
Обзоры архитектуры распространены в начале проекта. Сначала команда придерживается надёжных архитектурных принципов, система не слишком сложна в обслуживании и т. д. Как только выходит версия 1.0, команда обычно прекращает рассмотрение новых функций с точки зрения влияния на архитектуру в пользу быстрой доставки. Это компромисс. Нет времени пересматривать архитектурные изменения. Но правильно ли это? Понимаем ли мы растущую систему?

Что делать?
Необходим официальный анализ архитектуры каждой новой функции среднего и крупного размера. Назначьте команде фиксированное время для рассмотрения влияния на архитектуру всех новых функций. 3-5 страниц или слайдов обзора с 1-3 диаграммами архитектуры вполне достаточно. Включите требование о проведении архитектурного анализа во все планы проекта. Все участвуют в рассмотрении, и все оценивают.

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

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

Источник:
https://betterprogramming.pub/managing-team-health-tech-debt-and-avoiding-the-dreaded-rewrite-94878da7ca43
👍13👎1
День 1623. #ProjectManagement
Управляем Здоровьем Команды, Техническим Долгом и Предотвращаем Переписывание с Нуля. Продолжение
Начало

Хорошая практика №2: Журнал ошибок и постоянные исправления
Журнал ошибок указывает на проблемы продукта. Разберитесь со списком ошибок. Это требует времени, но сделайте это всё равно.

Обработка ошибок – принудительная функция, как соглашение об уровне обслуживания (SLA), в котором мы объявляем обязательство по исправлению ошибок перед с нашими внутренними клиентами. И не все ошибки равны. Некоторые более сложны или более важны:
1. Система неработоспособна
- Затронуты все пользователи: Критическая ошибка - Исправлять немедленно!
- Некоторые пользователи: Серьёзная – 3-5 часов.
- Мало пользователей: Средняя – 2-3 дня.
2. Система сильно повреждена
- Все: Серьёзная – 3-5 часов.
- Некоторые: Средняя – 2-3 дня.
- Мало: Средняя – 2-3 дня.
3. У пользователей есть альтернативное решение
- Все: Серьёзная – 3-5 часов.
- Некоторые: Незначительная – 1-2 недели.
- Мало: В период исправления ошибок – 1 месяц.
4. Не влияет на систему
- Все: Незначительная – 1-2 недели.
- Некоторые: В период исправления ошибок – 1 месяц.
- Мало: Можно игнорировать

Журнал ошибок должен содержать список ошибок с их уровнем критичности. Кроме того, за состоянием проекта можно следить, отмечая на графике оценку количества ошибок с коэффициентом их критичности:
- Критическая – 2х
- Серьёзная – 1,5х
- Средняя – 1х
- Незначительная – 0,5х
Тогда, если на 1й неделе случилось 5 средних и одна серьёзная ошибка – это 5*1 + 1*1,5 = 6,5. На второй неделе 1 серьёзная и 6 незначительных – 1*1,5 + 6*0,5 = 4,5. Со временем этот график будет показывать состояние продукта.

Что делать?
- Определите SLA ошибки. Каждый продукт и каждая команда отличаются, но мы можем установить внутренние ожидания в отношении времени обработки для серьёзных, но не критических проблем. Мы быстро исправим серьёзные ошибки, а ошибки с более низким приоритетом будут исправлены или подвергнуты рефакторингу позже.
- Каждый спринт члены команды просматривают журнал ошибок и назначают исправления, чтобы обеспечить постоянный цикл исправления и сокращения количества ошибок.
- Сообщайте об исправлениях ошибок в заметках о выпуске, публикации во внутреннем чате или в каком-либо другом средстве. Например, сделайте «день выпуска отчёта об ошибках» и публикуйте этот отчёт.
- Используйте простые формулы для расчёта количества ошибок и удерживайте количество ошибок как можно более равномерным спринт за спринтом. Отслеживайте это желательно на общих дашбордах о состоянии проекта.

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

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

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

Источник:
https://betterprogramming.pub/managing-team-health-tech-debt-and-avoiding-the-dreaded-rewrite-94878da7ca43
👍9
День 1624. #ProjectManagement
Управляем Здоровьем Команды, Техническим Долгом и Предотвращаем Переписывание с Нуля. Окончание
Начало
Продолжение

Хорошая практика №3: дисциплинированное исправление/рефакторинг
Организуйте ротацию дежурных в момент отправки продукта. Несмотря на то, что в продукте мало ошибок, может быть, мало клиентов и нет шансов выхода из строя под нагрузкой, назначение дежурных и их ротация заранее запускает «мышечную память».

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

Что делать?
- Установите ротацию в тот момент, когда пользователи продукта смогут получить доступ к продукту. Наличие пользователей – это сообщения об ошибках.
- Установите разумную продолжительность (1 неделя, 1 спринт), чёткие процедуры передачи задач и расписание, чтобы все знали, когда они будут дежурить.
- Дежурство — это устранение инцидентов, исправление ошибок, снижение сложности кода (рефакторинг), а только затем работа над новыми функциями. Если бэклог ошибок заполняет всё дежурство, этот инженер не работает над новыми функциями.
- Не назначайте дежурному инженеру ничего, кроме дежурных задач.

Зачем?
- Команда методично устраняет ошибки, и технический долг стабилизируется.
- Команда выполняет SLA по ошибкам — внутренние и внешние клиенты довольны.
- Более стабильный продукт => меньше пожаров в продакшене и меньше простоев.
- Младшие инженеры получают возможность исправлять ошибки во всех частях системы, быстрее повышая свой уровень.
- Ещё одна система раннего предупреждения о здоровье команды. Если дежурство занято сложными, серьёзными ошибками, мы узнаём о проблемах сразу, а не через несколько месяцев.
Помните о компромиссе: команда предоставит меньше функций за счёт более стабильного продукта. Устанавливайте ожидания относительно ротации дежурных не в команде, а в организации в целом.

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

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

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

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

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

Источник: https://betterprogramming.pub/managing-team-health-tech-debt-and-avoiding-the-dreaded-rewrite-94878da7ca43
👍8
День 1625. #ЧтоНовенького
Превью Новых Функций C# 12
В превью 6 .NET 8 продолжилась эволюция языка C#12. Эта версия превью включает функции, призванные заложить основу для будущих улучшений производительности.

1. nameof получил доступ к членам экземпляра
Ключевое слово nameof теперь работает с именами членов класса, включая инициализаторы, статические члены и атрибуты:
internal class NameOf
{
public string S { get; } = "";
public static int StaticField;
public string NameOfLength { get; }
= nameof(S.Length);
public static void NameOfExamples()
{
Console.WriteLine(nameof(S.Length));
Console.WriteLine(nameof(StaticField.MinValue));
}

[Description($"String {nameof(S.Length)}")]
public int StringLength(string s)
{ return s.Length; }
}

2. Встроенные массивы
Атрибут InlineArrayAttribute идентифицирует тип, который можно рассматривать как непрерывную последовательность примитивов для эффективных, типобезопасных, защищённых от переполнения индексируемых/разрезаемых встроенных данных. Библиотеки .NET повысят производительность приложений и инструментов, используя встроенные массивы.

Компилятор создаёт другой IL для доступа к встроенным массивам. Это приводит к некоторым ограничениям, таким как отсутствие поддержки шаблонов списков. Но в большинстве случаев вы сможете получить доступ к встроенным массивам так же, как и к обычным. Также другой IL обеспечивает прирост производительности без изменения вашего кода:
private static void InlineArrayAccess(
Buffer10<int> arr)
{
for (int i = 0; i < 10; i++)
{
arr[i] = i * i;
}
foreach (int i in arr)
{
Console.WriteLine(i);
}
}

В большинстве случаев вы будете потреблять встроенные массивы, а не создавать их. Встроенные массивы работают быстро, потому что они основаны на точном расположении данных заданной длины. Встроенный массив — это тип с одним полем, помеченный атрибутом InlineArrayAttribute, указывающим длину массива. В типе, использованном в предыдущем примере, среда выполнения создает хранилище ровно для десяти элементов в Buffer10<T> благодаря параметру атрибута:
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer10<T>
{
private T _element0;
}

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

Источник: https://devblogs.microsoft.com/dotnet/new-csharp-12-preview-features/
👍9
День 1626. #ЗаметкиНаПолях
Отменяем Рабочий Процесс GitHub при Новом Коммите в Ветке
Когда вы отправляете несколько коммитов в ветку, вы можете отменить текущие рабочие процессы и запустить только последний рабочий процесс. Это может быть полезно для снижения затрат, если вы используете платные сервисы. А в бесплатном варианте GitHub ограничивает количество одновременных заданий. Таким образом, это также поможет высвобождать ресурсы для других рабочих процессов.

Чтобы автоматически отменять незавершённые запуски, вы можете использовать функцию параллелизма (concurrency). Задайте имя группы рабочих процессов, затем вы можете настроить группу на отмену выполняемых запусков при запуске нового рабочего процесса. Вот пример:
name: sample
on:
push:
branches:
- '*'
pull_request:
branches:
- '*'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

Здесь:
- github.workflow – имя рабочего процесса,
- github.event.pull_request.number || github.ref – номер пул-реквеста или имя ветки, если это не пул-реквест,
- cancel-in-progress отменяет рабочий процесс, если новый рабочий процесс с тем же именем запущен в той же группе.

Подробнее о выражениях в GitHub Actions
Подробнее о переменных в GitHub Actions

Если вы хотите отменять незавершённые запуски только для веток, отличных от main, вы можете использовать следующую конфигурацию:
concurrency:
group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/main' && github.run_id || github.event.pull_request.number || github.ref }}
cancel-in-progress: true

Здесь:
- используем github.run_id ветки main,
- используем github.event.pull_request.number пул-реквестов, т.к. номер уникален для каждого пул-реквеста,
- используем github.ref на других ветках, т.к. он уникален для каждой ветки.

Вместо жесткого кодирования ветки main вы можете использовать github.ref_protected, чтобы избежать отмены заданий в защищённых ветках.

Источник: https://www.meziantou.net/how-to-cancel-github-workflows-when-pushing-new-commits-on-a-branch.htm
👍10
День 1627. #ЧтоНовенького
Новинки Blazor в Превью 6 .NET 8
1. Привязка и проверка модели формы с серверным рендерингом
Новый режим серверного рендеринга Blazor теперь может моделировать привязку и проверять значения POST-формы HTTP. Чтобы связать данные из запроса формы, примените атрибут [SupplyParameterFromForm] к свойству компонента. Данные в запросе, соответствующие имени свойства, будут привязаны к свойству. Свойство может быть примитивного типа, сложного типа, коллекцией или словарём. Также поддерживается проверка на стороне сервера с использованием аннотаций данных. Если на странице несколько форм, их можно различить с помощью параметра Name, и вы можете использовать свойство Name в [SupplyParameterFromForm], чтобы указать, из какой формы вы хотите привязать данные. Больше не нужно настраивать компонент CascadingModelBinder, чтобы включить привязку модели. Теперь он настраивается автоматически.

2. Улучшенная навигация по страницам и обработка форм
Blazor будет перехватывать запрос, чтобы применить ответ к существующей модели DOM, максимально сохраняя её. Улучшение устранит необходимость полной загрузки страницы и обеспечит UI аналогичный одностраничному приложению (SPA), даже при том, что приложение обрабатывается на стороне сервера.
В этом предварительном выпуске улучшенная навигация и обработка форм ещё не совместимы с одновременным наличием на странице интерактивных (серверных или WebAssembly) компонентов. Это ограничение будет устранено перед релизом .NET 8.

3. Сохранение существующих элементов DOM с помощью потокового рендеринга
Потоковая отрисовка Blazor теперь будет сохранять существующие элементы DOM при потоковой передаче обновлений на страницу, что обеспечит более быстрое и плавное взаимодействие с пользователем.

4. Указание режима рендеринга компонента
Теперь можно указать режим рендеринга для экземпляра компонента, используя атрибут @rendermode. Режим рендеринга будет применяться к компоненту и его дочерним элементам:
<Counter @rendermode="@RenderMode.Server" />

5. Интерактивный рендеринг в Blazor WebAssembly
Чтобы включить поддержку режима рендеринга WebAssembly в проекте Blazor, добавьте связанные службы, вызвав app.Services.AddRazorComponents().AddWebAssemblyComponents(), и режим рендеринга WebAssembly, вызвав app.MapRazorComponents<App>().AddWebAssemblyRenderMode(). Любые компоненты, которые вы хотите визуализировать в WebAssembly, должны быть загружены вместе со всеми их зависимостями в браузер. Вам потребуется настроить отдельный проект Blazor WebAssembly для создания любого кода, специфичного для WebAssembly, и ссылаться на него из приложения Blazor.

Вы можете указать режим интерактивной отрисовки WebAssembly для компонента, добавив атрибут [RenderModeWebAssembly] в определение компонента или указав @rendermode="@RenderMode.WebAssembly" в экземпляре компонента. Компоненты, которые вы настроили для рендеринга в WebAssembly, также будут предварительно визуализированы с сервера по умолчанию, поэтому обязательно либо создайте свои компоненты, чтобы они правильно отображались в любой среде, либо отключите предварительный рендеринг при указании режима рендеринга: [RenderModeWebAssembly(prerender: false)] или @rendermode="@(new WebAssemblyRenderMode(prerender: false)).

Вот пример, демонстрирующий, как настроить интерактивность на основе WebAssembly для компонента Counter, отображаемого на странице Index.

В настоящее время существует ограничение, при котором компоненты с маршрутами должны быть определены в той же сборке, что и компонент приложения, переданный в MapRazorComponents<App>(), поэтому в настоящее время их нельзя определить в клиентской сборке. Это будет исправлено в будущем обновлении.

Источник: https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-6/
👍8
День 1628. #Оффтоп
Сегодня поделюсь с вами выступлением моего любимого докладчика Дилана Бити, которое он сделал на конференции NDC Осло в июне. Дилан всегда восхищал меня своими докладами. Это одновременно интересная тема, увлекательная подача и искромётное чувство юмора. Этот доклад – не исключение. Дилан рассказывает о том, что такое электронная почта.

Мы не совсем уверены, когда именно была изобретена электронная почта. Где-то в 1971 году. Мы точно знаем, когда был изобретен спам: 3 мая 1978 года, когда Гэри Терк разослал по электронной почте 400 людям рекламу компьютеров DEC. Это очень разозлило многих людей... но также было продано несколько компьютеров, и так родилась нежелательная электронная почта.

Перенесемся на полвека вперёд: отношения между электронной почтой и коммерцией никогда не были такими сложными. В каком-то смысле утопический идеал свободной, децентрализованной электронной коммуникации стал реальностью. Электронная почта — это лучший кросс-сетевой и кросс-платформенный протокол связи. В другом смысле это гонка вооружений: почтовые провайдеры и интернет-провайдеры внедряют всё более строгие проверки и политики для предотвращения нежелательной почты, и если это означает, что важное сообщение случайно отправляется в папку «Спам», то ничего страшного… пока вы не сталкиваетесь с тем, что, рассылая билеты на мероприятие, обнаруживаете, что каждая компания, использующая Mimecast, решила, что ваш почтовый ретранслятор отправляет спам. Команде по маркетингу нужны красивые, красочные, отзывчивые электронные письма, но почтовые программы их клиентов всё ещё используют HTML 3.2, который даже не поддерживает CSS. И это уже не говоря о проблемах дизайна электронного письма, когда половина ваших читателей будет использовать «тёмный режим», и весь красивый дизайн на белом фоне идёт псу под хвост.

Электронная почта слишком объёмна, чтобы её можно было изменить, слишком сломана, чтобы её починить… и слишком важна, чтобы её игнорировать. В докладе Дилан разберёт, что нужно знать, чтобы сделать это правильно: DNS, записи MX, DKIM и SPF, как на самом деле работает MIME, что такое Papercut, Mailtrap, Mailjet, Foundation и как включить их в процесс разработки. Современная электронная почта — это костыли поверх костылей, поверх более старых костылей. Поэтому так интересно узнать, как это всё на самом деле работает.

https://youtu.be/mrGfahzt-4Q

Кому нравится докладчик, вот плейлист с его выступлениями.
👍11
День 1629. #ЗаметкиНаПолях
Создаём API-Шлюз для Микросервисов с Помощью YARP. Начало
Большие системы на основе микросервисов могут состоять из десятков отдельных сервисов. Клиентское приложение должно иметь всю эту информацию, чтобы иметь возможность делать запросы к соответствующему микросервису напрямую. Это влечёт множество проблем с безопасностью, повышением сложности и связанности.

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

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

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

Установка и настройка YARP
YARP — библиотека Microsoft для удовлетворения потребностей различных команд, которым необходимо создать обратный прокси-сервер, прекрасно интегрирующийся в существующую экосистему .NET.
После установки NuGet-пакета:
Install-Package Yarp.ReverseProxy

необходимо вызвать:
- AddReverseProxy для добавления необходимых сервисов YARP,
- LoadFromConfig для загрузки конфигурации из настроек приложения,
- MapReverseProxy для внедрения промежуточного ПО обратного прокси.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
.LoadFromConfig(
builder.Configuration
.GetSection("ReverseProxy"));

var app = builder.Build();
app.MapReverseProxy();
app.Run();

Нужно сообщить YARP, как направлять входящие запросы к отдельным микросервисам. YARP использует концепцию маршрутов для описания шаблонов запросов для прокси-сервера и кластеров для описания сервисов, к которым надо перенаправить эти запросы.
Вот пример конфигурации YARP. В системе есть две службы, Users.Api и Products.Api, которые являются приложениями .NET 7. Если запрос соответствует /users-service/{**catch-all}, например, /users-service/users, он будет перенаправлен в кластер пользователей. Та же логика применима к кластеру продуктов. Мы можем применить более сложные преобразования в разделе Transforms.
{
"ReverseProxy": {
"Routes": {
"users-route": {
"ClusterId": "users-cluster",
"Match": {
"Path": "/users-service/{**catch-all}"
},
"Transforms": [
{ "PathPattern": "{**catch-all}" }
]
},
"products-route": {
"ClusterId": "products-cluster",
"Match": {
"Path": "/products-service/{**catch-all}"
},
"Transforms": [
{ "PathPattern": "{**catch-all}" }
]
}
},
"Clusters": {
"users-cluster": {
"Destinations": {
"destination1": {
"Address": "https://localhost:5201/"
}
}
},
"products-cluster": {
"Destinations": {
"destination1": {
"Address": "https://localhost:5101/"
}
}
}
}
}
}

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

Источник:
https://www.milanjovanovic.tech/blog/implementing-an-api-gateway-for-microservices-with-yarp
👍15
День 1630. #ЗаметкиНаПолях
Создаём API-Шлюз для Микросервисов с Помощью YARP. Окончание
Начало

Что ещё можно делать в YARP?

1. Аутентификация
API-шлюз может применять аутентификацию и авторизацию в точке входа в систему, прежде чем разрешить выполнение аутентифицированных запросов. Сначала нужно определить политику авторизации:
builder.Services.AddAuthorization(opts =>
{
opts.AddPolicy("authenticated", p =>
p.RequireAuthenticatedUser());
});

Затем вызвать UseAuthentication и UseAuthorization. Важно это сделать до вызова MapReverseProxy.
app.UseAuthentication();
app.UseAuthorization();
app.MapReverseProxy();

Теперь только остаётся добавить раздел AuthorizationPolicy в конфигурацию прокси:
"users-route": {
"ClusterId": "users-cluster",
"AuthorizationPolicy": "authenticated",
"Match": {

}
}
YARP может пересылать большинство типов данных авторизации (cookie, bearer-токены) в защищённые сервисы, поскольку может быть важно по-разному идентифицировать пользователя в отдельных микросервисах.

2. Добавление ограничений
API-шлюз можно использовать для ограничений в системе. Это метод ограничения количества запросов к вашему API для повышения безопасности и снижения нагрузки на серверы. YARP поддерживает механизм ограничений, добавленный в .NET 7.

Для этого надо определить политику ограничения скорости:
builder.Services.AddRateLimiter(opts =>
{
opts.AddFixedWindowLimiter("fixed", o =>
{
o.Window = TimeSpan.FromSeconds(10);
o.PermitLimit = 5;
});
});

Затем нужно вызвать UseRateLimiter перед вызовом MapReverseProxy.
app.UseRateLimiter();
app.MapReverseProxy();

И добавить политику ограничений в раздел RateLimiterPolicy конфигурации:
"products-route": {
"ClusterId": "products-cluster",
"RateLimiterPolicy": "fixed",
"Match": {

}
}

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

Подробнее об использовании YARP можно почитать в документации.

Источник: https://www.milanjovanovic.tech/blog/implementing-an-api-gateway-for-microservices-with-yarp
👍13
День 1631. #TipsAndTricks #Frontend
Как Обнаружить Ненужную Отрисовку Элементов DOM в Веб-Приложении
Давненько я не писал ничего про фронтенд. А ведь я фуллстек разработчик всё-таки. Поэтому сегодня небольшой лайфхак про фронт.

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

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

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

На помощь придут инструменты разработчика Chrome (F12). Нажмите на гамбургер-меню (три вертикальные точки) > More tools (Другие инструменты) > Rendering (Отрисовка) > Paint flashing (Индикатор замены отображения).

Теперь каждая перерисовка элементов DOM на странице будет выделяться зелёным (заметьте, что это не работает на примере по ссылке выше, из-за эмуляции страницы результатов, но будет работать в реальном приложении). Браузер будет подсвечивать все перерисовки страницы, включая прокрутки, изменения при наведении мыши, анимации и т.п. Так вы можете легко определить элементы, которые перерисовываются, хотя не должны этого делать.

В разделе Rendering (Отрисовка) инструментов разработчика есть и другие полезные инструменты. В частности, мне пригодился Scrolling performance issues (Проблемы производительности при прокрутке).

Источник: https://dev.to/maxime1992/how-to-detect-unnecessary-renderings-of-dom-elements-in-your-web-app-to-improve-performances-13jd
👍10
День 1632. #ЗаметкиНаПолях
Разработка API для Людей.
Часть 3. Принципы разработки. Начало

Часть 1
Часть 2

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

При проектировании API следует помнить о трёх вещах:
- Кто является целевой аудиторией?
- Насколько настраиваемым должен быть API?
- От какой доступности вы готовы отказаться ради настраиваемости?

1. Целевая аудитория
Как правило, API предназначены для опытных пользователей. Нужны ли вам новички? Можно сделать API слишком простым для взаимодействия и позволить пользователю уверенно принимать многие решения самостоятельно.
Если ваш продукт нишевый и используется только подмножеством разработчиков (например, только разработчиками корпоративных приложений на определённой платформе) полагаетесь ли вы на объектные модели и языковые конструкции, которые они используют?

2. Настраиваемость
Мощный API предоставляет множество возможностей конечным пользователям, например, возможность настраивать различные факторы запросов. Это происходит в ущерб доступности, поскольку чем больше опций, тем сложнее становится API. Сложный API означает, что вы, скорее всего, столкнётесь с проблемами при попытке построить свою интеграцию: придётся принимать сложные решения, которые могут иметь последствия для будущих версий вашего проекта, возможно, не имея достаточного опыта.
Простые API предоставляют один маршрут «счастливого пути» и могут быть изучены за считанные минуты, но они ограничены. Мощные API, где пользователь имеет полный контроль над каждым аспектом, подстраивая API под свои нужды, сопряжены с крутой кривой обучения и уязвимы для любых будущих изменений в API. Важно быть где-то посередине, возможно, склоняясь к менее сложному API. В идеале «счастливый путь» — это путь интеграции, который охватывает почти все варианты использования ваших пользователей, предоставляя некоторое пространство для манёвра тем опытным пользователям, у которых есть специфические потребности.

3. От какой доступности отказаться ради настраиваемости?
Это во многом будет зависеть от вопроса, кто является целевой аудиторией. Вы можете превратить простой API в мощный и сложный, однако почти невозможно сделать обратное, не начав с нуля. Начните с простого, а затем наращивайте сложность, но старайтесь не загонять себя в угол, из которого потом будет трудно выбраться.

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

Источник:
https://dev.to/stripe/designing-apis-for-humans-design-patterns-5847
👍5