.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
День 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
День 1633. #ЗаметкиНаПолях
Разработка API для Людей.
Часть 3. Принципы разработки. Окончание
Начало

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

Раннее определение принципов разработки открывает ряд огромных преимуществ, которые помогут увеличить долговечность и скорость разработки вашего API.

1. Будьте последовательны
Наиболее распространёнными операциями для API являются запрос на получение ресурса (GET) и на создание или обновление ресурса (POST).
// Создание продукта
POST /v1/products

// Обновление продукта
POST /v1/products/:id

// Получение продукта
GET /v1/products/:id

В примере выше маршрут для получения продукта позволяет получать только один продукт за раз. Это довольно неудобно. Добавление конечной точки для получения списка продуктов предоставит пользователю гибкость и возможность сократить количество запросов к API:
// Получаем список продуктов
GET /v1/products

То же касается других ресурсов, таких как клиенты (Customers). Как разработчик, вы хотите, чтобы ваш API был максимально предсказуемым, позволяя пользователям угадывать, какая комбинация HTTP-команд и конечных точек сработает, основываясь на предыдущем опыте работы. Будьте строго последовательны: новые маршруты должны работать во многом так же, как и существующие. Это не только позволит пользователям быстрее освоить API, но и даст ощущение хорошо продуманного, интуитивно понятного интерфейса.

2. Будьте интуитивно понятны
Предположим, у нас есть объект ресурса Customer, который принимает 3 поля: имя, email и адрес. Сначала мы создаём клиента:
// Запрос
POST /v1/customers
{ "name": "John Watson",
"email": "[email protected]",
"address": "221B Baker Street" }

// Ответ
{
"id": "cus_123",
"object": "customer",
"name": "John Watson",
"email": "[email protected]",
"address": "221B Baker Street"
}

Затем мы решили обновить клиента:
// Запрос
POST /v1/customers/cus_123
{ "address": "London SW1A 1AA" }

// Ответ
{
"id": "cus_123",
"object": "customer",
"name": undefined,
"email": undefined,
"address": "London SW1A 1AA"
}

В этом запросе мы просто обновляем существующий адреса клиента. Но куда делись значения для имени и email? API сделал именно то, что ему было сказано. Он обновил значение адреса, но, поскольку значения имени и email отсутствовали в запросе на обновление, он интерпретировал их отсутствие как запрос на обнуление. Формально это правильно, но является чётким показателем того, что этот API не был разработан для людей. Вместо того, чтобы проверять, был ли передан параметр, разработчики этого API обновляют объект значением атрибута, независимо от того, был он предоставлен или нет.

API преуспевают благодаря своей интуитивности. Операции, подобные описанным выше, должны «просто работать» на основе общего предположения, а не на склонности компьютера делать именно так, как ему было сказано.

Источник: https://dev.to/stripe/designing-apis-for-humans-design-patterns-5847
👍13
День 1634. #юмор
👍24
День 1635. #ЧтоНовенького
Метрики в .NET 8
Метрики — это измерения, которые собираются с течением времени и чаще всего используются для мониторинга работоспособности приложения и создания предупреждений. Например, счётчик, сообщающий о неудачных HTTP-запросах, может отображаться на информационных панелях или генерировать оповещения, когда количество сбоев превышает пороговое значение.

В превью .NET 8 в ASP.NET Core добавлены:
- новые виды измерений со счетчиками, датчиками и гистограммами;
- мощные отчёты с многомерными значениями;
- интеграция в более широкую облачную экосистему за счёт соответствия стандартам OpenTelemetry;
- добавлены метрики для хостинга ASP.NET Core, Kestrel и SignalR.

Также ASP.NET Core теперь использует API IMeterFactory для создания и получения счётчиков. Это позволяет использовать внедрение зависимостей, что упрощает изоляцию и сбор метрик. IMeterFactory особенно полезен для юнит-тестов, когда несколько тестов могут выполняться параллельно и собирать данные только для своего теста:
public class BasicTests :
IClassFixture<WebApplicationFactory<Program>>
{
private WebApplicationFactory<Program> factory;
public BasicTests(
WebApplicationFactory<Program> f)
=> factory = f;

[Fact]
public async Task GetRequest()
{
var mf = factory.Services
.GetRequiredService<IMeterFactory>();
var col = new MetricCollector<double>(
mf,
"Microsoft.AspNetCore.Hosting",
"http-server-request-duration"
);
var cl = factory.CreateClient();

var resp = await cl.GetAsync("/");

Assert.Equal("Hello World!",
await resp.Content.ReadAsStringAsync());

await col.WaitForMeasurementsAsync(
minCount: 1);
Assert.Collection(col.GetMeasurementSnapshot(),
m => {
Assert.Equal("http", m.Tags["scheme"]);
Assert.Equal("GET", m.Tags["method"]);
Assert.Equal("/", m.Tags["route"]);
});
}
}

