День 1857. #Холивар
Возник у нас тут небольшой спор с Кириллом, автором канала C# Heppard (кстати, подписывайтесь). Кирилл пишет:
И ещё несколько лет назад, я бы тоже посмеялся над мужиком. А тут… В общем, не знаю почему, но решил я выступить адвокатом дьявола)))
Тут надо оговориться, что Кирилл в основном занимается оптимизациями, а я всё-таки фуллстек, и экономия байтиков и наносекунд для меня скорей теоретическая область и «чисто по фану». То есть, я практически этим не занимаюсь, т.к. в моей практике я чаще оптимизирую запросы к БД или UI в браузере. Но…
В принципе, мужик не так уж и не прав. Понятно, что нет серебряной пули, и у всех ситуации разные. Но, как мне кажется, в довольно приличном количестве случаев апгрейд железа – действительно выход. Понятно, что мы - программисты, мы все любим чистый и быстрый код. Хлебом нас не корми, дай реализовать свой супербыстрый и эффективный по памяти вариант сортировки или словаря.
Но давайте перенесёмся на самый высокий уровень – уровень бизнеса. Как говорит Марк Симан: «Не забывайте, что в конечном итоге, вы делаете продукт, который должен решать задачи бизнеса». Так вот, с точки зрения бизнеса, оптимизировать алгоритм или докупить памяти – разницы нет. Всё выражается в деньгах. Надо заплатить за одно или за другое. Если ПМ, техлид или другой менеджер этого уровня прикинет, как говорится, писю к носу, и сравнит, сколько денег будет стоить апгрейд железа, и сколько примерно надо потратить человекочасов (умноженных на часовую ставку программистов) на оптимизацию, то вполне может оказаться, что «докупить плашку памяти» просто выгоднее для бизнеса (по крайней мере, на ближайшую перспективу).
Конечно, тут будет множество нюансов, особенно если дело касается облачных решений. Там просто так памяти не докинешь, это будут размазанные по времени постоянные издержки, и конечно, через какое-то время оптимизация окажется дешевле. А если мы говорим о микросервисах, кубернетисах и вот этом вот всём, то там ограничения на железо ещё более строгие.
В общем, давайте обсудим. Что думаете? Апгрейд железа может быть опцией? Бывали ли у вас в практике такие случаи?
Возник у нас тут небольшой спор с Кириллом, автором канала C# Heppard (кстати, подписывайтесь). Кирилл пишет:
«Я всегда вспоминаю один диалог. Состоялся он, ох, уж сколько лет прошло, на одном локальном митапе. Я, как всегда, вещал про то, что было бы неплохо писать оптимальный код, в том смысле, чтобы экономить байтики и наносекунды. То да сё, слайды, смешки, лёгкое непонимание в глазах слушателей. Наконец, секция вопросов и ответов.
Я хорошо помню этого мужика, который во время доклада делал фейс-палмы, громко ёрзал стулом, сидел в телефоне. Я ждал вопросов от него и дождался.
- Скажите, а зачем всё это?
- В смысле, - не понимаю я.
- В том смысле, - сказал он авторитетно, - что это всё не нужно. Проще купить плашку в 16 ГБ памяти, чем всё это знать.»
И ещё несколько лет назад, я бы тоже посмеялся над мужиком. А тут… В общем, не знаю почему, но решил я выступить адвокатом дьявола)))
Тут надо оговориться, что Кирилл в основном занимается оптимизациями, а я всё-таки фуллстек, и экономия байтиков и наносекунд для меня скорей теоретическая область и «чисто по фану». То есть, я практически этим не занимаюсь, т.к. в моей практике я чаще оптимизирую запросы к БД или UI в браузере. Но…
В принципе, мужик не так уж и не прав. Понятно, что нет серебряной пули, и у всех ситуации разные. Но, как мне кажется, в довольно приличном количестве случаев апгрейд железа – действительно выход. Понятно, что мы - программисты, мы все любим чистый и быстрый код. Хлебом нас не корми, дай реализовать свой супербыстрый и эффективный по памяти вариант сортировки или словаря.
Но давайте перенесёмся на самый высокий уровень – уровень бизнеса. Как говорит Марк Симан: «Не забывайте, что в конечном итоге, вы делаете продукт, который должен решать задачи бизнеса». Так вот, с точки зрения бизнеса, оптимизировать алгоритм или докупить памяти – разницы нет. Всё выражается в деньгах. Надо заплатить за одно или за другое. Если ПМ, техлид или другой менеджер этого уровня прикинет, как говорится, писю к носу, и сравнит, сколько денег будет стоить апгрейд железа, и сколько примерно надо потратить человекочасов (умноженных на часовую ставку программистов) на оптимизацию, то вполне может оказаться, что «докупить плашку памяти» просто выгоднее для бизнеса (по крайней мере, на ближайшую перспективу).
Конечно, тут будет множество нюансов, особенно если дело касается облачных решений. Там просто так памяти не докинешь, это будут размазанные по времени постоянные издержки, и конечно, через какое-то время оптимизация окажется дешевле. А если мы говорим о микросервисах, кубернетисах и вот этом вот всём, то там ограничения на железо ещё более строгие.
В общем, давайте обсудим. Что думаете? Апгрейд железа может быть опцией? Бывали ли у вас в практике такие случаи?
👍39
Что говорит enum с именем во множественном числе и атрибутом Flags о своих значениях?
#Quiz #BestPractices
#Quiz #BestPractices
Anonymous Quiz
75%
Значения могут комбинироваться как значения битового поля
8%
Значения должны быть степенью двойки
9%
Значения должны обозначать сущности во множественном числе
7%
Значения представляют взаимоисключающие варианты
👍10👎3
День 1858. #Шпаргалка
Примеры Кода для Повседневных Задач
В этой серии представлю вам коллекцию фрагментов кода C#, охватывающих широкий спектр сценариев, с которыми вы можете столкнуться при разработке ПО.
IV. Потоки
Многопоточная обработка — важный аспект параллельного программирования на C#. Рассмотрим, как работать с потоками, используя пространство имен System.Threading.
1. Создание нового потока
Чтобы создать новый поток в C#, вы можете использовать класс Thread:
2. Операции над потоком
- Запуск
- Слияние (ожидание завершения потока)
- Приостановка (текущий поток)
См. подробнее о потоках
3. Использование пула потоков
Для работы с пулом потоков используется класс ThreadPool:
См. подробнее о пуле потоков
V. Задачи
1. Создание новой задачи
Для создания и запуска задачи используется класс Task:
См. подробнее о задачах
2. Ожидание задачи
3. Отмена задачи
Для отмены задачи в C# используется класс CancellationTokenSource, создающий токен отмены. А для собственно отмены – метод токена Cancel():
См. подробнее «Скоординированная отмена»
Источник: https://medium.com/bytehide/100-csharp-code-snippets-for-everyday-problems-e913c786dec9
Примеры Кода для Повседневных Задач
В этой серии представлю вам коллекцию фрагментов кода C#, охватывающих широкий спектр сценариев, с которыми вы можете столкнуться при разработке ПО.
IV. Потоки
Многопоточная обработка — важный аспект параллельного программирования на C#. Рассмотрим, как работать с потоками, используя пространство имен System.Threading.
1. Создание нового потока
Чтобы создать новый поток в C#, вы можете использовать класс Thread:
using System.Threading;
void PrintNumbers()
{
for (int i = 1; i <= 5; i++)
Console.WriteLine(i);
}
var thread = new Thread(PrintNumbers);
2. Операции над потоком
- Запуск
thread.Start();
- Слияние (ожидание завершения потока)
thread.Join();
- Приостановка (текущий поток)
Thread.Sleep(1000); // пауза на 1 секунду
См. подробнее о потоках
3. Использование пула потоков
Для работы с пулом потоков используется класс ThreadPool:
using System.Threading;
ThreadPool.QueueUserWorkItem(PrintNumbers);
См. подробнее о пуле потоков
V. Задачи
1. Создание новой задачи
Для создания и запуска задачи используется класс Task:
using System.Threading.Tasks;
Task.Run(PrintNumbers);
См. подробнее о задачах
2. Ожидание задачи
var task = Task.Run(PrintNumbers);
task.Wait();
3. Отмена задачи
Для отмены задачи в C# используется класс CancellationTokenSource, создающий токен отмены. А для собственно отмены – метод токена Cancel():
using System.Threading;
using System.Threading.Tasks;
var cts = new CancellationTokenSource();
Task.Run(() => PrintNumbers(), cts.Token);
cts.Cancel();
См. подробнее «Скоординированная отмена»
Источник: https://medium.com/bytehide/100-csharp-code-snippets-for-everyday-problems-e913c786dec9
👍14
День 1859. #ЗаметкиНаПолях
Как Принудить Запускать Приложение от Имени Администратора
Чтобы заставить приложение запускаться в Windows от имени администратора, нужно добавить и изменить файл манифеста приложения. Манифест приложения — это XML-файл, который предоставляет информацию о приложении операционной системе.
Начнем с создания консольного приложения:
После этого в свойствах проекта добавим целевую ОС Windows.
Также нужно включить в проект файл app.manifest. В Visual Studio это можно сделать, щелкнув правой кнопкой мыши проект, выбрав Add > New Element… (Добавить > Новый элемент…). В появившемся окне найти Application Manifest file (Windows only) (Файл Манифеста приложения (только Windows)).
В файле app.manifest измените:
на:
Это заставит приложение требовать административных привилегий.
Теперь проверим административные привилегии в файле Program.cs:
Класс WindowsIdentity представляет личность пользователя Windows. WindowsIdentity.GetCurrent() получает личность пользователя, исполняющего текущий процесс, т.е. пользователя, запустившего приложение.
Объект WindowsPrincipal создаётся на основании WindowsIdentity и представляет собой контекст безопасности пользователя, что позволяет проверить его роль.
Теперь можно запустить приложение. В зависимости от ваших настроек Windows, вам либо будет предложено повысить привилегии и запустить приложение от имени администратора, либо приложение будет запущено, но выдаст ошибку:
Unhandled exception: System.ComponentModel.Win32Exception (740):
An error occurred trying to start process
'C:\...\AdministratorApp.exe' ….
The requested operation requires elevation.
Возникает исключение System.ComponentModel.Win32Exception с кодом ошибки 740 «Запрошенная операция требует повышения прав». Подразумевается, что мы можем запускать наше приложение только с правами администратора.
Если запустить приложение от имени администратора, мы увидим в консоли вывод:
Источник: https://code-maze.com/csharp-how-to-force-run-net-application-as-administrator/
Как Принудить Запускать Приложение от Имени Администратора
Чтобы заставить приложение запускаться в Windows от имени администратора, нужно добавить и изменить файл манифеста приложения. Манифест приложения — это XML-файл, который предоставляет информацию о приложении операционной системе.
Начнем с создания консольного приложения:
dotnet new console -n AdministratorApp
После этого в свойствах проекта добавим целевую ОС Windows.
<TargetFramework>net8.0-windows</TargetFramework>
Также нужно включить в проект файл app.manifest. В Visual Studio это можно сделать, щелкнув правой кнопкой мыши проект, выбрав Add > New Element… (Добавить > Новый элемент…). В появившемся окне найти Application Manifest file (Windows only) (Файл Манифеста приложения (только Windows)).
В файле app.manifest измените:
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
на:
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
Это заставит приложение требовать административных привилегий.
Теперь проверим административные привилегии в файле Program.cs:
using System.Security.Principal;
var p = new WindowsPrincipal(
WindowsIdentity.GetCurrent());
if (p.IsInRole(WindowsBuiltInRole.Administrator))
Console.WriteLine("Admin mode");
else
Console.WriteLine("Not Admin mode");
Класс WindowsIdentity представляет личность пользователя Windows. WindowsIdentity.GetCurrent() получает личность пользователя, исполняющего текущий процесс, т.е. пользователя, запустившего приложение.
Объект WindowsPrincipal создаётся на основании WindowsIdentity и представляет собой контекст безопасности пользователя, что позволяет проверить его роль.
Теперь можно запустить приложение. В зависимости от ваших настроек Windows, вам либо будет предложено повысить привилегии и запустить приложение от имени администратора, либо приложение будет запущено, но выдаст ошибку:
Unhandled exception: System.ComponentModel.Win32Exception (740):
An error occurred trying to start process
'C:\...\AdministratorApp.exe' ….
The requested operation requires elevation.
Возникает исключение System.ComponentModel.Win32Exception с кодом ошибки 740 «Запрошенная операция требует повышения прав». Подразумевается, что мы можем запускать наше приложение только с правами администратора.
Если запустить приложение от имени администратора, мы увидим в консоли вывод:
Admin mode
Источник: https://code-maze.com/csharp-how-to-force-run-net-application-as-administrator/
👍20👎1
День 1860. #ЗаметкиНаПолях
Используем HttpContext.Items для Передачи Данных в ASP.NET Core
HttpContext.Items — это словарь, к которому может получить доступ каждый компонент, участвующий в обработке HTTP-запроса. В отличие от HttpContext.Session, данные, сохранённые в HttpContext.Items, не сохраняются между запросами. Это изолирует данные и гарантирует, что они не будут мешать другим одновременным запросам.
В большинстве веб-приложений компонентам часто необходимо взаимодействовать друг с другом. Для этого должна быть организована передача параметров или общее состояние. Но это имеет определённые недостатки и проблемы: проблемы параллелизма, нарушение целостности данных, жёсткая связанность, издержки производительности и т. д.
HttpContext.Items позволяет промежуточному ПО, фильтрам и контроллерам обмениваться данными без прямой связи при их выполнении на разных этапах жизненного цикла запроса. Можно использовать любой тип данных для ключа. Однако, когда мы используем ключ строкового типа, мы рискуем столкнуться с коллизией ключей. Лучшая альтернатива — использовать объект в качестве ключа элемента.
Посмотрим, как использовать это в приложении веб-API ASP.NET Core.
1. Промежуточное ПО
Мы создаём новый экземпляр объекта для ключа MWKey. В методе Invoke() устанавливаем значение в словаре HttpContext.Items.
В Program.cs используем наше промежуточное ПО:
2. Фильтр
Зарегистрируем фильтр в сервисах:
3. В контроллере
Внутри метода контроллера мы используем TryGetValue() для получения значения, установленного в промежуточном ПО по ключу MWKey.
Итого
HttpContext.Items позволяет эффективно использовать данные между компонентами обработки запроса и автоматически удаляет сохранённые данные после завершения запроса.
Полный код примера на GitHub.
Источник: https://code-maze.com/aspnetcore-httpcontext-items-pass-data/
Используем HttpContext.Items для Передачи Данных в ASP.NET Core
HttpContext.Items — это словарь, к которому может получить доступ каждый компонент, участвующий в обработке HTTP-запроса. В отличие от HttpContext.Session, данные, сохранённые в HttpContext.Items, не сохраняются между запросами. Это изолирует данные и гарантирует, что они не будут мешать другим одновременным запросам.
В большинстве веб-приложений компонентам часто необходимо взаимодействовать друг с другом. Для этого должна быть организована передача параметров или общее состояние. Но это имеет определённые недостатки и проблемы: проблемы параллелизма, нарушение целостности данных, жёсткая связанность, издержки производительности и т. д.
HttpContext.Items позволяет промежуточному ПО, фильтрам и контроллерам обмениваться данными без прямой связи при их выполнении на разных этапах жизненного цикла запроса. Можно использовать любой тип данных для ключа. Однако, когда мы используем ключ строкового типа, мы рискуем столкнуться с коллизией ключей. Лучшая альтернатива — использовать объект в качестве ключа элемента.
Посмотрим, как использовать это в приложении веб-API ASP.NET Core.
1. Промежуточное ПО
public class CustomMW
{
private RequestDelegate _next;
public static readonly object MWKey = new();
public CustomMW(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext ctx)
{
ctx.Items[MWKey] = "MW";
await _next(ctx);
}
}
Мы создаём новый экземпляр объекта для ключа MWKey. В методе Invoke() устанавливаем значение в словаре HttpContext.Items.
В Program.cs используем наше промежуточное ПО:
…
app.UseMiddleware<CustomMiddleware>();
app.Run()
2. Фильтр
public class CustomFilter : IActionFilter
{
public static readonly object
FilterKey = new();
…
public void OnActionExecuting(
ActionExecutingContext ctx)
{
ctx.HttpContext.Items[FilterKey] = "Loading…";
…
}
public void OnActionExecuted(
ActionExecutedContext ctx) {
ctx.HttpContext.Items[FilterKey] = "Loaded.";
…
}
…
}
Зарегистрируем фильтр в сервисах:
builder.Services.AddScoped<CustomFilter>();
3. В контроллере
[ServiceFilter(typeof(CustomFilter))]
public IEnumerable<Item> Get()
{
HttpContext.Items.TryGetValue(
CustomMW.MWKey, out var mwValue);
…
}
Внутри метода контроллера мы используем TryGetValue() для получения значения, установленного в промежуточном ПО по ключу MWKey.
Итого
HttpContext.Items позволяет эффективно использовать данные между компонентами обработки запроса и автоматически удаляет сохранённые данные после завершения запроса.
Полный код примера на GitHub.
Источник: https://code-maze.com/aspnetcore-httpcontext-items-pass-data/
👍17
День 1861. #ВопросыНаСобеседовании #Многопоточность
Самые часто задаваемые вопросы на собеседовании по C#
29. Какие потенциальные проблемы могут возникнуть при использовании Thread.Abort() для завершения работающего потока? Объясните последствия и предложите альтернативные методы корректной остановки потока.
Использование Thread.Abort() для завершения работающего потока может привести к нескольким потенциальным проблемам:
1. Непредсказуемое состояние.
Thread.Abort() вызывает исключение ThreadAbortException, которое немедленно прерывает поток, потенциально оставляя общие ресурсы, структуры данных или критические разделы в несогласованном состоянии.
2. Утечки ресурсов.
Если прерванный поток выделил ресурсы, такие как дескрипторы, файловые потоки или соединения с базой данных, они могут не быть освобождены, что приведёт к утечке ресурсов.
3. Взаимные блокировки.
Прерванные потоки, удерживающие блокировки или другие примитивы синхронизации, могут не иметь возможности их освободить, что приводит к взаимоблокировкам в других потоках.
4. Обработка ThreadAbortException.
Если поток перехватывает ThreadAbortException и игнорирует его, запрос на прерывание завершится неудачно, и поток продолжит выполнение.
5. Устарело и больше не поддерживается.
Метод Thread.Abort() не поддерживается в .NET Core, .NET 5 и более поздних версиях, что указывает на то, что его не следует использовать в современных приложениях.
Чтобы корректно остановить поток, есть следующие альтернативы.
1. Общий флаг
Добавим логический флаг, который будет периодически проверяться выполняемым потоком. Если флаг установлен в true, поток должен завершиться. Обязательно использовать ключевое слово volatile или класс Interlocked, чтобы обеспечить правильную синхронизацию:
2. Токен отмены
Если использовать задачи вместо потоков, библиотека параллельных задач (TPL) предоставляет модель отмены с использованием токена отмены:
Грамотная остановка потоков с помощью этих методов гарантирует освобождение ресурсов, правильное управление блокировками и сохранение согласованного состояния общих данных. Она также совместима с современными версиями .NET и TPL.
Источник: https://dev.to/bytehide/c-multithreading-interview-questions-and-answers-4opj
Самые часто задаваемые вопросы на собеседовании по C#
29. Какие потенциальные проблемы могут возникнуть при использовании Thread.Abort() для завершения работающего потока? Объясните последствия и предложите альтернативные методы корректной остановки потока.
Использование Thread.Abort() для завершения работающего потока может привести к нескольким потенциальным проблемам:
1. Непредсказуемое состояние.
Thread.Abort() вызывает исключение ThreadAbortException, которое немедленно прерывает поток, потенциально оставляя общие ресурсы, структуры данных или критические разделы в несогласованном состоянии.
2. Утечки ресурсов.
Если прерванный поток выделил ресурсы, такие как дескрипторы, файловые потоки или соединения с базой данных, они могут не быть освобождены, что приведёт к утечке ресурсов.
3. Взаимные блокировки.
Прерванные потоки, удерживающие блокировки или другие примитивы синхронизации, могут не иметь возможности их освободить, что приводит к взаимоблокировкам в других потоках.
4. Обработка ThreadAbortException.
Если поток перехватывает ThreadAbortException и игнорирует его, запрос на прерывание завершится неудачно, и поток продолжит выполнение.
5. Устарело и больше не поддерживается.
Метод Thread.Abort() не поддерживается в .NET Core, .NET 5 и более поздних версиях, что указывает на то, что его не следует использовать в современных приложениях.
Чтобы корректно остановить поток, есть следующие альтернативы.
1. Общий флаг
Добавим логический флаг, который будет периодически проверяться выполняемым потоком. Если флаг установлен в true, поток должен завершиться. Обязательно использовать ключевое слово volatile или класс Interlocked, чтобы обеспечить правильную синхронизацию:
volatile bool stopRequested = false;
var thread = new Thread(() => {
while (!stopRequested)
{
// выполняем работу
// ...
}
});
// Останавливаем поток
stopRequested = true;
2. Токен отмены
Если использовать задачи вместо потоков, библиотека параллельных задач (TPL) предоставляет модель отмены с использованием токена отмены:
var cts = new CancellationTokenSource();
var task = Task.Run(() =>
{
while (!cts.IsCancellationRequested)
{
// Выполняем работу
// ...
}
});
// Останавливаем задачу
cts.Cancel();
Грамотная остановка потоков с помощью этих методов гарантирует освобождение ресурсов, правильное управление блокировками и сохранение согласованного состояния общих данных. Она также совместима с современными версиями .NET и TPL.
Источник: https://dev.to/bytehide/c-multithreading-interview-questions-and-answers-4opj
👍22
День 1862. #ЗаметкиНаПолях
REST vs RESTful. В чём Разница? Начало
REST API — один из самых популярных API в сообществе веб-разработчиков. В чем разница между REST API и RESTful API?
Короткий ответ: REST - REpresentational State Transfer (Передача Репрезентативного Состояния). Это архитектурный шаблон для создания веб-сервисов. RESTful-сервис реализует этот шаблон.
Длинный ответ начинается со слов «вроде как» и «это зависит» и продолжается более полными определениями.
REST
Для некоторых REST – это обмен JSON-данными между клиентом и сервером. Это не только не полное определение, но и не всегда верное. Спецификация REST не требует HTTP или JSON, JSON или XML там даже не упоминаются.
Рой Филдинг представил архитектурный шаблон REST в своей диссертации в 2000 году. Там описывается метод облегчения обмена данными приложений между клиентами и серверами. Ключевой особенностью является то, что от клиента не требуется никаких предварительных знаний о приложении. Филдинг не предъявляет особых требований. Вместо этого он определяет REST в отношении ограничений и архитектурных элементов.
Архитектурные ограничения REST
1. Клиент-сервер
REST-сервисы имеют сервер, который управляет данными и состоянием приложения. Сервер взаимодействует с клиентом, который обрабатывает взаимодействие с пользователем. Чёткое разделение задач сервера и клиента означает, что вы можете обновлять и улучшать их независимо.
2. Нет сохранения состояния
Сервер не поддерживает состояние клиента. Клиенты управляют состоянием своего приложения. Их запросы к серверу содержат всю информацию, необходимую для их обработки.
3. Кэш
Сервер должен помечать свои ответы как кэшируемые или нет. Так инфраструктура и клиент могут кэшировать ответы, когда это возможно, для повышения производительности. Некэшируемая информация может удаляться, так что ни один клиент не будет использовать устаревшие данные.
4. Единый интерфейс
Филдинг говорит: «Главной особенностью, которая отличает архитектурный стиль REST от других сетевых стилей, является акцент на единообразном интерфейсе между компонентами». REST-сервисы предоставляют данные в виде ресурсов с согласованным пространством имён.
5. Многоуровневая система
Компоненты системы не могут «видеть» ничего за пределами своего уровня. Так вы можете легко добавить балансировщики нагрузки и прокси-серверы для повышения безопасности или производительности.
RESTful-сервис — это больше, чем веб-сервер, который обменивается JSON или любыми другими форматами данных. Вместе приведённые выше ограничения создают очень специфический тип приложения.
Применение ограничений
Клиент-серверность, многоуровневость системы и ограничение на несохранение состояния образуют приложение с чёткими границами и чётким разделением задач. По запросу данные передаются с сервера клиенту, где они отображаются или обрабатываются. Если необходимы какие-либо изменения в состоянии, клиент отправляет изменённые данные обратно на сервер для хранения. В REST и клиент, и сервер обладают общими знаниями о данных и их состоянии. Архитектура не скрывает данные, она лишь скрывает реализацию. Данные приложения доступны клиентам в понятном и согласованном интерфейсе и, по возможности, кэшируются.
Окончание следует…
Источник: https://blog.ndepend.com/rest-vs-restful/
REST vs RESTful. В чём Разница? Начало
REST API — один из самых популярных API в сообществе веб-разработчиков. В чем разница между REST API и RESTful API?
Короткий ответ: REST - REpresentational State Transfer (Передача Репрезентативного Состояния). Это архитектурный шаблон для создания веб-сервисов. RESTful-сервис реализует этот шаблон.
Длинный ответ начинается со слов «вроде как» и «это зависит» и продолжается более полными определениями.
REST
Для некоторых REST – это обмен JSON-данными между клиентом и сервером. Это не только не полное определение, но и не всегда верное. Спецификация REST не требует HTTP или JSON, JSON или XML там даже не упоминаются.
Рой Филдинг представил архитектурный шаблон REST в своей диссертации в 2000 году. Там описывается метод облегчения обмена данными приложений между клиентами и серверами. Ключевой особенностью является то, что от клиента не требуется никаких предварительных знаний о приложении. Филдинг не предъявляет особых требований. Вместо этого он определяет REST в отношении ограничений и архитектурных элементов.
Архитектурные ограничения REST
1. Клиент-сервер
REST-сервисы имеют сервер, который управляет данными и состоянием приложения. Сервер взаимодействует с клиентом, который обрабатывает взаимодействие с пользователем. Чёткое разделение задач сервера и клиента означает, что вы можете обновлять и улучшать их независимо.
2. Нет сохранения состояния
Сервер не поддерживает состояние клиента. Клиенты управляют состоянием своего приложения. Их запросы к серверу содержат всю информацию, необходимую для их обработки.
3. Кэш
Сервер должен помечать свои ответы как кэшируемые или нет. Так инфраструктура и клиент могут кэшировать ответы, когда это возможно, для повышения производительности. Некэшируемая информация может удаляться, так что ни один клиент не будет использовать устаревшие данные.
4. Единый интерфейс
Филдинг говорит: «Главной особенностью, которая отличает архитектурный стиль REST от других сетевых стилей, является акцент на единообразном интерфейсе между компонентами». REST-сервисы предоставляют данные в виде ресурсов с согласованным пространством имён.
5. Многоуровневая система
Компоненты системы не могут «видеть» ничего за пределами своего уровня. Так вы можете легко добавить балансировщики нагрузки и прокси-серверы для повышения безопасности или производительности.
RESTful-сервис — это больше, чем веб-сервер, который обменивается JSON или любыми другими форматами данных. Вместе приведённые выше ограничения создают очень специфический тип приложения.
Применение ограничений
Клиент-серверность, многоуровневость системы и ограничение на несохранение состояния образуют приложение с чёткими границами и чётким разделением задач. По запросу данные передаются с сервера клиенту, где они отображаются или обрабатываются. Если необходимы какие-либо изменения в состоянии, клиент отправляет изменённые данные обратно на сервер для хранения. В REST и клиент, и сервер обладают общими знаниями о данных и их состоянии. Архитектура не скрывает данные, она лишь скрывает реализацию. Данные приложения доступны клиентам в понятном и согласованном интерфейсе и, по возможности, кэшируются.
Окончание следует…
Источник: https://blog.ndepend.com/rest-vs-restful/
👍20
День 1863. #ЗаметкиНаПолях
REST vs RESTful. В чём Разница? Окончание
Начало
RPC через HTTP vs RESTful
Часто говорят, что сервис «не REST», из-за неправильных URI или использования глаголов HTTP. Имеется в виду представление данных REST как единообразного набора ресурсов.
Это различие иногда формулируется как различие между вызовами удаленных процедур (RPC) и REST. Представьте себе веб-сервис для перечисления, добавления и удаления товаров онлайн-магазина.
В одной версии есть один URL, который мы запрашиваем с помощью HTTP GET или POST. Вы взаимодействуете с сервисом, отправляя данные POST, содержащие то, что вы хотите сделать. Например, добавить элемент с помощью NewItem:
Запросить через POST и ItemRequest:
Некоторые реализации также допускают запросы через GET-параметры:
И т.п.
Это не REST. Мы не обмениваемся состоянием ресурсов. Мы вызываем функцию с аргументами, которые находятся в документе JSON или параметрая URL.
RESTful-сервис имеет URI для каждого ресурса. Поэтому добавление нового товара будет выглядеть так же, как в примере выше. Но на этом сходства заканчиваются.
Запрос элемента – это всегда GET:
Удаление - DELETE:
Изменение - PUT:
Разница важна. В REST операции используют различные глаголы HTTP, которые соответствуют действиям с данными: GET, POST, PUT, DELETE и PATCH имеют определённые контракты. Большинство хорошо спроектированных REST API также возвращают определённые коды HTTP в зависимости от результата запроса. Основным различием является то, что URI соответствуют данным, а не удалёнными методам.
REST против RESTful и модель зрелости Ричардсона
Разработка URI в соответствии с ресурсами и использование глаголов HTTP способствует предсказуемости API. Когда разработчики поймут, как вы структурировали ресурсы, они смогут сделать обоснованные прогнозы относительно структуры API. В этом смысле основное внимание уделяется пониманию самих данных, а не связанных с ними операций.
Но даже если вы не можете сделать API полностью предсказуемым, вы можете документировать любой REST-сервис с помощью HATEOAS. Тогда каждый элемент, возвращаемый сервером, будет содержать ссылки для удаления или изменения ресурса.
Многие сайты не соответствуют этому требованию, но по-прежнему называются REST. Леонард Ричардсон создал модель уровней зрелости REST:
0 – API через HTTP с вызовом удалённых методов с аргументами.
1 – Использование ресурсов вместо методов.
2 – Правильное использование HTTP-глаголов.
3 – Использование HATEOAS, что делает видимым весь API или его часть.
По Филдингу требуется третий уровень, так что большинство приложений не являются REST.
То, насколько хорошо архитектура соответствует стандарту, не так важно, как то, насколько хорошо она соответствует потребностям и может расти вместе с бизнесом.
Архитектурный шаблон REST имеет множество преимуществ. Филдинг разработал его для Интернета, и 18 лет спустя большинство ограничений, которые он добавил, все ещё с нами. В 2000 году у нас не было Android или iPhone, а IE5 занимал 50% рынка браузеров. Но Филдинг понимал, что нужно онлайн-приложениям и как веб-клиенты будут развиваться от механизмов отображения HTML в полноценные приложения. Инструменты, которые мы используем сегодня, адаптированы к REST, а не наоборот.
Модель зрелости Ричардсона — хорошее руководство для разработки приложений. Желательно находиться на втором уровне модели и посмотреть, как третий уровень может улучшить ваш дизайн.
Источник: https://blog.ndepend.com/rest-vs-restful/
REST vs RESTful. В чём Разница? Окончание
Начало
RPC через HTTP vs RESTful
Часто говорят, что сервис «не REST», из-за неправильных URI или использования глаголов HTTP. Имеется в виду представление данных REST как единообразного набора ресурсов.
Это различие иногда формулируется как различие между вызовами удаленных процедур (RPC) и REST. Представьте себе веб-сервис для перечисления, добавления и удаления товаров онлайн-магазина.
В одной версии есть один URL, который мы запрашиваем с помощью HTTP GET или POST. Вы взаимодействуете с сервисом, отправляя данные POST, содержащие то, что вы хотите сделать. Например, добавить элемент с помощью NewItem:
POST /inventory HTTP/1.1
{
"NewItem": {
"name": "new item",
"price": "9.99",
"id": "1001"
}
}
Запросить через POST и ItemRequest:
POST /inventory HTTP/1.1
{
"ItemRequest": {
"id": "1001"
}
}
Некоторые реализации также допускают запросы через GET-параметры:
POST /inventory?id=1001 HTTP/1.1
И т.п.
Это не REST. Мы не обмениваемся состоянием ресурсов. Мы вызываем функцию с аргументами, которые находятся в документе JSON или параметрая URL.
RESTful-сервис имеет URI для каждого ресурса. Поэтому добавление нового товара будет выглядеть так же, как в примере выше. Но на этом сходства заканчиваются.
Запрос элемента – это всегда GET:
GET /item/1001 HTTP/1.1
Удаление - DELETE:
DELETE /item/1001 HTTP/1.1
Изменение - PUT:
PUT /inventory HTTP/1.1
{
"Item": {
"name": "new item",
"price": "7.99",
"id": "1001"
}
}
Разница важна. В REST операции используют различные глаголы HTTP, которые соответствуют действиям с данными: GET, POST, PUT, DELETE и PATCH имеют определённые контракты. Большинство хорошо спроектированных REST API также возвращают определённые коды HTTP в зависимости от результата запроса. Основным различием является то, что URI соответствуют данным, а не удалёнными методам.
REST против RESTful и модель зрелости Ричардсона
Разработка URI в соответствии с ресурсами и использование глаголов HTTP способствует предсказуемости API. Когда разработчики поймут, как вы структурировали ресурсы, они смогут сделать обоснованные прогнозы относительно структуры API. В этом смысле основное внимание уделяется пониманию самих данных, а не связанных с ними операций.
Но даже если вы не можете сделать API полностью предсказуемым, вы можете документировать любой REST-сервис с помощью HATEOAS. Тогда каждый элемент, возвращаемый сервером, будет содержать ссылки для удаления или изменения ресурса.
Многие сайты не соответствуют этому требованию, но по-прежнему называются REST. Леонард Ричардсон создал модель уровней зрелости REST:
0 – API через HTTP с вызовом удалённых методов с аргументами.
1 – Использование ресурсов вместо методов.
2 – Правильное использование HTTP-глаголов.
3 – Использование HATEOAS, что делает видимым весь API или его часть.
По Филдингу требуется третий уровень, так что большинство приложений не являются REST.
То, насколько хорошо архитектура соответствует стандарту, не так важно, как то, насколько хорошо она соответствует потребностям и может расти вместе с бизнесом.
Архитектурный шаблон REST имеет множество преимуществ. Филдинг разработал его для Интернета, и 18 лет спустя большинство ограничений, которые он добавил, все ещё с нами. В 2000 году у нас не было Android или iPhone, а IE5 занимал 50% рынка браузеров. Но Филдинг понимал, что нужно онлайн-приложениям и как веб-клиенты будут развиваться от механизмов отображения HTML в полноценные приложения. Инструменты, которые мы используем сегодня, адаптированы к REST, а не наоборот.
Модель зрелости Ричардсона — хорошее руководство для разработки приложений. Желательно находиться на втором уровне модели и посмотреть, как третий уровень может улучшить ваш дизайн.
Источник: https://blog.ndepend.com/rest-vs-restful/
👍16
День 1864. #ЗаметкиНаПолях
Необязательные Параметры Могут Быть в Середине
В .NET необязательные параметры не всегда должны быть последними параметрами в сигнатуре метода. Хотя C# не позволяет объявлять необязательные параметры в середине, это можно сделать в IL или других языках, таких как VB.NET или F#. Кроме того, компилятор может создавать методы с необязательными параметрами в середине списка параметров.
Почему это имеет значение? Если вы пишете генератор исходного кода, который генерирует новые методы с теми же параметрами, что и другой метод, вам может потребоваться сгенерировать методы с необязательными параметрами в середине списка параметров. Хотя это довольно редкий случай, это допустимо в .NET и может быть полезно в некоторых сценариях.
Чтобы лучше понять суть вопроса, начнём с простого примера. Следующий код недопустим в C#:
При компиляции метода с необязательными параметрами компилятор заменяет синтаксис C# двумя атрибутами: [Optional] и [DefaultParameterValue]. Атрибут [Optional] используется для пометки параметра как необязательного, а атрибут [DefaultParameterValue] используется для установки значения по умолчанию. Следующий код допустим в C#:
Обратите внимание, что компилятор C# более строг, чем в других языках, в отношении допустимых значений в [DefaultParameterValue]. Он гарантирует, что тип значения совпадает с типом параметра, но может не допускать некоторых допустимых преобразований. Например, следующий код будет допустим в VB.NET или F#, но не в C#:
Тот же метод в F# допустим:
Кстати: даже компилятор C# может сгенерировать метод с необязательными параметрами в середине списка параметров. При создании индексатора с необязательными параметрами компилятор генерирует метод с необязательным параметром в середине:
Источник: https://www.meziantou.net/optional-parameters.htm
Необязательные Параметры Могут Быть в Середине
В .NET необязательные параметры не всегда должны быть последними параметрами в сигнатуре метода. Хотя C# не позволяет объявлять необязательные параметры в середине, это можно сделать в IL или других языках, таких как VB.NET или F#. Кроме того, компилятор может создавать методы с необязательными параметрами в середине списка параметров.
Почему это имеет значение? Если вы пишете генератор исходного кода, который генерирует новые методы с теми же параметрами, что и другой метод, вам может потребоваться сгенерировать методы с необязательными параметрами в середине списка параметров. Хотя это довольно редкий случай, это допустимо в .NET и может быть полезно в некоторых сценариях.
Чтобы лучше понять суть вопроса, начнём с простого примера. Следующий код недопустим в C#:
// CS1737 Optional parameters must appear after all required parameters
void Sample(int a = 1, int b)
{
}
При компиляции метода с необязательными параметрами компилятор заменяет синтаксис C# двумя атрибутами: [Optional] и [DefaultParameterValue]. Атрибут [Optional] используется для пометки параметра как необязательного, а атрибут [DefaultParameterValue] используется для установки значения по умолчанию. Следующий код допустим в C#:
// Вывод: 42
Sample(b: 2);
// Объявляем "a" как необязательный
// со значением 40 по умолчанию
void Sample(
[Optional, DefaultParameterValue(40)]
int a,
int b)
{
Console.WriteLine(a + b);
}
Обратите внимание, что компилятор C# более строг, чем в других языках, в отношении допустимых значений в [DefaultParameterValue]. Он гарантирует, что тип значения совпадает с типом параметра, но может не допускать некоторых допустимых преобразований. Например, следующий код будет допустим в VB.NET или F#, но не в C#:
// CS1908 The type of the argument to the DefaultParameterValue attribute must match the parameter type
void Sample(
[Optional, DefaultParameterValue(default)]
CancellationToken cancellationToken)
{
}
Тот же метод в F# допустим:
type Class1 =
static member Sample(
[<Optional; DefaultParameterValue(CancellationToken())>]
ct: CancellationToken) = ()
Кстати: даже компилятор C# может сгенерировать метод с необязательными параметрами в середине списка параметров. При создании индексатора с необязательными параметрами компилятор генерирует метод с необязательным параметром в середине:
// Создаёт метод (set_Item) с необязательным параметром в середине
// int set_Item(int a, int b = 2, int value)
public int this[int a, int b = 2]
{
set { }
}
Источник: https://www.meziantou.net/optional-parameters.htm
👍11👎2
День 1865. #ЗаметкиНаПолях
Безопасность Типов в xUnit с Помощью TheoryData<T>
В xUnit есть небольшая, но очень полезная особенность – класс TheoryData<T>.
Проблема
Имеем следующий код тестов репозитория:
Проблема здесь в том, что данные имеют тип IEnumerable<object[]>, а параметр act — тип Action<IServiceCollection>. То есть теоретически можно передать в метод что угодно, и компилятор с радостью это примет. Вот тут-то и пригодится TheoryData<T>.
Решение
TheoryData<T> — это класс, который позволяет обертывать данные строго типизированным способом. Например:
Точно такой же тест, но более типобезопасный. Теперь компилятор не позволит передать в метод ничего, кроме Action<IServiceCollection>.
Источник: https://steven-giesel.com/blogPost/a8aa3385-8829-444a-b269-7ecb38aeaf2f/typesafety-in-xunit-with-theorydatat
Безопасность Типов в xUnit с Помощью TheoryData<T>
В xUnit есть небольшая, но очень полезная особенность – класс TheoryData<T>.
Проблема
Имеем следующий код тестов репозитория:
public static IEnumerable<object[]>
Data => new List<object[]>
{
new object[] {
new Action<IServiceCollection>(
s => s.UseSqliteAsStorageProvider())
},
new object[] {
new Action<IServiceCollection>(
s => s.UseSqlAsStorageProvider())
},
new object[] {
new Action<IServiceCollection>(
s => s.UseInMemoryAsStorageProvider())
}
};
[Theory]
[MemberData(nameof(Data))]
public void TestStorageProvider(
Action<IServiceCollection> act)
{ … }
Проблема здесь в том, что данные имеют тип IEnumerable<object[]>, а параметр act — тип Action<IServiceCollection>. То есть теоретически можно передать в метод что угодно, и компилятор с радостью это примет. Вот тут-то и пригодится TheoryData<T>.
Решение
TheoryData<T> — это класс, который позволяет обертывать данные строго типизированным способом. Например:
public static TheoryData<Action<IServiceCollection>>
Data => new()
{
s => s.UseSqliteAsStorageProvider(),
s => s.UseSqlAsStorageProvider(),
s => s.UseInMemoryAsStorageProvider()
};
[Theory]
[MemberData(nameof(Data))]
public void TestStorageProvider(
Action<IServiceCollection> act)
{ … }
Точно такой же тест, но более типобезопасный. Теперь компилятор не позволит передать в метод ничего, кроме Action<IServiceCollection>.
Источник: https://steven-giesel.com/blogPost/a8aa3385-8829-444a-b269-7ecb38aeaf2f/typesafety-in-xunit-with-theorydatat
👍21
Когда метод Validate вызывается на объекте, реализующем IValidatableObject, какой возвращается результат?
#Quiz #ASPNET
#Quiz #ASPNET
Anonymous Quiz
14%
Значение True/False, обозначающее, что все свойства прошли валидацию
29%
IEnumerable из объектов ValidationResult с ошибками валидации
53%
Объект ValidationResult, включающий True/False результата валидации и коллекцию ошибок валиации
4%
IEnumerable всех свойств объекта с флагом валидности
День 1866. #ЗаметкиНаПолях
Определяем Разные Строковые Представления Объекта
Даже если внутренние данные одинаковы, иногда их можно представить по-разному. Взять структуру DateTime. Используя разные модификаторы, вы можете представлять одну и ту же дату в разных форматах.
Одна дата, но написана по-разному. Также можно предоставить культуру:
Интерфейс IFormattable позволяет определить такое поведение в наших классах, переопределив метод ToString(). Например:
Теперь мы можем определить различные форматы. Согласно принципу близости данных (хранить данные рядом с местом их использования), добавим в Person вложенный класс с доступными форматами:
Наконец, реализуем ToString() с нашими форматами и случаем, когда формат не распознается:
Заметьте, что в случае Formats.Full мы используем FormattableString, чтобы применить параметр IFormatProvider к конечному результату.
Тестируем
Источник: https://www.code4it.dev/csharptips/iformattable-interface/
Определяем Разные Строковые Представления Объекта
Даже если внутренние данные одинаковы, иногда их можно представить по-разному. Взять структуру DateTime. Используя разные модификаторы, вы можете представлять одну и ту же дату в разных форматах.
var dt = new DateTime(2024, 1, 1, 8, 53, 14);
dt.ToString("yyyy-MM-dddd"); //2024-01-Monday
dt.ToString("Y"); //January 2024
Одна дата, но написана по-разному. Также можно предоставить культуру:
var it = new CultureInfo("it-IT");
dt.ToString("yyyy-MM-dddd", it); //2024-01-lunedì
Интерфейс IFormattable позволяет определить такое поведение в наших классах, переопределив метод ToString(). Например:
public class Person : IFormattable
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public string ToString(
string? format,
IFormatProvider? fp)
{
// Здесь определяем логику форматирования
}
}
Теперь мы можем определить различные форматы. Согласно принципу близости данных (хранить данные рядом с местом их использования), добавим в Person вложенный класс с доступными форматами:
public class Person : IFormattable
{
…
public static class Formats
{
public const string FirstAndLastName = "FL";
public const string Mini = "Mini";
public const string Full = "Full";
}
}
Наконец, реализуем ToString() с нашими форматами и случаем, когда формат не распознается:
public string ToString(
string? format,
IFormatProvider? fp)
{
switch (format)
{
case Formats.FirstAndLastName:
return $"{FirstName} {LastName}";
case Formats.Full:
{
FormattableString fs =
$"{FirstName} {LastName} ({BirthDate:D})";
return fs.ToString(fp);
}
case Formats.Mini:
return $"{FirstName[0]}.{LastName[0]}";
default:
return this.ToString();
}
}
Заметьте, что в случае Formats.Full мы используем FormattableString, чтобы применить параметр IFormatProvider к конечному результату.
Тестируем
var p = new Person
{
FirstName = "Albert",
LastName = "Einstein",
BirthDate = new DateTime(1879, 3, 14)
};
p.ToString(Person.Formats.FirstAndLastName, it);
//Albert Einstein
p.ToString(Person.Formats.Full, it);
//Albert Einstein (venerdì 14 marzo 1879)
p.ToString(Person.Formats.Full, null);
//Albert Einstein (Friday, March 14, 1879)
p.ToString("INVALID FORMAT", CultureInfo.InvariantCulture);
//Person
$"I am not {p:Full}";
//I am not Albert Einstein (Friday, March 14, 1879)
Источник: https://www.code4it.dev/csharptips/iformattable-interface/
👍22
День 1867. #BestPractices
Избегайте Нескольких Булевых Параметров
Булевы параметры хороши, но сложно отследить, что делает каждый из них, если их несколько. Сегодня посмотрим, почему лучше избегать использования нескольких булевых параметров и как это отрефакторить.
Почему это плохо?
Посмотрим на следующий метод:
Это пример из библиотеки компонентов ImageListView. Проблема здесь в том, что два логических параметра обозначают четыре разные комбинации значений:
И в некоторой степени вы ожидаете четырёх разных вариантов поведения. Но в реализации это не так:
Разрешены только определённые комбинации. Кроме того, «сила» и «ленивость» кажутся в этом контексте противоречивыми. Трудно запомнить, что делает каждая комбинация. Можно сказать, что это в некоторой степени нарушает «принцип единой ответственности», поскольку метод выполняет несколько задач.
Другой частый пример: есть уже существующий метод, и какой-то новый метод должен расширить/изменить его поведение. Для простоты предположим, что нужна сортировка. Самый простой способ — добавить булев параметр:
Проблема здесь – понять, что происходит. И чем больше параметров, тем больше комбинаций и тем сложнее понять метод.
Рефакторинг
1. Сделать каждую комбинацию отдельным методом
Так все варианты очевидны, и вы можете использовать более выразительное имя метода. Из примера выше:
Теперь ясно, что недопустимо использовать оба параметра сразу. При этом мы возлагаем на вызывающую сторону ответственность за решение, какой метод вызывать. Клиент знает, что он хочет сделать, и может выразить это более чётко. Внутри эти методы, безусловно, могут совместно использовать большую часть кода. Кроме того, если нужно изменить поведение одного из методов, это гораздо проще сделать, поскольку между ними меньше связи.
Замечание: надо различать общедоступный API и закрытые методы. Можно иметь несколько булевых параметров в закрытых методах, но надо стараться избегать их в публичных API. Публичный API - интерфейс с внешним миром и должен быть максимально понятным.
2. Использовать перечисления с возможными вариантами
Хотя здесь показаны все возможные варианты, это не так ясно, как предыдущий подход. Все ещё возможно, что метод делает слишком много. Здесь всегда компромисс между слишком большим количеством параметров и слишком большим количеством методов.
Источник: https://steven-giesel.com/blogPost/9994b00c-8bc2-4794-ae74-80e6ee4cd5e5/avoid-multiple-boolean-parameters
Избегайте Нескольких Булевых Параметров
Булевы параметры хороши, но сложно отследить, что делает каждый из них, если их несколько. Сегодня посмотрим, почему лучше избегать использования нескольких булевых параметров и как это отрефакторить.
Почему это плохо?
Посмотрим на следующий метод:
/// <summary>
/// Refreshes the control.
/// </summary>
/// <param name="force">Forces a refresh even if the renderer is suspended.</param>
/// <param name="lazy">Refreshes the control only if a set amount of time
/// has passed since the last refresh.</param>
void Refresh(bool force, bool lazy);
Это пример из библиотеки компонентов ImageListView. Проблема здесь в том, что два логических параметра обозначают четыре разные комбинации значений:
Refresh(true, true);
Refresh(true, false);
Refresh(false, true);
Refresh(false, false);
И в некоторой степени вы ожидаете четырёх разных вариантов поведения. Но в реализации это не так:
internal void Refresh(bool force, bool lazy)
{
if (force)
base.Refresh();
else if (lazy)
{
rendererNeedsPaint = true;
lazyRefreshTimer.Start();
}
else if (CanPaint())
base.Refresh();
else
rendererNeedsPaint = true;
}
Разрешены только определённые комбинации. Кроме того, «сила» и «ленивость» кажутся в этом контексте противоречивыми. Трудно запомнить, что делает каждая комбинация. Можно сказать, что это в некоторой степени нарушает «принцип единой ответственности», поскольку метод выполняет несколько задач.
Другой частый пример: есть уже существующий метод, и какой-то новый метод должен расширить/изменить его поведение. Для простоты предположим, что нужна сортировка. Самый простой способ — добавить булев параметр:
IEnumerable<Item> GetItems(Filter f);
IEnumerable <Item> GetItems(Filter f, bool sort);
// или ещё хуже:
IEnumerable <Item> GetItems(Filter f, bool sort, bool descending);
Проблема здесь – понять, что происходит. И чем больше параметров, тем больше комбинаций и тем сложнее понять метод.
Рефакторинг
1. Сделать каждую комбинацию отдельным методом
Так все варианты очевидны, и вы можете использовать более выразительное имя метода. Из примера выше:
void Refresh();
void RefreshForce();
void RefreshLazy();
Теперь ясно, что недопустимо использовать оба параметра сразу. При этом мы возлагаем на вызывающую сторону ответственность за решение, какой метод вызывать. Клиент знает, что он хочет сделать, и может выразить это более чётко. Внутри эти методы, безусловно, могут совместно использовать большую часть кода. Кроме того, если нужно изменить поведение одного из методов, это гораздо проще сделать, поскольку между ними меньше связи.
Замечание: надо различать общедоступный API и закрытые методы. Можно иметь несколько булевых параметров в закрытых методах, но надо стараться избегать их в публичных API. Публичный API - интерфейс с внешним миром и должен быть максимально понятным.
2. Использовать перечисления с возможными вариантами
enum RefreshMode { Default, Force, Lazy }
void Refresh(RefreshMode mode);
Хотя здесь показаны все возможные варианты, это не так ясно, как предыдущий подход. Все ещё возможно, что метод делает слишком много. Здесь всегда компромисс между слишком большим количеством параметров и слишком большим количеством методов.
Источник: https://steven-giesel.com/blogPost/9994b00c-8bc2-4794-ae74-80e6ee4cd5e5/avoid-multiple-boolean-parameters
👍20
День 1868. #Шпаргалка #Git
Популярные Настройки Git Config. Начало
Джулия Эванс спросила у коллег-программистов мнения о самых популярных и полезных настройках Git, которые они используют. Вот такой список получился.
Описание всех настроек можно найти в документации Git.
1. pull.ff only или pull.rebase true
Эти две были самыми популярными. Обе они преследуют схожие цели: избежать случайного создания коммита слияния при запуске
- pull.rebase true эквивалентен запуску
- pull.ff only эквивалентен запуску
Скорее всего, нет смысла устанавливать обе одновременно, поскольку
2. merge.conflictstyle zdiff3
Делаем конфликты слияний более читабельными!
По умолчанию в git конфликты слияния выглядят так:
Вам предлагается решить, что лучше:
Вот как выглядит тот же конфликт слияния с merge.conflictstyle diff3:
Здесь содержится дополнительная информация: теперь исходная версия кода находится посередине! Итак, мы видим:
- одна сторона изменила
- другая сторона переименовала
Поэтому, по-видимому, правильным решением конфликта слияния является
zdiff3 — это тот же diff3, но он лучше выносит любые общие строки в начале или конце за пределы зоны конфликта. То есть, если обе стороны сделали изменения, которые имеют общие строки в начале или в конце, то они будут корректно слиты и не будут входить в зону конфликта.
3. rebase.autosquash true
Цель autosquash — упростить модификацию старых коммитов. Допустим, у вас есть коммит с исправлением другого коммита (3 коммита назад), который вы хотели бы объединить с ним. Вы фиксируете новый коммит с помощью
Теперь, когда вы запускаете
rebase.autosquash true означает, что
4. rebase.autostash true
Это автоматически запускает
Продолжение следует…
Источник: https://jvns.ca/blog/2024/02/16/popular-git-config-options/
Популярные Настройки Git Config. Начало
Джулия Эванс спросила у коллег-программистов мнения о самых популярных и полезных настройках Git, которые они используют. Вот такой список получился.
Описание всех настроек можно найти в документации Git.
1. pull.ff only или pull.rebase true
Эти две были самыми популярными. Обе они преследуют схожие цели: избежать случайного создания коммита слияния при запуске
git pull
на ветке, где восходящая ветвь расходится.- pull.rebase true эквивалентен запуску
git pull --rebase
каждый раз,- pull.ff only эквивалентен запуску
git pull --ff-only
каждый раз.Скорее всего, нет смысла устанавливать обе одновременно, поскольку
--ff-only
переопределяет --rebase
.2. merge.conflictstyle zdiff3
Делаем конфликты слияний более читабельными!
По умолчанию в git конфликты слияния выглядят так:
<<<<<<< HEAD
def parse(input):
return input.split("\n")
=======
def parse(text):
return text.split("\n\n")
>>>>>>> somebranch
Вам предлагается решить, что лучше:
input.split("\n")
или text.split("\n\n")
. Но как? Что делать, если вы не помните, нужно \n
или \n\n
?Вот как выглядит тот же конфликт слияния с merge.conflictstyle diff3:
<<<<<<< HEAD
def parse(input):
return input.split("\n")
||||||| base
def parse(input):
return input.split("\n\n")
=======
def parse(text):
return text.split("\n\n")
>>>>>>> somebranch
Здесь содержится дополнительная информация: теперь исходная версия кода находится посередине! Итак, мы видим:
- одна сторона изменила
\n\n
на \n
,- другая сторона переименовала
input
в text
.Поэтому, по-видимому, правильным решением конфликта слияния является
return text.split("\n")
, поскольку он объединяет изменения с обеих сторон.zdiff3 — это тот же diff3, но он лучше выносит любые общие строки в начале или конце за пределы зоны конфликта. То есть, если обе стороны сделали изменения, которые имеют общие строки в начале или в конце, то они будут корректно слиты и не будут входить в зону конфликта.
3. rebase.autosquash true
Цель autosquash — упростить модификацию старых коммитов. Допустим, у вас есть коммит с исправлением другого коммита (3 коммита назад), который вы хотели бы объединить с ним. Вы фиксируете новый коммит с помощью
git commit --fixup OLD_COMMIT_ID
, что даёт новому коммиту сообщение «fixup! …».Теперь, когда вы запускаете
git rebase --autosquash main
, он автоматически объединяет все коммиты-исправления (fixup!) со своими целями.rebase.autosquash true означает, что
--autosquash
всегда автоматически передаётся в git rebase
.4. rebase.autostash true
Это автоматически запускает
git stash
перед git rebase
и git stash pop
после. По сути, он передаёт --autostash
в git rebase
. Это означает, что вы можете запустить rebase на «грязном» рабочем дереве. Однако будьте осторожны: применение изменений из stash после успешного перебазирования может привести к нетривиальным конфликтам. Хотя, похоже, это не очень часто встречается у людей, поскольку этот вариант конфигурации кажется действительно популярным.Продолжение следует…
Источник: https://jvns.ca/blog/2024/02/16/popular-git-config-options/
👍13
День 1869. #Шпаргалка #Git
Популярные Настройки Git Config. Продолжение
Начало
5. push.default simple и push.default current
Параметры push.default сообщают git push автоматически отправлять текущую ветку в удалённую ветку с тем же именем.
-
-
current кажется хорошей настройкой, если вы уверены, что никогда случайно не создадите локальную ветку с тем же именем, что и несвязанная удалённая ветка. У многих людей есть соглашения об именах веток (например,
6. init.defaultBranch main
Создавать ветку main вместо ветки master при создании нового репозитория.
7. commit.verbose true
Добавит все различия коммита в текстовый редактор, где вы пишете сообщение о коммите, чтобы помочь вам запомнить, что вы делали.
8. rerere.enabled true
Позволяет использовать функцию rerere (reuse recovered resolution - повторно использовать восстановленное разрешение), которая запоминает, как вы разрешали конфликты слияния во время git rebase, и автоматически разрешает конфликты за вас, когда это возможно.
9. help.autocorrect 10
Если git обнаружит опечатки и сможет определить ровно одну действительную команду, аналогичную ошибке, он попытается предложить правильную команду или даже автоматически запустить предложение. Возможные значения:
-
-
-
-
-
10. core.pager delta
«Пейджер» — это то, что git использует для отображения результатов git diff, git log, git show и т. д.
delta – это модный инструмент для просмотра различий с подсветкой синтаксиса.
11. diff.algorithm histogram
Алгоритм сравнения Git по умолчанию часто плохо справляется с переупорядочением функций. Например:
Это сильно путает. Но с diff.algorithm histogram всё становится гораздо понятнее:
Ещё один популярный вариант - patience.
12. core.excludesFile – глобальный .gitignore
Окончание следует…
Источник: https://jvns.ca/blog/2024/02/16/popular-git-config-options/
Популярные Настройки Git Config. Продолжение
Начало
5. push.default simple и push.default current
Параметры push.default сообщают git push автоматически отправлять текущую ветку в удалённую ветку с тем же именем.
-
push.default simple
— значение по умолчанию в Git. Это работает только в том случае, если ваша ветка уже отслеживает удалённую ветку.-
push.default current
- аналогична, но она всегда передаёт локальную ветку в удалённую ветку с тем же именем.current кажется хорошей настройкой, если вы уверены, что никогда случайно не создадите локальную ветку с тем же именем, что и несвязанная удалённая ветка. У многих людей есть соглашения об именах веток (например,
julia/my-change
), которые делают конфликты такого рода маловероятными, либо у них просто мало сотрудников, поэтому конфликтов имён ветвей, вероятно, не происходит.6. init.defaultBranch main
Создавать ветку main вместо ветки master при создании нового репозитория.
7. commit.verbose true
Добавит все различия коммита в текстовый редактор, где вы пишете сообщение о коммите, чтобы помочь вам запомнить, что вы делали.
8. rerere.enabled true
Позволяет использовать функцию rerere (reuse recovered resolution - повторно использовать восстановленное разрешение), которая запоминает, как вы разрешали конфликты слияния во время git rebase, и автоматически разрешает конфликты за вас, когда это возможно.
9. help.autocorrect 10
Если git обнаружит опечатки и сможет определить ровно одну действительную команду, аналогичную ошибке, он попытается предложить правильную команду или даже автоматически запустить предложение. Возможные значения:
-
0
(по умолчанию) - показать предложенную команду.-
положительное число
- запустить предложенную команду через указанные децисекунды (0,1 секунды).-
"immediate"
- немедленно запустить предложенную команду.-
"prompt"
- показать предложение и запросить подтверждение для запуска команды.-
"never"
- не запускать и не показывать предлагаемые команды.10. core.pager delta
«Пейджер» — это то, что git использует для отображения результатов git diff, git log, git show и т. д.
delta – это модный инструмент для просмотра различий с подсветкой синтаксиса.
11. diff.algorithm histogram
Алгоритм сравнения Git по умолчанию часто плохо справляется с переупорядочением функций. Например:
-.header {
+.footer {
margin: 0;
}
-.footer {
+.header {
margin: 0;
+ color: green;
}
Это сильно путает. Но с diff.algorithm histogram всё становится гораздо понятнее:
-.header {
- margin: 0;
-}
-
.footer {
margin: 0;
}
+.header {
+ margin: 0;
+ color: green;
+}
Ещё один популярный вариант - patience.
12. core.excludesFile – глобальный .gitignore
core.excludesFile = ~/.gitignore
позволяет установить глобальный файл gitignore, который применяется ко всем репозиториям, для таких вещей, как .idea или .vs, которые вы никогда не хотите коммитить в какой-либо репозиторий. По умолчанию это ~/.config/git/ignore
.Окончание следует…
Источник: https://jvns.ca/blog/2024/02/16/popular-git-config-options/
👍12
День 1870. #Шпаргалка #Git
Популярные Настройки Git Config. Окончание
Начало
Продолжение
13. includeIf: отдельные конфигурации git для личного и рабочего
Многие используют это для настройки разных email для личных и рабочих репозиториев. Вы можете настроить это примерно так:
14. url."[email protected]:".insteadOf 'https://github.com/'
Если вы часто случайно клонируете HTTP-версию репозитория вместо SSH-версии, а затем приходится вручную заходить в ~/.git/config и редактировать удалённый URL, это заменит https://github.com в удалённых репозиториях на [email protected]:
Кто-то вместо этого использует pushInsteadOf для замены только в git push, потому что не хочет разблокировать свой SSH-ключ при извлечении из общедоступного репозитория.
15. fsckobjects
Проверяет получаемые/отправляемые объекты. Если будет обнаружено повреждение или ссылка на несуществующий объект, операция прервётся:
16. Остальное
-
Позволяет указать файл с коммитами, который следует игнорировать во время git blame, чтобы гигантские переименования не мешал.
-
Заставит git branch сортировать ветки по последнему использованию, а не по алфавиту, чтобы упростить поиск ветвей. tag.sort taggerdate аналогично для тегов.
-
Отключает подстветку.
-
В Windows для работы совместно с коллегами на Unix.
-
Использовать emacs (или другой редактор) для сообщений коммита.
-
Использовать difftastic (или другой редактор) для отображения различий.
-
Использовать meld (или другой редактор) для разрешения конфликтов слияния.
-
Автоматически удалит локальные ветки и теги, которых больше нет в удалённом репозитории.
-
Позволит подписывать коммиты ключами SSH.
-
Отобразит даты как 2024-03-14 15:54:51 вместо Thu Mar 14 15:54:51 2024
-
Чтобы избавиться от файлов .orig, которые git создаёт при слиянии конфликтов.
-
Добавит новые теги вместе с добавляемыми коммитами.
-
Не позволит удалять коммиты во время rebase.
Источник: https://jvns.ca/blog/2024/02/16/popular-git-config-options/
Популярные Настройки Git Config. Окончание
Начало
Продолжение
13. includeIf: отдельные конфигурации git для личного и рабочего
Многие используют это для настройки разных email для личных и рабочих репозиториев. Вы можете настроить это примерно так:
[includeIf "gitdir:~/code/<work>/"]
path = "~/code/<work>/.gitconfig"
14. url."[email protected]:".insteadOf 'https://github.com/'
Если вы часто случайно клонируете HTTP-версию репозитория вместо SSH-версии, а затем приходится вручную заходить в ~/.git/config и редактировать удалённый URL, это заменит https://github.com в удалённых репозиториях на [email protected]:
[url "[email protected]:"]
insteadOf = https://github.com/
Кто-то вместо этого использует pushInsteadOf для замены только в git push, потому что не хочет разблокировать свой SSH-ключ при извлечении из общедоступного репозитория.
15. fsckobjects
Проверяет получаемые/отправляемые объекты. Если будет обнаружено повреждение или ссылка на несуществующий объект, операция прервётся:
transfer.fsckobjects = true
fetch.fsckobjects = true
receive.fsckObjects = true
16. Остальное
-
blame.ignoreRevsFile .git-blame-ignore-revs
Позволяет указать файл с коммитами, который следует игнорировать во время git blame, чтобы гигантские переименования не мешал.
-
branch.sort -committerdate
Заставит git branch сортировать ветки по последнему использованию, а не по алфавиту, чтобы упростить поиск ветвей. tag.sort taggerdate аналогично для тегов.
-
color.ui false
Отключает подстветку.
-
core.autocrlf false
В Windows для работы совместно с коллегами на Unix.
-
core.editor emacs
Использовать emacs (или другой редактор) для сообщений коммита.
-
diff.tool difftastic
Использовать difftastic (или другой редактор) для отображения различий.
-
merge.tool meld
Использовать meld (или другой редактор) для разрешения конфликтов слияния.
-
fetch.prune true
и fetch.prunetags
Автоматически удалит локальные ветки и теги, которых больше нет в удалённом репозитории.
-
gpg.format ssh
Позволит подписывать коммиты ключами SSH.
-
log.date iso
Отобразит даты как 2024-03-14 15:54:51 вместо Thu Mar 14 15:54:51 2024
-
merge.keepbackup false
Чтобы избавиться от файлов .orig, которые git создаёт при слиянии конфликтов.
-
push.followtags true
Добавит новые теги вместе с добавляемыми коммитами.
-
rebase.missingCommitsCheck error
Не позволит удалять коммиты во время rebase.
Источник: https://jvns.ca/blog/2024/02/16/popular-git-config-options/
👍7
День 1871. #ВопросыНаСобеседовании #ASPNET
Самые часто задаваемые вопросы на собеседовании по C#
30. Какие существуют возможности управления конфигурацией приложения .NET Core в разных средах (разработка/тестирование/производство)?
.NET Core предоставляет решение для управления конфигурациями в различных средах с помощью поставщиков конфигурации.
Во-первых, задать среду. Приложения .NET Core делают это, читая переменную окружения ASPNETCORE_ENVIRONMENT. Значение по умолчанию – "Production". Формально значение может быть любым, но лучше придерживаться одного из трёх стандартных:
- Development
- Staging
- Production
Это даст доступ, например, к методам расширения, таким как IHostingEnvironment.IsDevelopment(). Значение можно задать в переменных окружения машины. IDE, такие как Visual Studio, также могут брать это значение из файла launch.json из корня проекта.
Кроме того, среду можно задать принудительно в коде, вручную настроив веб хост:
Среда определяется на раннем этапе старта приложения, поэтому дальнейшую настройку можно определять в зависимости от среды. Например, для управления конфигурацией можно использовать файл appsettings.json, который является файлом по умолчанию, считываемым приложением .NET Core. Можно иметь отдельные файлы конфигурации для каждой среды, например appsettings.Development.json или appsettings.Production.json, что даёт возможность переопределить настройки по умолчанию.
Вообще есть несколько вариантов настройки:
1. Использовать различные настройки в коде в зависимости от среды, например:
2. Использовать различные файлы appsettings.{Environment}.json для хранения конфигураций, специфичных для среды.
3. Использовать поставщика конфигурации переменных окружения, чтобы задать параметры, специфичные для конкретной машины.
4. Важно исключить конфиденциальные данные, такие как строки подключения и секреты приложений, из системы управления версиями (например, файлов appsettings.json). Их лучше хранить либо в переменных среды, либо использовать безопасные методы, такие как пользовательские секреты в среде разработки или сервисы вроде Azure Key Vault в производственной среде.
В приложении использовать интерфейс IConfiguration для доступа к настроенным параметрам. См. подробнее.
См. также про изменения в конфигурации введённые в .NET6.
Источники:
- https://dev.to/bytehide/net-core-interview-question-answers-4bc1
- Эндрю Лок “ASP.NET Core в действии”. 2-е изд. – М.: ДМК Пресс, 2021. Глава 11.
Самые часто задаваемые вопросы на собеседовании по C#
30. Какие существуют возможности управления конфигурацией приложения .NET Core в разных средах (разработка/тестирование/производство)?
.NET Core предоставляет решение для управления конфигурациями в различных средах с помощью поставщиков конфигурации.
Во-первых, задать среду. Приложения .NET Core делают это, читая переменную окружения ASPNETCORE_ENVIRONMENT. Значение по умолчанию – "Production". Формально значение может быть любым, но лучше придерживаться одного из трёх стандартных:
- Development
- Staging
- Production
Это даст доступ, например, к методам расширения, таким как IHostingEnvironment.IsDevelopment(). Значение можно задать в переменных окружения машины. IDE, такие как Visual Studio, также могут брать это значение из файла launch.json из корня проекта.
Кроме того, среду можно задать принудительно в коде, вручную настроив веб хост:
var builder = new WebHostBuilder()
.UseEnvironment(Environments.Production)
.UseKestrel()
.… ;
Среда определяется на раннем этапе старта приложения, поэтому дальнейшую настройку можно определять в зависимости от среды. Например, для управления конфигурацией можно использовать файл appsettings.json, который является файлом по умолчанию, считываемым приложением .NET Core. Можно иметь отдельные файлы конфигурации для каждой среды, например appsettings.Development.json или appsettings.Production.json, что даёт возможность переопределить настройки по умолчанию.
Вообще есть несколько вариантов настройки:
1. Использовать различные настройки в коде в зависимости от среды, например:
…
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
…
2. Использовать различные файлы appsettings.{Environment}.json для хранения конфигураций, специфичных для среды.
3. Использовать поставщика конфигурации переменных окружения, чтобы задать параметры, специфичные для конкретной машины.
4. Важно исключить конфиденциальные данные, такие как строки подключения и секреты приложений, из системы управления версиями (например, файлов appsettings.json). Их лучше хранить либо в переменных среды, либо использовать безопасные методы, такие как пользовательские секреты в среде разработки или сервисы вроде Azure Key Vault в производственной среде.
В приложении использовать интерфейс IConfiguration для доступа к настроенным параметрам. См. подробнее.
См. также про изменения в конфигурации введённые в .NET6.
Источники:
- https://dev.to/bytehide/net-core-interview-question-answers-4bc1
- Эндрю Лок “ASP.NET Core в действии”. 2-е изд. – М.: ДМК Пресс, 2021. Глава 11.
👍22
День 1872. #ЗаметкиНаПолях
Простая Шина Сообщений в Памяти, Используя Каналы. Начало
Обмен сообщениями играет важную роль в современной архитектуре ПО, обеспечивая координацию между слабосвязанными компонентами. Шина сообщений в памяти особенно полезна, когда критическими требованиями являются высокая производительность и низкая задержка.
Недостатки
- Потеря сообщений при сбое процесса программы.
- Работает только внутри одного процесса, поэтому бесполезна в распределённых системах.
Практический вариант использования — создание модульного монолита. Можно реализовать связь между модулями через доменные события. Когда нужно будет выделить какой-то модуль в отдельный сервис, можно заменить шину в памяти на распределённую.
Абстракции для шины
Нам нужны две абстракции для публикации сообщений и для обработчика сообщений.
Интерфейс IEventBus предоставляет метод PublishAsync для публикации сообщений. Также определено ограничение, которое позволяет передавать только экземпляр IDomainEvent.
Используем MediatR для модели издатель-подписчик. Интерфейс IDomainEvent будет наследоваться от INotification. Это позволит легко определять обработчики IDomainEvent с помощью INotificationHandler<T>. Кроме того, в IDomainEvent добавим идентификатор, чтобы отслеживать выполнение. Абстрактный класс DomainEvent будет базовым для конкретных реализаций.
Простая очередь в памяти с использованием каналов
Пространство имён System.Threading.Channels предоставляет структуры данных для асинхронной передачи сообщений между производителями и потребителями. Производители асинхронно создают данные, а потребители асинхронно потребляют их. В отличие от традиционных очередей сообщений, каналы полностью работают в памяти. Недостатком этого подхода является возможность потери сообщения в случае сбоя приложения.
MessageQueue создаёт неограниченный канал, т.е. у канала может быть любое количество читателей и писателей. Он также предоставляет ChannelReader и ChannelWriter, которые позволяют клиентам публиковать и потреблять сообщения.
Зарегистрируем сервис как синглтон:
Реализация шины событий
Класс EventBus использует MessageQueue для доступа к ChannelWriter и записи события в канал.
Шину также регистрируем как синглтон, т.к. она не сохраняет состояния:
Окончание следует…
Источник: https://www.milanjovanovic.tech/blog/lightweight-in-memory-message-bus-using-dotnet-channels
Простая Шина Сообщений в Памяти, Используя Каналы. Начало
Обмен сообщениями играет важную роль в современной архитектуре ПО, обеспечивая координацию между слабосвязанными компонентами. Шина сообщений в памяти особенно полезна, когда критическими требованиями являются высокая производительность и низкая задержка.
Недостатки
- Потеря сообщений при сбое процесса программы.
- Работает только внутри одного процесса, поэтому бесполезна в распределённых системах.
Практический вариант использования — создание модульного монолита. Можно реализовать связь между модулями через доменные события. Когда нужно будет выделить какой-то модуль в отдельный сервис, можно заменить шину в памяти на распределённую.
Абстракции для шины
Нам нужны две абстракции для публикации сообщений и для обработчика сообщений.
Интерфейс IEventBus предоставляет метод PublishAsync для публикации сообщений. Также определено ограничение, которое позволяет передавать только экземпляр IDomainEvent.
public interface IEventBus
{
Task PublishAsync<T>(
T domainEvent,
CancellationToken ct = default)
where T : class, IDomainEvent;
}
Используем MediatR для модели издатель-подписчик. Интерфейс IDomainEvent будет наследоваться от INotification. Это позволит легко определять обработчики IDomainEvent с помощью INotificationHandler<T>. Кроме того, в IDomainEvent добавим идентификатор, чтобы отслеживать выполнение. Абстрактный класс DomainEvent будет базовым для конкретных реализаций.
using MediatR;
public interface IDomainEvent : INotification
{
Guid Id { get; init; }
}
public abstract record DomainEvent(Guid Id)
: IDomainEvent;
Простая очередь в памяти с использованием каналов
Пространство имён System.Threading.Channels предоставляет структуры данных для асинхронной передачи сообщений между производителями и потребителями. Производители асинхронно создают данные, а потребители асинхронно потребляют их. В отличие от традиционных очередей сообщений, каналы полностью работают в памяти. Недостатком этого подхода является возможность потери сообщения в случае сбоя приложения.
MessageQueue создаёт неограниченный канал, т.е. у канала может быть любое количество читателей и писателей. Он также предоставляет ChannelReader и ChannelWriter, которые позволяют клиентам публиковать и потреблять сообщения.
internal class MessageQueue
{
private readonly Channel<IDomainEvent> _channel =
Channel.CreateUnbounded<IDomainEvent>();
public ChannelReader<IDomainEvent>
Reader => _channel.Reader;
public ChannelWriter<IDomainEvent>
Writer => _channel.Writer;
}
Зарегистрируем сервис как синглтон:
builder.Services.AddSingleton<MessageQueue>();
Реализация шины событий
Класс EventBus использует MessageQueue для доступа к ChannelWriter и записи события в канал.
internal class EventBus(MessageQueue queue)
: IEventBus
{
public async Task PublishAsync<T>(
T domainEvent,
CancellationToken ct = default)
where T : class, IDomainEvent
{
await queue.Writer.WriteAsync(
domainEvent, ct);
}
}
Шину также регистрируем как синглтон, т.к. она не сохраняет состояния:
builder.Services.AddSingleton<IEventBus, EventBus>();
Окончание следует…
Источник: https://www.milanjovanovic.tech/blog/lightweight-in-memory-message-bus-using-dotnet-channels
👍17
День 1873. #ЗаметкиНаПолях
Простая Шина Сообщений в Памяти, Используя Каналы. Окончание
Начало
Потребление событий
Потребление опубликованного IDomainEvent можно реализовать через фоновый сервис (IHostedService). EventProcessorJob использует MessageQueue для чтения (потребления) сообщений. Используем ChannelReader.ReadAllAsync, чтобы получить IAsyncEnumerable, что позволит асинхронно обрабатывать все сообщения в канале.
IPublisher из MediatR поможет связать IDomainEvent с обработчиками. Если мы используем scoped-сервисы, важно получать их из области, для этого внедрим IServiceScopeFactory.
Зарегистрируем сервис:
Использование шины
Сервис IEventBus запишет сообщение в канал и немедленно вернёт управление. Это позволяет публиковать сообщения неблокирующим способом, что повышает производительность. Допустим, при регистрации нового пользователя, нам нужно опубликовать и обработать доменное событие NewUserEvent. Производитель:
Для потребителя нужно определить реализацию INotificationHandler, обрабатывающую доменное событие NewUserEvent, - NewUserEventHandler. Когда фоновое задание EventProcessorJob считает NewUserEvent из канала, оно опубликует сообщение в MediatR и выполнит обработчик.
Возможные улучшения
- Устойчивость. Мы можем добавить повторные попытки при возникновении исключений, что повысит надёжность шины сообщений.
- Идемпотентность. Надо ли вы обрабатывать одно и то же сообщение дважды? Если нет, стоит регистрировать обработанные события и проверять перед обработкой, не были ли они обработаны ранее.
- Очередь недоставленных сообщений. Иногда мы не можем правильно обработать сообщение. Можно создать постоянное хранилище для этих сообщений, что позволит устранить неполадки позднее.
Источник: https://www.milanjovanovic.tech/blog/lightweight-in-memory-message-bus-using-dotnet-channels
Простая Шина Сообщений в Памяти, Используя Каналы. Окончание
Начало
Потребление событий
Потребление опубликованного IDomainEvent можно реализовать через фоновый сервис (IHostedService). EventProcessorJob использует MessageQueue для чтения (потребления) сообщений. Используем ChannelReader.ReadAllAsync, чтобы получить IAsyncEnumerable, что позволит асинхронно обрабатывать все сообщения в канале.
IPublisher из MediatR поможет связать IDomainEvent с обработчиками. Если мы используем scoped-сервисы, важно получать их из области, для этого внедрим IServiceScopeFactory.
internal class EventProcessorJob(
MessageQueue queue,
IServiceScopeFactory sf,
ILogger<EventProcessorJob> logger)
: BackgroundService
{
protected override async Task
ExecuteAsync(CancellationToken ct)
{
await foreach (IDomainEvent e in
queue.Reader.ReadAllAsync(ct))
{
try
{
using var scope = sf.CreateScope();
var pub = scope.ServiceProvider
.GetRequiredService<IPublisher>();
await pub.Publish(e, ct);
}
catch (Exception ex)
{
logger.LogError(ex,
"Failed! {DomainEventId}",
e.Id);
}
}
}
}
Зарегистрируем сервис:
csharp
builder.Services
.AddHostedService<EventProcessorJob>();
Использование шины
Сервис IEventBus запишет сообщение в канал и немедленно вернёт управление. Это позволяет публиковать сообщения неблокирующим способом, что повышает производительность. Допустим, при регистрации нового пользователя, нам нужно опубликовать и обработать доменное событие NewUserEvent. Производитель:
internal class UserService(
IUserRepository userRepo,
IEventBus eventBus)
{
public async Task<User> Register(
User user,
CancellationToken ct)
{
// Добавляем пользователя
userRepo.Add(user);
// Публикуем событие
await eventBus.PublishAsync(
new NewUserEvent(user.Id),
ct);
return user;
}
}
Для потребителя нужно определить реализацию INotificationHandler, обрабатывающую доменное событие NewUserEvent, - NewUserEventHandler. Когда фоновое задание EventProcessorJob считает NewUserEvent из канала, оно опубликует сообщение в MediatR и выполнит обработчик.
internal class NewUserEventHandler
: INotificationHandler<NewUserEvent>
{
public async Task Handle(
NewUserEvent event,
CancellationToken ct)
{
// Асинхронно обрабатываем событие, например
// отправим email новому пользователю
}
}
Возможные улучшения
- Устойчивость. Мы можем добавить повторные попытки при возникновении исключений, что повысит надёжность шины сообщений.
- Идемпотентность. Надо ли вы обрабатывать одно и то же сообщение дважды? Если нет, стоит регистрировать обработанные события и проверять перед обработкой, не были ли они обработаны ранее.
- Очередь недоставленных сообщений. Иногда мы не можем правильно обработать сообщение. Можно создать постоянное хранилище для этих сообщений, что позволит устранить неполадки позднее.
Источник: https://www.milanjovanovic.tech/blog/lightweight-in-memory-message-bus-using-dotnet-channels
👍7
Сколько областей видимости переменных в блоке try...catch...finally, обрабатывающем два вида исключений?
#Quiz #CSharp
#Quiz #CSharp
Anonymous Quiz
14%
1
16%
2
27%
3
38%
4
5%
5
👍2