Новые счетчики в превью 6
- routing-match-success и routing-match-failure сообщают, как запрос был перенаправлен в приложении. Если запрос успешно соответствует маршруту, счётчик включает информацию о шаблоне маршрута и о том, был ли это резервный маршрут.
- diagnostics-handler-exception добавляется к ПО обработки исключений и сообщает, как оно обрабатывает необработанные исключения, включая информацию об имени исключения и результате обработки.
- http-server-unhandled-requests — сообщает, когда HTTP-запрос достигает конца конвейера промежуточного ПО и не обрабатывается приложением.
- Различные новые счётчики промежуточного ПО ограничений позволяют отслеживать количество прошедших запросов, запросов в очереди, продолжительность очереди и многое другое.

Если вам интересно попробовать метрики, разработчики .NET собрали панели мониторинга Grafana, которые сообщают о метриках ASP.NET Core, собранных Prometheus в репозитории aspnetcore-grafana

Источники:
-
https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-6/
-
https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-4/
👍14
День 1636. #DDD
Использование Доменных Событий для Построения Слабосвязанных Систем. Начало
В программной инженерии «связанность» означает, насколько разные части программной системы зависят друг от друга. Если они тесно связаны, изменения в одной части могут повлиять на другие. Если слабо, изменения в одной части не вызовут больших проблем в остальной системе. Доменные события — это тактический шаблон DDD, который можно использовать для создания слабосвязанных систем.

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

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

Особенности
- Неизменяемость — доменные события являются фактами и должны быть неизменными.
- Толстые и Тонкие события — сколько информации вам нужно передавать?
- Используйте прошедшее время для именования событий.

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

Интеграционные события:
- потребляются другими подсистемами (микросервисы, ограниченные контексты);
- отправляются брокером сообщений через очередь;
- обрабатываются только асинхронно.

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

Реализация
Реализовать доменные события можно через создание абстракции IDomainEvent и реализацию INotification в MediatR. Так вы можете использовать поддержку публикации-подписки в MediatR для публикации уведомления одному или нескольким обработчикам.
using MediatR;
public interface IDomainEvent : INotification
{
}

public class CourseCompleted : IDomainEvent
{
public Guid CourseId { get; init; }
}

Вызов
Только сущности могут вызывать доменные события, поэтому создадим базовый класс Entity, сделав метод RaiseDomainEvent защищённым. Доменные события хранятся во внутренней коллекции, чтобы никто не мог получить к ним доступ. GetDomainEvents предназначен для получения моментального снимка коллекции, ClearDomainEvents — для очистки.
public abstract class Entity : IEntity
{
private readonly List<IDomainEvent>
_events = new();

public IReadOnlyList<IDomainEvent>
GetDomainEvents() => _events.ToList();

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

protected void RaiseDomainEvent(
IDomainEvent domainEvent)
=> _events.Add(domainEvent);
}

Теперь сущности могут наследовать от Entity и вызывать доменные события:
public class Course : Entity
{
public Guid Id { get; private set; }
public CourseStatus Status { get; private set; }
public DateTime? Completed { get; private set; }

public void Complete()
{
Status = CourseStatus.Completed;
Completed = DateTime.UtcNow;

RaiseDomainEvent(
new CourseCompleted {
CourseId = this.Id
});
}
}

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

Источник:
https://www.milanjovanovic.tech/blog/using-domain-events-to-build-loosely-coupled-systems
👍16
День 1637. #DDD
Использование Доменных Событий для Построения Слабосвязанных Систем. Окончание
Начало

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

Можно либо переопределить метод SaveChangesAsync, либо использовать перехватчик:
public class ApplicationDbContext : DbContext
{
public override async Task<int> SaveChangesAsync(
CancellationToken ct = default)
{
var result = await
base.SaveChangesAsync(ct);

await PublishDomainEventsAsync();

return result;
}
}

Важное решение: публиковать доменные события до или после вызова SaveChangesAsync (сохранения данных в БД)?
До:
- события являются частью той же транзакции,
- немедленная согласованность данных.
После:
- события – отдельная транзакция,
- конечная согласованность, т.к. сообщения обрабатываются после исходной транзакции,
- риск несогласованности БД, т.к. обработка события может привести к сбою.

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

Метода PublishDomainEventsAsync:
private async Task PublishDomainEventsAsync()
{
var events = ChangeTracker
.Entries<Entity>()
.Select(e => e.Entity)
.SelectMany(ent =>
{
var evnts = ent.GetDomainEvents();
ent.ClearDomainEvents();
return evnts;
})
.ToList();

foreach (var ev in events)
await _mediator.Publish(ev);
}

Обработка
Нужно определить класс, реализующий INotificationHandler<T>, где T – тип доменного события. Обработчик доменного события CourseCompleted ниже публикует CourseCompletedIntegrationEvent для уведомления других систем.
public class CourseCompletedDomainEventHandler
: INotificationHandler<CourseCompleted>
{
private readonly IBus _bus;

public CourseCompletedDomainEventHandler(IBus bus)
{
_bus = bus;
}

public async Task Handle(
CourseCompleted de,
CancellationToken ct)
{
await _bus.Publish(
new CourseCompletedIntegrationEvent(de.CourseId),
ct);
}
}

Итого
- Доменные события могут помочь построить слабосвязанную систему, отделяя основную логику домена от побочных эффектов, которые можно обрабатывать асинхронно.
- Для реализации доменных событий, можно использовать библиотеки EF Core и MediatR.
- Нужно решить, когда публиковать доменные события: до или после сохранения изменений в БД.
- Публикация доменных событий после сохранения изменений в БД и паттерн Outbox для добавления транзакционных гарантий обеспечивают окончательную согласованность, но также бОльшую надёжность.

Источник: https://www.milanjovanovic.tech/blog/using-domain-events-to-build-loosely-coupled-systems
👍12
День 1638. #PostgresTips
Советы по Postgres для Начинающих
Давно я не писал ничего про базы данных. А тут наткнулся на советы по PostgreSQL и решил пройтись по ним, а заодно повторить для себя некоторые моменты, поскольку скоро мне предстоит переход на эту СУБД. Это будет серия постов с тегом #PostgresTips, которые периодически будут появляться на канале.

1. Кортежи — это физические версии строк
Одним из основополагающих аспектов PostgreSQL, который удивляет многих новичков, является концепция кортежей. Попросту говоря, кортеж в Postgres — это физическая версия строки данных. Это означает, что при изменении данных в строке вместо изменения существующих данных Postgres добавляет новую версию этой строки, кортеж. Эта система управления версиями называется MVCC (Multiversion Concurrency Control), и важно понимать её для разработки высокопроизводительных систем.

Вот что происходит во время различных операций записи:
- Когда вы выполняете команду DELETE, она не сразу освобождает место на диске. Вместо этого старый кортеж помечается как мёртвый, но остаётся в БД до тех пор, пока VACUUM не удалит его. Если эти мёртвые кортежи накапливаются и удаляются при очистке больших объёмов, это приводит к раздуванию таблиц и индексов (они занимают гораздо больше места на диске, чем содержат данных).
- Точно так же, когда вы обновляете строку, Postgres не изменяет существующий кортеж. Вместо этого он создаёт новую версию этой строки (новый кортеж) и помечает старую как мёртвую.
- Даже отменённый INSERT создаёт мёртвый кортеж, что может удивить многих. Это означает, что, если вы попытаетесь вставить запись, а затем откатите это действие, кортеж, который должен был быть вставлен, помечается как мёртвый.

Чтобы помочь понять эти концепции, каждая таблица в Postgres имеет скрытые столбцы, которые вы можете посмотреть: ctid, xmin и xmax. ctid представляет расположение кортежа (номер страницы + смещение внутри неё), а xmin и xmax можно рассматривать как «дату (номер транзакции) рождения» и «дату смерти» для кортежей.

Поняв это поведение на раннем этапе, вы будете лучше подготовлены к решению проблем, связанных с дисковым пространством, раздуванием и процессами автоочистки, которые направлены на удаление этих мёртвых кортежей. Вот базовый пример, тривиальный, но очень важный:
pg=# create table t1 as select 1 as id;
SELECT 1
pg=# select ctid, xmin, xmax, * from t1;
ctid | xmin | xmax | id
-------+-------+------+----
(0,1) | 47496 | 0 | 1
(1 row)

pg=# update t1 set id = id where id = 1;
UPDATE 1
pg=# select ctid, xmin, xmax, * from t1;
ctid | xmin | xmax | id
-------+-------+------+----
(0,2) | 47497 | 0 | 1
(1 row)

Мы создали таблицу с одной строкой, проверили расположение живого кортежа этой строки (ctid), а затем сделали обновление, которое логически ничего не делает, т.е. не меняет значение. Но местоположение изменилось с (0,1) (страница 0, смещение 1) на (0,2). Потому что физически Postgres создал новый кортеж — новую версию строки. Понимание этого поведения Postgres поможет вам проектировать системы, работающие более эффективно.

Источник: https://postgres.ai/blog/20230722-10-postgres-tips-for-beginners
👍26👎1