День 1510. #ЗаметкиНаПолях
Создание Параметризованных Тестов в xUnit
xUnit использует атрибуты для определения методов тестирования. Атрибут Fact определяет простой тест, а атрибут Theory определяет параметризованный тест. Допустим, у нас есть следующий тест, проверяющий, что парсер email корректно извлекает домен:
Рассмотрим 4 способа написания параметризованных тестов.
1. InlineData
Простейший способ - вы предоставляете тестовые данные, передавая значения конструктору:
Мы предоставляем два строковых значения для атрибута InlineData: аргумент тестируемого метода и ожидаемый результат. Можно указать атрибут InlineData столько раз, сколько будет тестовых случаев.
Недостаток в том, что код становится очень многословным, когда у нас много тестовых случаев. И мы ограничены использованием только константных данных для параметров.
2. MemberData
С помощью MemberData мы можем программно предоставить тестовые данные из статического свойства или члена типа:
Вы указываете имя члена в атрибуте MemberData. Одно ограничение - свойство (или метод) должно возвращать IEnumerable<object[]>, поэтому строгой типизации не существует.
3. ClassData
Позволяет извлечь тестовые данные в отдельный класс. Это полезно для организации тестовых данных отдельно от тестов и упрощает повторное использование. Вы загружаете тестовые данные из класса, который наследуется от IEnumerable<object[]> и реализует метод GetEnumerator:
Этот подход сложен, потому что нужно реализовать интерфейс IEnumerable. И мы всё ещё страдаем от отсутствия безопасности типов.
4. TheoryData
Позволяет реализовать класс для тестовых данных, сохраняя при этом безопасность типов. Вот пример в сочетании с атрибутом ClassData, но его также можно использовать и с MemberData, возвращая TheoryData из свойства или метода:
TheoryData – обобщённый класс, который позволяет указывать типы для параметризованного теста. Вы просто вызываете метод Add в конструкторе, чтобы предоставить тестовые данные для одного тестового примера. А добавление дополнительных тестов сводится к многократному вызову метода Add.
Источник: https://www.milanjovanovic.tech/blog/creating-data-driven-tests-with-xunit
Создание Параметризованных Тестов в xUnit
xUnit использует атрибуты для определения методов тестирования. Атрибут Fact определяет простой тест, а атрибут Theory определяет параметризованный тест. Допустим, у нас есть следующий тест, проверяющий, что парсер email корректно извлекает домен:
public void Should_Return_Domain(
string email, string expectedDomain)
{
var parser = new EmailParser();
var domain = parser.GetDomain(email);
Assert.Equal(domain, expectedDomain);
}
Рассмотрим 4 способа написания параметризованных тестов.
1. InlineData
Простейший способ - вы предоставляете тестовые данные, передавая значения конструктору:
[Theory]
[InlineData("[email protected]", "test.com")]
[InlineData("[email protected]", "github.io")]
public void Should_Return_Domain(
string email, string expectedDomain)
{ … }
Мы предоставляем два строковых значения для атрибута InlineData: аргумент тестируемого метода и ожидаемый результат. Можно указать атрибут InlineData столько раз, сколько будет тестовых случаев.
Недостаток в том, что код становится очень многословным, когда у нас много тестовых случаев. И мы ограничены использованием только константных данных для параметров.
2. MemberData
С помощью MemberData мы можем программно предоставить тестовые данные из статического свойства или члена типа:
[Theory]
[MemberData(nameof(EmailTestData))]
public void Should_Return_Domain(
string email, string expectedDomain)
{ … }
public static IEnumerable<object[]>
EmailTestData => new List<object>
{
new object[] { "[email protected]", "test.com" },
new object[] { "[email protected]", "github.io" }
};
Вы указываете имя члена в атрибуте MemberData. Одно ограничение - свойство (или метод) должно возвращать IEnumerable<object[]>, поэтому строгой типизации не существует.
3. ClassData
Позволяет извлечь тестовые данные в отдельный класс. Это полезно для организации тестовых данных отдельно от тестов и упрощает повторное использование. Вы загружаете тестовые данные из класса, который наследуется от IEnumerable<object[]> и реализует метод GetEnumerator:
[Theory]
[ClassData(typeof(EmailTestData))]
public void Should_Return_Domain(
string email, string expectedDomain)
{ … }
public class EmailTestData : IEnumerable<object[]>
{
public IEnumerable<object[]> GetEnumerator()
{
yield return new object[] {
"[email protected]", "test.com" };
yield return new object[] {
"[email protected]", "github.io" };
}
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
};
Этот подход сложен, потому что нужно реализовать интерфейс IEnumerable. И мы всё ещё страдаем от отсутствия безопасности типов.
4. TheoryData
Позволяет реализовать класс для тестовых данных, сохраняя при этом безопасность типов. Вот пример в сочетании с атрибутом ClassData, но его также можно использовать и с MemberData, возвращая TheoryData из свойства или метода:
[Theory]
[ClassData(typeof(EmailTestData))]
public void Should_Return_Domain(
string email, string expectedDomain)
{ … }
public class EmailTestData :
TheoryData<string, string>
{
public EmailTestData()
{
Add("[email protected]", "test.com");
Add("[email protected]", "github.io");
}
};
TheoryData – обобщённый класс, который позволяет указывать типы для параметризованного теста. Вы просто вызываете метод Add в конструкторе, чтобы предоставить тестовые данные для одного тестового примера. А добавление дополнительных тестов сводится к многократному вызову метода Add.
Источник: https://www.milanjovanovic.tech/blog/creating-data-driven-tests-with-xunit
👍20
День 1511. #Алгоритмы
Использование Префиксного Дерева для Текстового Поиска
Сегодня рассмотрим алгоритм префиксного дерева (Trie) и способы его использования в C# для эффективного поиска текстовых шаблонов.
Trie (произносится «трай») — это древовидная структура данных, которая часто используется для быстрого хранения и извлечения строк. Она состоит из узлов, представляющих отдельные символы в строке. Корневой узел – пустая строка, а каждый дочерний представляет символ, который может следовать за строкой, представленной его родителем.
Преимущества
1) Быстрое определение, присутствует ли данная строка в наборе строк. Строка представлена в виде уникального пути в дереве, что позволяет легко определить её наличие или отсутствие.
2) Способность быстро находить все строки с заданным префиксом. Все строки с общим префиксом имеют один и тот же путь в дереве до конечной точки префикса.
Рассмотрим слова
Сохранение: Корневой узел пустой. Первый символ в обеих строках —
Поиск: Теперь можно легко определить, присутствует ли в дереве, например, строка
Реализуем структуру данных Trie. Это набор узлов, поэтому начнём с создания класса TrieNode:
Теперь создадим класс Trie, который хранит ссылку на корневой узел и предоставляет методы для вставки и поиска слов:
Чтобы найти слово, начинаем с корневого узла и перебираем каждый символ в слове. Проверяем, есть ли дочерний узел, представляющий этот символ. Если нет, слова нет в дереве. Если достигаем конца слова и свойство IsWord на последнем узле - true, значит слово находится в дереве.
Использование
Источник: https://code-maze.com/csharp-using-trie-class-for-efficient-text-pattern-searching/
Использование Префиксного Дерева для Текстового Поиска
Сегодня рассмотрим алгоритм префиксного дерева (Trie) и способы его использования в C# для эффективного поиска текстовых шаблонов.
Trie (произносится «трай») — это древовидная структура данных, которая часто используется для быстрого хранения и извлечения строк. Она состоит из узлов, представляющих отдельные символы в строке. Корневой узел – пустая строка, а каждый дочерний представляет символ, который может следовать за строкой, представленной его родителем.
Преимущества
1) Быстрое определение, присутствует ли данная строка в наборе строк. Строка представлена в виде уникального пути в дереве, что позволяет легко определить её наличие или отсутствие.
2) Способность быстро находить все строки с заданным префиксом. Все строки с общим префиксом имеют один и тот же путь в дереве до конечной точки префикса.
Рассмотрим слова
«cat»
и «car»
. Сохранение: Корневой узел пустой. Первый символ в обеих строках —
«c»
, поэтому добавляем дочерний узел. Аналогично добавляем дочерний узел для «а»
. Наконец, добавляем два дочерних узла из узла «a»
для представления символов «t»
и «r»
.Поиск: Теперь можно легко определить, присутствует ли в дереве, например, строка
«cab»
. А подсчёт всех строк с префиксом «ca»
— это просто количество дочерних узлов для узла «c» > «a»
.Реализуем структуру данных Trie. Это набор узлов, поэтому начнём с создания класса TrieNode:
public class TrieNodeTrieNode хранит флаг, указывающий, представляет ли узел конец слова в свойстве IsWord, а также словарь дочерних узлов.
{
public bool IsWord { get; set; }
public Dictionary<char, TrieNode>
Children { get; } = new();
}
Теперь создадим класс Trie, который хранит ссылку на корневой узел и предоставляет методы для вставки и поиска слов:
public class TrieЧтобы вставить слово, мы начинаем с корневого узла. Для каждого символа проверяем, есть ли дочерний узел, представляющий этот символ. Если нет, создаём новый и добавляем его в словарь. Затем переходим к дочернему узлу и повторяем процесс для следующего символа. Достигнув конца слова, устанавливаем для свойства IsWord последнего узла значение true.
{
private readonly TrieNode _root = new();
public void AddWord(string word)
{
var node = _root;
foreach (char c in word)
{
if (!node.Children.ContainsKey(c))
node.Children[c] = new();
node = node.Children[c];
}
node.IsWord = true;
}
public bool Search(string word)
{
var node = _root;
foreach (char c in word)
{
if (!node.Children.ContainsKey(c))
return false;
node = node.Children[c];
}
return node.IsWord;
}
}
Чтобы найти слово, начинаем с корневого узла и перебираем каждый символ в слове. Проверяем, есть ли дочерний узел, представляющий этот символ. Если нет, слова нет в дереве. Если достигаем конца слова и свойство IsWord на последнем узле - true, значит слово находится в дереве.
Использование
var trie = new Trie();Создаём экземпляр Trie и добавляем каждое слово предложения в него с помощью метода AddWord(). Далее с помощью метода Search() проверяем существование слова.
var text = "The quick brown fox jumps over the lazy dog";
foreach (var word in text.Split(' '))
trie.AddWord(word);
Console.WriteLine(trie.Search("quick"));
Источник: https://code-maze.com/csharp-using-trie-class-for-efficient-text-pattern-searching/
👍15
День 1512. #ЧтоНовенького
Новинки ASP.NET Core 8 Превью 2. Начало
Недавно Microsoft выпустила превью 2 .NET 8, в которую включены несколько улучшений в ASP.NET Core. Вот самые заметные.
1. QuickGrid — высокопроизводительный компонент сетки, который позволяет разработчикам отображать данные в табличном формате с расширенными функциями, такими как сортировка, фильтрация, разбиение по страницам и виртуализация. Чтобы его использовать необходима ссылка на пакет Microsoft.AspNetCore.Components.QuickGrid. Этот компонент ранее был экспериментальным пакетом для .NET 7 и претерпел изменения и улучшения API для версии .NET 8.
Если вы использовали QuickGrid в .NET 7, чтобы обновиться до .NET 8, придётся внести некоторые изменения:
- переименовать атрибут Value в State
- переименовать атрибут IsDefaultSort в InitialSortDirection,
- добавить IsDefaultSortColumn=true,
- удалить атрибут ResizableColumns.
На демо-странице https://aspnet.github.io/quickgridsamples/ можно попробовать QuickGrid в действии.
2. Улучшена производительность Blazor WebAssembly с помощью jiterpreter.
jiterpreter — это новая функция среды выполнения в .NET 8, которая обеспечивает частичную поддержку JIT в интерпретаторе .NET IL для повышения производительности среды выполнения.
Приложения Blazor WebAssembly могут запускать код .NET в браузере благодаря небольшой среде выполнения .NET, реализованной в WebAssembly, которая загружается вместе с приложением. Эта среда выполнения представляет собой интерпретатор .NET IL, который является полностью функциональным и небольшим по размеру, но ему не хватает преимуществ производительности выполнения нативного кода посредством JIT-компиляции. JIT для WebAssembly требует создания новых модулей WebAssembly на лету и создания их экземпляров, что создает проблемы для среды выполнения. Вместо этого приложения Blazor WebAssembly могут выбрать предварительную компиляцию (AOT) в WebAssembly, чтобы повысить производительность во время выполнения, но за счёт гораздо большего размера загрузки. Поскольку некоторые распространённые шаблоны кода .NET несовместимы с AOT, интерпретатор .NET IL по-прежнему необходим в качестве резервного механизма для поддержания полной функциональности.
jiterpreter оптимизирует выполнение байт-кодов интерпретатора, заменяя их крошечными фрагментами кода WebAssembly. Используя интерпретатор в качестве основы, мы можем оптимизировать наиболее важные части приложения без необходимости обрабатывать более сложные или непонятные случаи и без чрезмерного усложнения среды выполнения. Хотя jiterpreter не является полной реализацией JIT, он значительно повышает производительность во время выполнения без увеличения размера и времени сборки AOT. jiterpreter также помогает при использовании AOT, оптимизируя случаи, когда среда выполнения должна отступать от интерпретатора. Таким образом, jiterpreter может значительно ускорить выполнение низкоуровневых операций. В превью 2 .NET 8 jiterpreter автоматически включен.
Разработчики, которые хотят использовать ASP.NET Core с превью 2 .NET 8, должны сначала установить пакет SDK для .NET 8. Пользователям Visual Studio для Windows рекомендуется загрузить последнюю предварительную версию VS 2022. Visual Studio для Mac не поддерживает предварительные версии .NET 8 в настоящее время.
Окончание следует…
Источник: https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-2/
Новинки ASP.NET Core 8 Превью 2. Начало
Недавно Microsoft выпустила превью 2 .NET 8, в которую включены несколько улучшений в ASP.NET Core. Вот самые заметные.
1. QuickGrid — высокопроизводительный компонент сетки, который позволяет разработчикам отображать данные в табличном формате с расширенными функциями, такими как сортировка, фильтрация, разбиение по страницам и виртуализация. Чтобы его использовать необходима ссылка на пакет Microsoft.AspNetCore.Components.QuickGrid. Этот компонент ранее был экспериментальным пакетом для .NET 7 и претерпел изменения и улучшения API для версии .NET 8.
Если вы использовали QuickGrid в .NET 7, чтобы обновиться до .NET 8, придётся внести некоторые изменения:
- переименовать атрибут Value в State
- переименовать атрибут IsDefaultSort в InitialSortDirection,
- добавить IsDefaultSortColumn=true,
- удалить атрибут ResizableColumns.
На демо-странице https://aspnet.github.io/quickgridsamples/ можно попробовать QuickGrid в действии.
2. Улучшена производительность Blazor WebAssembly с помощью jiterpreter.
jiterpreter — это новая функция среды выполнения в .NET 8, которая обеспечивает частичную поддержку JIT в интерпретаторе .NET IL для повышения производительности среды выполнения.
Приложения Blazor WebAssembly могут запускать код .NET в браузере благодаря небольшой среде выполнения .NET, реализованной в WebAssembly, которая загружается вместе с приложением. Эта среда выполнения представляет собой интерпретатор .NET IL, который является полностью функциональным и небольшим по размеру, но ему не хватает преимуществ производительности выполнения нативного кода посредством JIT-компиляции. JIT для WebAssembly требует создания новых модулей WebAssembly на лету и создания их экземпляров, что создает проблемы для среды выполнения. Вместо этого приложения Blazor WebAssembly могут выбрать предварительную компиляцию (AOT) в WebAssembly, чтобы повысить производительность во время выполнения, но за счёт гораздо большего размера загрузки. Поскольку некоторые распространённые шаблоны кода .NET несовместимы с AOT, интерпретатор .NET IL по-прежнему необходим в качестве резервного механизма для поддержания полной функциональности.
jiterpreter оптимизирует выполнение байт-кодов интерпретатора, заменяя их крошечными фрагментами кода WebAssembly. Используя интерпретатор в качестве основы, мы можем оптимизировать наиболее важные части приложения без необходимости обрабатывать более сложные или непонятные случаи и без чрезмерного усложнения среды выполнения. Хотя jiterpreter не является полной реализацией JIT, он значительно повышает производительность во время выполнения без увеличения размера и времени сборки AOT. jiterpreter также помогает при использовании AOT, оптимизируя случаи, когда среда выполнения должна отступать от интерпретатора. Таким образом, jiterpreter может значительно ускорить выполнение низкоуровневых операций. В превью 2 .NET 8 jiterpreter автоматически включен.
Разработчики, которые хотят использовать ASP.NET Core с превью 2 .NET 8, должны сначала установить пакет SDK для .NET 8. Пользователям Visual Studio для Windows рекомендуется загрузить последнюю предварительную версию VS 2022. Visual Studio для Mac не поддерживает предварительные версии .NET 8 в настоящее время.
Окончание следует…
Источник: https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-2/
👍12
День 1513. #ЗаметкиНаПолях
Таймеры в .NET
В .NET есть как минимум 6 разных таймеров! Каждый для своей цели и варианта использования. Рассмотрим различия между ними.
Во-первых, есть специальные таймеры пользовательского интерфейса. Эти таймеры используются для выполнения кода в UI-потоке:
- System.Windows.Forms.Timer
- System.Windows.Threading.DispatcherTimer
Эти таймеры выполняют обратный вызов в UI-потоке. Таким образом, вы можете взаимодействовать с пользовательским интерфейсом в обоих случаях. Кроме того, поскольку событие вызывается в UI-потоке, одновременно выполняется только один обратный вызов. Таким образом, вам не нужно беспокоиться о потокобезопасности.
Для WebForms есть ещё один таймер: System.Web.UI.Timer. Он генерирует событие обратной передачи на сервере. Но не будем об устаревших технологиях)))
Наконец, 3 таймера, которые не зависят от пользовательского интерфейса.
1. System.Threading.Timer
Самый простой. Он планирует обратный вызов в ThreadPool. Если обработчику требуется больше времени для выполнения, чем интервал, обработчик будет выполнен снова, и вы получите несколько обработчиков, работающих параллельно.
Использует System.Threading.Timer внутри и предоставляет несколько дополнительных функций, таких как AutoReset, Enabled или SynchronizingObject, которые позволяют настроить способ выполнения обратного вызова. Также событие Tick позволяет зарегистрировать несколько обработчиков. Вы также можете изменить обработчик после запуска таймера.
Используется в цикле и поддерживает асинхронные обработчики. У него нет события Tick, но он предоставляет метод WaitForNextTickAsync, возвращающий ValueTask<bool>, которая завершается к концу интервала. Булев параметр указывает, был ли таймер удалён. Таким образом, можно использовать его в цикле while и обратные вызовы не могут перекрываться.
Таймеры в .NET
В .NET есть как минимум 6 разных таймеров! Каждый для своей цели и варианта использования. Рассмотрим различия между ними.
Во-первых, есть специальные таймеры пользовательского интерфейса. Эти таймеры используются для выполнения кода в UI-потоке:
- System.Windows.Forms.Timer
- System.Windows.Threading.DispatcherTimer
Эти таймеры выполняют обратный вызов в UI-потоке. Таким образом, вы можете взаимодействовать с пользовательским интерфейсом в обоих случаях. Кроме того, поскольку событие вызывается в UI-потоке, одновременно выполняется только один обратный вызов. Таким образом, вам не нужно беспокоиться о потокобезопасности.
Для WebForms есть ещё один таймер: System.Web.UI.Timer. Он генерирует событие обратной передачи на сервере. Но не будем об устаревших технологиях)))
Наконец, 3 таймера, которые не зависят от пользовательского интерфейса.
1. System.Threading.Timer
Самый простой. Он планирует обратный вызов в ThreadPool. Если обработчику требуется больше времени для выполнения, чем интервал, обработчик будет выполнен снова, и вы получите несколько обработчиков, работающих параллельно.
var timer = new System.Threading.Timer(2. System.Timers.Timer
// обратный вызов может быть выполнен параллельно
// если предыдущий ещё не завершился
// до старта следующего
callback: state => Console.WriteLine("tick"),
// Используется для передачи данных
// в метод обратного вызова,
// чтобы не использовать замыкание
state: null,
// Начать немедленно
dueTime: TimeSpan.Zero,
// Повторять каждую секунду
period: TimeSpan.FromSeconds(1));
// Поставить таймер на паузу
timer.Change(
dueTime: Timeout.Infinite,
period: Timeout.Infinite);
Использует System.Threading.Timer внутри и предоставляет несколько дополнительных функций, таких как AutoReset, Enabled или SynchronizingObject, которые позволяют настроить способ выполнения обратного вызова. Также событие Tick позволяет зарегистрировать несколько обработчиков. Вы также можете изменить обработчик после запуска таймера.
var timer = new System.Timers.Timer(3. System.Threading.PeriodicTimer
TimeSpan.FromSeconds(1));
// несколько обработчиков
timer.Elapsed += (sender, e)
=> Console.WriteLine("Handler 1");
timer.Elapsed += (sender, e)
=> Console.WriteLine("Handler 2");
// Останавливаем после 1го выполнения
timer.AutoReset = false;
// Запускаем
timer.Start();
Используется в цикле и поддерживает асинхронные обработчики. У него нет события Tick, но он предоставляет метод WaitForNextTickAsync, возвращающий ValueTask<bool>, которая завершается к концу интервала. Булев параметр указывает, был ли таймер удалён. Таким образом, можно использовать его в цикле while и обратные вызовы не могут перекрываться.
using var cts =Источник: https://www.meziantou.net/too-many-timers-in-dotnet.htm
new CancellationTokenSource();
using var timer =
new PeriodicTimer(TimeSpan.FromSeconds(1));
while (
await timer.WaitForNextTickAsync(cts.Token))
{
Console.WriteLine("Tick");
await AsyncOperation();
}
👍15
День 1514. #ЧтоНовенького
Новинки ASP.NET Core 8 Превью 2. Продолжение
Начало
3. Новый интерфейс IResettable в ObjectPool
Microsoft.Extensions.ObjectPool обеспечивает поддержку объединения экземпляров объектов в памяти. Приложения могут использовать пул объектов, если выделение или инициализация значений требуют больших затрат.
В превью 2 упрощено использование пула объектов путём добавления интерфейса IResettable. Повторно используемые типы часто необходимо возвращать в состояние по умолчанию между использованиями. Типы IResettable автоматически сбрасываются при возврате в пул объектов.
В превью 1 была добавлена поддержка использования именованных каналов в Kestrel. Именованные каналы — это популярная технология для построения межпроцессного взаимодействия (IPC) между приложениями Windows. Теперь вы можете создать сервер IPC, используя .NET, Kestrel и именованные каналы.
В превью 2 улучшена производительность соединения именованного канала. Теперь принимаются параллельные соединения и повторно используются экземпляры NamedPipeServerStream.
Источник: https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-2/
Новинки ASP.NET Core 8 Превью 2. Продолжение
Начало
3. Новый интерфейс IResettable в ObjectPool
Microsoft.Extensions.ObjectPool обеспечивает поддержку объединения экземпляров объектов в памяти. Приложения могут использовать пул объектов, если выделение или инициализация значений требуют больших затрат.
В превью 2 упрощено использование пула объектов путём добавления интерфейса IResettable. Повторно используемые типы часто необходимо возвращать в состояние по умолчанию между использованиями. Типы IResettable автоматически сбрасываются при возврате в пул объектов.
public class ReusableBuffer : IResettable4. Повышение производительности передачи по именованным каналам
{
public byte[] Data { get; }
= new byte[1024 * 1024]; // 1 MB
public bool TryReset()
{
Array.Clear(Data);
return true;
}
}
var bufferPool = ObjectPool.Create<ReusableBuffer>();
var buffer = bufferPool.Get();
try
{
await ProcessDataAsync(buffer.Data);
}
finally
{
// Данные автоматически сбрасываются
bufferPool.Return(buffer);
}
В превью 1 была добавлена поддержка использования именованных каналов в Kestrel. Именованные каналы — это популярная технология для построения межпроцессного взаимодействия (IPC) между приложениями Windows. Теперь вы можете создать сервер IPC, используя .NET, Kestrel и именованные каналы.
var builder =Подробнее про межпроцессное взаимодействие с помощью gRPC в документации.
WebApplication.CreateBuilder(args);
builder.WebHost
.ConfigureKestrel(opts =>
{
opts.ListenNamedPipe("MyPipeName");
});
В превью 2 улучшена производительность соединения именованного канала. Теперь принимаются параллельные соединения и повторно используются экземпляры NamedPipeServerStream.
Источник: https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-2/
👍8
День 1515. #Карьера
Менталитет, Ведущий к Успеху в Разработке ПО
Образ мышления, который вы привносите в свою работу, играет важную роль в определении траектории вашей карьеры. Дружелюбное, но участное отношение, активное стремление поддержать и воодушевить коллег может направить на путь личного роста и профессионального успеха. А негативное мышление - нанести ущерб.
С самого начала нас призывают мыслить творчески и разрабатывать более эффективные или действенные решения, чем традиционные подходы. Эта конкурентность может быть полезной, если направить её в правильное русло, но некоторые разработчики заходят слишком далеко, постоянно пытаясь превзойти своих коллег, отстаивая свою точку зрения или стремясь стать лучше других членов команды. Такое отношение может быть нездоровым и, в конечном счёте, пагубным как для отдельного человека, так и для успеха команды.
Часто разработчик убеждён, что конкретный язык программирования, инструмент или система намного превосходят все остальные. Хотя могут быть случаи, когда это верно, важно сохранять непредвзятость и рассматривать все варианты и их потенциальное влияние на конкретный проект. Как разработчики, мы имеем доступ к широкому спектру технологий, каждая из которых имеет свои преимущества и недостатки. Расширяйте свой набор навыков и знакомьтесь с различными инструментами и технологиями, так как это может повысить вашу ценность и сделать вас более адаптируемым к потребностям проекта.
Негативное мышление может проявляться и во взгляде свысока на коллег, которые обращаются за помощью. Отказ в адекватной помощи или предположение, что другие должны знать что-то известное вам, не только препятствует вашему личностному росту, но и ограничивает возможности для продвижения. Быть ценным ресурсом для своей команды — ключевой аспект работы разработчика, а менеджеры и технические руководители обращают внимание на то, как человек влияет на команду как своим кодом, так и своей способностью помогать другим.
Хорошее эмпирическое правило - всегда учиться и воспринимать всё как возможность. Если вы столкнётесь на работе с чем-то, чему нужно научиться, - отлично, - теперь вам платят за то, что вы учитесь и становились лучше. Если вы хотите заполнить свободное время работой над личным проектом, попробуйте что-то, отличающееся от того, что вы делали в прошлом. Даже если проект будет заброшен или используемые навыки не относятся к вашей профессиональной деятельности, это всё равно увеличит ваше понимание того, что вас интересует, и может помочь в будущем.
Вы можете извлечь пользу из любого совместного процесса, в том числе из общения с младшим коллегой, нуждающимся в помощи. Не просто дайте быстрый ответ, найдите время, чтобы понять его подход и мыслительный процесс, а также выяснить, почему что-то не работает и как это можно улучшить. Подобные обсуждения могут привести к взаимному обучению и, по крайней мере, обеспечить более глубокое понимание того, как другие думают и решают проблемы.
Ещё одним важным аспектом позитивного отношения в индустрии разработки ПО является мышление роста. Это вера в то, что способности и интеллект можно развить благодаря самоотверженности и упорному труду. Такое мышление поощряет постоянное обучение и совершенствование. В проблемах и неудачах люди с установкой на рост чаще видят возможности для развития.
Этот менталитет поможет вам оставаться мотивированным и заинтересованным в решении проблем. Кроме того, мышление роста имеет решающее значение в отрасли, которая постоянно развивается и меняется, поскольку оно побуждает вас адаптироваться и быть готовым изучать новые технологии и методы. Наличие мышления роста также делает вас ценным активом для вашей команды, поскольку вы с большей вероятностью будете открыты для обратной связи, возьмёте на себя новые обязанности и привнесёте в команду инновационные решения и идеи.
Источник: https://betterprogramming.pub/the-mindset-that-leads-to-success-in-software-development-a6b9ac955b75
Менталитет, Ведущий к Успеху в Разработке ПО
Образ мышления, который вы привносите в свою работу, играет важную роль в определении траектории вашей карьеры. Дружелюбное, но участное отношение, активное стремление поддержать и воодушевить коллег может направить на путь личного роста и профессионального успеха. А негативное мышление - нанести ущерб.
С самого начала нас призывают мыслить творчески и разрабатывать более эффективные или действенные решения, чем традиционные подходы. Эта конкурентность может быть полезной, если направить её в правильное русло, но некоторые разработчики заходят слишком далеко, постоянно пытаясь превзойти своих коллег, отстаивая свою точку зрения или стремясь стать лучше других членов команды. Такое отношение может быть нездоровым и, в конечном счёте, пагубным как для отдельного человека, так и для успеха команды.
Часто разработчик убеждён, что конкретный язык программирования, инструмент или система намного превосходят все остальные. Хотя могут быть случаи, когда это верно, важно сохранять непредвзятость и рассматривать все варианты и их потенциальное влияние на конкретный проект. Как разработчики, мы имеем доступ к широкому спектру технологий, каждая из которых имеет свои преимущества и недостатки. Расширяйте свой набор навыков и знакомьтесь с различными инструментами и технологиями, так как это может повысить вашу ценность и сделать вас более адаптируемым к потребностям проекта.
Негативное мышление может проявляться и во взгляде свысока на коллег, которые обращаются за помощью. Отказ в адекватной помощи или предположение, что другие должны знать что-то известное вам, не только препятствует вашему личностному росту, но и ограничивает возможности для продвижения. Быть ценным ресурсом для своей команды — ключевой аспект работы разработчика, а менеджеры и технические руководители обращают внимание на то, как человек влияет на команду как своим кодом, так и своей способностью помогать другим.
Хорошее эмпирическое правило - всегда учиться и воспринимать всё как возможность. Если вы столкнётесь на работе с чем-то, чему нужно научиться, - отлично, - теперь вам платят за то, что вы учитесь и становились лучше. Если вы хотите заполнить свободное время работой над личным проектом, попробуйте что-то, отличающееся от того, что вы делали в прошлом. Даже если проект будет заброшен или используемые навыки не относятся к вашей профессиональной деятельности, это всё равно увеличит ваше понимание того, что вас интересует, и может помочь в будущем.
Вы можете извлечь пользу из любого совместного процесса, в том числе из общения с младшим коллегой, нуждающимся в помощи. Не просто дайте быстрый ответ, найдите время, чтобы понять его подход и мыслительный процесс, а также выяснить, почему что-то не работает и как это можно улучшить. Подобные обсуждения могут привести к взаимному обучению и, по крайней мере, обеспечить более глубокое понимание того, как другие думают и решают проблемы.
Ещё одним важным аспектом позитивного отношения в индустрии разработки ПО является мышление роста. Это вера в то, что способности и интеллект можно развить благодаря самоотверженности и упорному труду. Такое мышление поощряет постоянное обучение и совершенствование. В проблемах и неудачах люди с установкой на рост чаще видят возможности для развития.
Этот менталитет поможет вам оставаться мотивированным и заинтересованным в решении проблем. Кроме того, мышление роста имеет решающее значение в отрасли, которая постоянно развивается и меняется, поскольку оно побуждает вас адаптироваться и быть готовым изучать новые технологии и методы. Наличие мышления роста также делает вас ценным активом для вашей команды, поскольку вы с большей вероятностью будете открыты для обратной связи, возьмёте на себя новые обязанности и привнесёте в команду инновационные решения и идеи.
Источник: https://betterprogramming.pub/the-mindset-that-leads-to-success-in-software-development-a6b9ac955b75
👍13
День 1516. #Оффтоп
Прекрасного воскресенья вам. Сегодня поговорим об искуственном интеллекте. А поскольку я сам о нём почти ничего не знаю, порекомендую вам два видео с канала Coputerphile. Это беседы с ютубером, специалистом по искусственному интеллекту, Робертом Майлсом. Кстати, вот его канал https://www.youtube.com/@RobertMilesAI
Итак, первое видео (примерно месячной давности) – про ChatGPT https://youtu.be/viJt_DXTfwA.
Роберт объясняет, что такое ChatGPT, чем он отличается от GPT, как работает, почему иногда выдаёт неверные факты, какие интересные и забавные ситуации возникали при его использовании, а также почему они возникали. Почему нельзя «перетренировывать» модели, и что может получиться, если «перетренировать», например, GitHub Copilot?
Второе видео, вышедшее всего несколько дней назад – про Bing Chat https://youtu.be/jHwHPyWkShk (поиск Bing с прикрученным к нему ChatGPT), который в теории должен был усовершенствовать поиск в интернете, добавив к нему ИИ. Но вдруг пользователи стали сталкиваться со странными, откровенно ложными и даже грубыми ответами. Что пошло не так у Майкрософт, почему они выпустили недоделанный продукт, и можно ли его исправить?
Прекрасного воскресенья вам. Сегодня поговорим об искуственном интеллекте. А поскольку я сам о нём почти ничего не знаю, порекомендую вам два видео с канала Coputerphile. Это беседы с ютубером, специалистом по искусственному интеллекту, Робертом Майлсом. Кстати, вот его канал https://www.youtube.com/@RobertMilesAI
Итак, первое видео (примерно месячной давности) – про ChatGPT https://youtu.be/viJt_DXTfwA.
Роберт объясняет, что такое ChatGPT, чем он отличается от GPT, как работает, почему иногда выдаёт неверные факты, какие интересные и забавные ситуации возникали при его использовании, а также почему они возникали. Почему нельзя «перетренировывать» модели, и что может получиться, если «перетренировать», например, GitHub Copilot?
Второе видео, вышедшее всего несколько дней назад – про Bing Chat https://youtu.be/jHwHPyWkShk (поиск Bing с прикрученным к нему ChatGPT), который в теории должен был усовершенствовать поиск в интернете, добавив к нему ИИ. Но вдруг пользователи стали сталкиваться со странными, откровенно ложными и даже грубыми ответами. Что пошло не так у Майкрософт, почему они выпустили недоделанный продукт, и можно ли его исправить?
YouTube
ChatGPT with Rob Miles - Computerphile
A massive topic deserves a massive video. Rob Miles discusses ChatGPT and how it may not be dangerous, yet.
More from Rob Miles: https://bit.ly/Rob_Miles_YouTube
The 'Danish' Reddit post: https://www.reddit.com/r/GPT3/comments/zb4msc/speaking_to_chat…
More from Rob Miles: https://bit.ly/Rob_Miles_YouTube
The 'Danish' Reddit post: https://www.reddit.com/r/GPT3/comments/zb4msc/speaking_to_chat…
👍4
День 1517. #ЗаметкиНаПолях
Параллельная Публикация Уведомлений в MediatR
MediatR — популярная библиотека с простой реализацией паттерна посредник в .NET. С ростом популярности паттерна CQRS MediatR стала популярной библиотекой для реализации команд и запросов.
Однако она также поддерживает паттерн издатель-подписчик с использованием уведомлений. Вы можете опубликовать экземпляр INotification, и несколько подписчиков обработают опубликованное сообщение. До недавнего времени обработчики могли выполняться только последовательно. Сегодня рассмотрим, как это можно делать параллельно.
Нам нужен класс, реализующий INotification:
Реализация по умолчанию - ForeachAwaitPublisher:
Но вы также можете использовать TaskWhenAllPublisher (показаны только отличия в реализации метода Publish:
Настройка стратегии публикации происходит в методе AddMediatR.
Если вы хотите использовать стратегию TaskWhenAllPublisher, вы можете:
- указать значение для свойства NotificationPublisher (тогда издатель будет синглтоном),
- указать тип стратегии в свойстве NotificationPublisherType и использовать свойство ServiceLifetime для задания времени жизни:
Чем это полезно?
Возможность параллельного запуска обработчиков уведомлений обеспечивает значительное повышение производительности по сравнению с поведением по умолчанию.
Однако обратите внимание, что все обработчики будут использовать одну и ту же область видимости. Если у вас есть экземпляры сервисов, которые не поддерживают конкурентный доступ, вы можете столкнуться с проблемами. К сожалению, одним из таких является EF Core DbContext.
Источник: https://www.milanjovanovic.tech/blog/how-to-publish-mediatr-notifications-in-parallel
Параллельная Публикация Уведомлений в MediatR
MediatR — популярная библиотека с простой реализацией паттерна посредник в .NET. С ростом популярности паттерна CQRS MediatR стала популярной библиотекой для реализации команд и запросов.
Однако она также поддерживает паттерн издатель-подписчик с использованием уведомлений. Вы можете опубликовать экземпляр INotification, и несколько подписчиков обработают опубликованное сообщение. До недавнего времени обработчики могли выполняться только последовательно. Сегодня рассмотрим, как это можно делать параллельно.
Нам нужен класс, реализующий INotification:
public record OrderCreated(Guid OrderId) : INotification;А также реализация соответствующего INotificationHandler:
public class OrderCreatedHandler :Теперь можно публиковать сообщение с помощью IMediator или IPublisher:
INotificationHandler<OrderCreated>
{
private readonly INotificationService svc;
public OrderCreatedHandler(
INotificationService service)
{
svc = service;
}
public async Task Handle(
OrderCreated notification,
CancellationToken ct)
{
await svc.SendOrderCreatedEmail(
notification.OrderId,
ct);
}
}
await publisher.Publish(MediatR вызовет все соответствующие обработчики. До 12й версии MediatR стратегия публикации вызывала каждый обработчик по отдельности. Однако появился новый интерфейс INotificationPublisher, управляющий тем, как вызываются обработчики.
new OrderCreated(order.Id),
cancellationToken);
Реализация по умолчанию - ForeachAwaitPublisher:
public class ForeachAwaitPublisherОна вызывает обработчики по одному и завершается неудачей при ошибке в одном из обработчиков.
: INotificationPublisher
{
public async Task Publish(
IEnumerable<NotificationHandlerExecutor> executors,
INotification notification,
CancellationToken ct)
{
foreach (var e in executors)
{
await e
.HandlerCallback(notification, ct)
.ConfigureAwait(false);
}
}
}
Но вы также можете использовать TaskWhenAllPublisher (показаны только отличия в реализации метода Publish:
var tasks = executorsTaskWhenAllPublisher вызывает все обработчики одновременно и выполняет их все независимо от того, возникали ли в них ошибки. Если вы сохраните задачу, возвращенную TaskWhenAllPublisher, вы можете получить доступ к свойству Task.Exception, содержащему экземпляр AggregateException, и реализовать обработку исключений.
.Select(e =>
e.HandlerCallback(notification, ct))
.ToArray();
return Task.WhenAll(tasks);
Настройка стратегии публикации происходит в методе AddMediatR.
Если вы хотите использовать стратегию TaskWhenAllPublisher, вы можете:
- указать значение для свойства NotificationPublisher (тогда издатель будет синглтоном),
- указать тип стратегии в свойстве NotificationPublisherType и использовать свойство ServiceLifetime для задания времени жизни:
services.AddMediatR(cfg => {Вы также можете реализовать пользовательский экземпляр INotificationPublisher и вместо этого использовать собственную реализацию.
…
cfg.NotificationPublisher =
new TaskWhenAllPublisher();
// или
cfg.NotificationPublisherType =
typeof(TaskWhenAllPublisher);
cfg.ServiceLifetime = ServiceLifetime.Transient;
});
Чем это полезно?
Возможность параллельного запуска обработчиков уведомлений обеспечивает значительное повышение производительности по сравнению с поведением по умолчанию.
Однако обратите внимание, что все обработчики будут использовать одну и ту же область видимости. Если у вас есть экземпляры сервисов, которые не поддерживают конкурентный доступ, вы можете столкнуться с проблемами. К сожалению, одним из таких является EF Core DbContext.
Источник: https://www.milanjovanovic.tech/blog/how-to-publish-mediatr-notifications-in-parallel
👍9
This media is not supported in your browser
VIEW IN TELEGRAM
День 1518. #ЧтоНовенького
Группы Точек Останова
В Visual Studio 17.6 превью 2 появилась новая функция Группы точек останова, позволяющая создавать именованные группы с любым количеством точек останова в них. Так можно настраивать конфигурацию точек и включать/выключать их по отдельности или вместе.
Можно создавать группы, соответствующие различным аспектам приложения, таким как отдельные функции, модули или даже проекты. Такая организация упрощает переключение между различными сценариями отладки, тестирование различных теорий и отслеживание результатов каждого сеанса отладки.
Чтобы создать новую группу точек останова, выберите в окне Breakpoints в меню New > Breakpoint Group… (Новая > Группа точек останова…). Затем нужные точки можно либо перетащить в группу или нажать на них правой кнопкой и выбрать “Add to Breakpoint Group” (Добавить в Группу Точек Останова). После этого можно включать/отключать отдельные точки или всю группу (см. видео).
Источник
Группы Точек Останова
В Visual Studio 17.6 превью 2 появилась новая функция Группы точек останова, позволяющая создавать именованные группы с любым количеством точек останова в них. Так можно настраивать конфигурацию точек и включать/выключать их по отдельности или вместе.
Можно создавать группы, соответствующие различным аспектам приложения, таким как отдельные функции, модули или даже проекты. Такая организация упрощает переключение между различными сценариями отладки, тестирование различных теорий и отслеживание результатов каждого сеанса отладки.
Чтобы создать новую группу точек останова, выберите в окне Breakpoints в меню New > Breakpoint Group… (Новая > Группа точек останова…). Затем нужные точки можно либо перетащить в группу или нажать на них правой кнопкой и выбрать “Add to Breakpoint Group” (Добавить в Группу Точек Останова). После этого можно включать/отключать отдельные точки или всю группу (см. видео).
Источник
👍23
День 1519. #Testing
Как Тестировать Контракты HTTP API в .NET
Тесты HTTP API можно называть модульными, интеграционными или компонентными – это не так важно. Важно, чтобы тесты взаимодействовали только с интерфейсом, использующимся в производственном коде.
Если определённая часть вашей системы вызывается только через HTTP API, тест должен делать то же самое. Прямой вызов метода класса контроллера ASP.NET нарушает эту идею.
Убедитесь, что вы проверяете только то, что имеет отношение к конкретному тестовому случаю:
- Если вы ожидаете исключения, убедитесь, что тип исключения правильный, свойства имеют правильные значения, а сообщение соответствует ожиданиям.
- В сообщении об исключении, часто надо проверить только отдельные части. Утверждение WithMessage в Fluent Assertions принимает шаблон сообщения именно по этой причине.
- Если API возвращает конкретный код ошибки HTTP, проверяйте только его и игнорируйте тело.
- Если тест охватывает определённый URL, где имеет значение только определённое свойство результата, игнорируйте остальные.
Это позволяет избегать провалов тестов по несвязанным причинам.
Многие разработчики используют в тестах реальный тип (например, тип DTO) из кода проекта, чтобы сравнивать с ним десериализованный результат, полученный из HTTP API. Обычный аргумент в пользу этого: это удобнее для рефакторинга, так что, например, изменение имени свойства этого типа не нарушит теста.
Но на самом деле это должно ломать тест! Маршрут, заголовки и конкретный JSON, возвращаемый HTTP API, являются контрактом и, следовательно, должны рассматриваться как таковые.
Как быть? Есть два распространённых способа:
- Использовать необработанный JSON. Самый чистый способ, но это сложно, если надо проверить только отдельные части результата.
- Десериализовать результат в анонимный тип определённой структуры. Рассмотрим пример, используя NewtonSoft.Json и Fluent Assertions:
В System.Text.Json мы можем добиться того же результата:
Источник: https://www.continuousimprover.com/2023/03/test-http-contracts.html
Как Тестировать Контракты HTTP API в .NET
Тесты HTTP API можно называть модульными, интеграционными или компонентными – это не так важно. Важно, чтобы тесты взаимодействовали только с интерфейсом, использующимся в производственном коде.
Если определённая часть вашей системы вызывается только через HTTP API, тест должен делать то же самое. Прямой вызов метода класса контроллера ASP.NET нарушает эту идею.
Убедитесь, что вы проверяете только то, что имеет отношение к конкретному тестовому случаю:
- Если вы ожидаете исключения, убедитесь, что тип исключения правильный, свойства имеют правильные значения, а сообщение соответствует ожиданиям.
- В сообщении об исключении, часто надо проверить только отдельные части. Утверждение WithMessage в Fluent Assertions принимает шаблон сообщения именно по этой причине.
- Если API возвращает конкретный код ошибки HTTP, проверяйте только его и игнорируйте тело.
- Если тест охватывает определённый URL, где имеет значение только определённое свойство результата, игнорируйте остальные.
Это позволяет избегать провалов тестов по несвязанным причинам.
Многие разработчики используют в тестах реальный тип (например, тип DTO) из кода проекта, чтобы сравнивать с ним десериализованный результат, полученный из HTTP API. Обычный аргумент в пользу этого: это удобнее для рефакторинга, так что, например, изменение имени свойства этого типа не нарушит теста.
Но на самом деле это должно ломать тест! Маршрут, заголовки и конкретный JSON, возвращаемый HTTP API, являются контрактом и, следовательно, должны рассматриваться как таковые.
Как быть? Есть два распространённых способа:
- Использовать необработанный JSON. Самый чистый способ, но это сложно, если надо проверить только отдельные части результата.
- Десериализовать результат в анонимный тип определённой структуры. Рассмотрим пример, используя NewtonSoft.Json и Fluent Assertions:
IHost host = GetTestClient();Здесь мы устанавливаем ожидание (expect) с определёнными значениями, а затем используем DeserializeAnonymousType, чтобы NewtowSoft.Json попытался десериализовать JSON в анонимный объект, структура которого определяется объектом expect. BeEquivalentTo использует глубокое сравнение ожидаемого и фактического (actual) объектов.
var response = await host.GetAsync(…);
var body = await
response.Content.ReadAsStringAsync();
var expect = new[] {
new {
State = "Active",
Count = 1
}
}
var actual = JsonConvert
.DeserializeAnonymousType(body, expect);
actual.Should().BeEquivalentTo(expect);
В System.Text.Json мы можем добиться того же результата:
…Вы можете инкапсулировать большую часть логики проверки в метод BeEquivalentTo, который принимает множество настроек. Также можно использовать метод Should().BeAs(), предоставляемый библиотекой FluentAssertions.Web.
var actual = JsonSerializer.Deserialize(
body,
expect.GetType(),
new JsonSerializerOptions {
PropertyNameCaseInsensitive = true
});
actual.Should().BeEquivalentTo(expect);
Источник: https://www.continuousimprover.com/2023/03/test-http-contracts.html
👍13
День 1520. #ЗаметкиНаПолях
Кэширование Вывода в ASP.NET Core. Начало
Кэширование выходных данных — это первоклассная функция в ASP.NET Core 7. Раньше его приходилось реализовывать самостоятельно. Рассмотрим особенности кэширования и как его реализовать.
Кэширование вывода — можно применить в ASP.NET Core для кэширования часто используемых данных, в основном для повышения производительности. Предотвратив чрезмерные вызовы ресурсоемких зависимостей (например, БД или сетевых API), мы можем значительно улучшить время отклика, что является одним из ключей к масштабированию приложений.
Прежде всего, кэширование вывода (Output Caching) сильно отличается от кэширования ответов (Response Caching):
- Ответственность: кэширование ответов возлагает ответственность за кэширование на клиентов (или промежуточные прокси-серверы) путем установки заголовков кэша. Кэширование вывода возлагает ответственность на сервер.
- Носитель данных: кэширование ответов хранится в памяти, тогда как кэширование вывода имеет множество параметров для настройки хранилища.
- Удаление: поскольку мы контролируем кэширование вывода на сервере, у нас есть возможность удалить эти записи кэша. При кэшировании ответов это сложно, поскольку мы не контролируем поведение клиентов.
- Повторная проверка: кэширование вывода может возвращать код ответа 304 Not Modified вместо кэшированного тела ответа. Это поможет сэкономить трафик.
При этом одно не лучше другого и каждый вид имеет разные варианты использования.
Настройка кэширования вывода
Давайте начнем с настройки самого простого примера кэширования вывода:
Рассмотрим простейший метод контроллера, возвращающий случайное число:
По умолчанию ответ кэшируется на 1 минуту.
Политики
Политики кэширования вывода позволяют настроить кэширование и в самом атрибуте, но лучше сделать это централизованно в Program.cs:
- Базовая политика по умолчанию кэширует на 5 секунд.
- Политика CacheTenSec кэширует на 10 секунд.
Если удалить атрибут
Стоит отметить, что конфигурация кэша по умолчанию принимает некоторые смелые решения:
- кэшируются только ответы HTTP 200,
- кэшируются только ответы GET/HEAD,
- не кэшируются ответы с аутентификацией или cookie.
Это можно переопределить с помощью создания пользовательской политики кэширования вывода.
Продолжение следует…
Источник: https://code-maze.com/aspnet-core-output-caching/
Кэширование Вывода в ASP.NET Core. Начало
Кэширование выходных данных — это первоклассная функция в ASP.NET Core 7. Раньше его приходилось реализовывать самостоятельно. Рассмотрим особенности кэширования и как его реализовать.
Кэширование вывода — можно применить в ASP.NET Core для кэширования часто используемых данных, в основном для повышения производительности. Предотвратив чрезмерные вызовы ресурсоемких зависимостей (например, БД или сетевых API), мы можем значительно улучшить время отклика, что является одним из ключей к масштабированию приложений.
Прежде всего, кэширование вывода (Output Caching) сильно отличается от кэширования ответов (Response Caching):
- Ответственность: кэширование ответов возлагает ответственность за кэширование на клиентов (или промежуточные прокси-серверы) путем установки заголовков кэша. Кэширование вывода возлагает ответственность на сервер.
- Носитель данных: кэширование ответов хранится в памяти, тогда как кэширование вывода имеет множество параметров для настройки хранилища.
- Удаление: поскольку мы контролируем кэширование вывода на сервере, у нас есть возможность удалить эти записи кэша. При кэшировании ответов это сложно, поскольку мы не контролируем поведение клиентов.
- Повторная проверка: кэширование вывода может возвращать код ответа 304 Not Modified вместо кэшированного тела ответа. Это поможет сэкономить трафик.
При этом одно не лучше другого и каждый вид имеет разные варианты использования.
Настройка кэширования вывода
Давайте начнем с настройки самого простого примера кэширования вывода:
var builder = WebApplication.CreateBuilder(args);Мы добавляем промежуточное ПО для кэширования вывода в коллекцию сервисов и конвейер запросов.
…
builder.Services.AddControllers();
builder.Services.AddOutputCache();
var app = builder.Build();
…
app.MapControllers();
app.UseOutputCache();
app.Run();
Рассмотрим простейший метод контроллера, возвращающий случайное число:
public IResult Get()При каждом обновлении число меняется. Но если добавить к методу атрибут [OutputCache], то при обновлении результат будет оставаться прежним.
=> Results.Ok(Random.Shared.Next(100));
По умолчанию ответ кэшируется на 1 минуту.
Политики
Политики кэширования вывода позволяют настроить кэширование и в самом атрибуте, но лучше сделать это централизованно в Program.cs:
builder.Services.AddOutputCache(opt =>Здесь мы добавляем две политики:
{
opt.AddBasePolicy(c =>
c.Expire(TimeSpan.FromSeconds(5)));
opt.AddPolicy("CacheTenSec", c =>
c.Expire(TimeSpan.FromSeconds(10)));
});
- Базовая политика по умолчанию кэширует на 5 секунд.
- Политика CacheTenSec кэширует на 10 секунд.
Если удалить атрибут
[OutputCache]
из нашего метода действия, мы увидим, что ответ кэшируется на 5 секунд. Если добавить атрибут с указанием политики:[OutputCache(PolicyName="CacheTenSec")]то ответ будет кэшироваться на 10 секунд. Политики кэширования позволяют централизовано определять стратегии кэширования, избавляя методы действий от лишних атрибутов.
Стоит отметить, что конфигурация кэша по умолчанию принимает некоторые смелые решения:
- кэшируются только ответы HTTP 200,
- кэшируются только ответы GET/HEAD,
- не кэшируются ответы с аутентификацией или cookie.
Это можно переопределить с помощью создания пользовательской политики кэширования вывода.
Продолжение следует…
Источник: https://code-maze.com/aspnet-core-output-caching/
👍17
День 1521. #ЗаметкиНаПолях
Кэширование Вывода в ASP.NET Core. Продолжение
Начало
Ключи кэша
По умолчанию ключом кэша для конечной точки является полный URL. Если добавить параметры строки запроса, ключ также будет включать их значения и отдельно кэшировать элементы. Если мы хотим иметь больший контроль над тем, как создаётся ключ кэша, можно явно это указать.
1. Вариация по запросу
Изменим политику CacheTenSec:
2. Вариация по заголовку
3. Существует также VaryByValue, более сложная конфигурация, которая позволяет изменять ключ по вычисляемому на сервере значению.
Повторная проверка кэша
До сих пор мы возвращали полное тело кэшированного ответа, даже если оно было идентично предыдущему ответу. Но можно просто проинструктировать клиента, что ответ тот же.
Добавим ещё одну конечную точку, используя минимальные API в Program.cs:
- заголовок ETag,
- код ответа 200 (ОК),
- тело ответа «hello».
Добавим в запрос специальный заголовок If-None-Match со значением только что полученного ETag (увеличьте время кэширования в настройках, чтобы успеть это сделать). Теперь мы получим ответ 304 и пустое тело ответа. Это связано с тем, что с помощью заголовка If-None-Match клиент проинструктировал сервер, что у него уже есть копия ответа с таким значением ETag, поэтому, если оно не изменилось, не нужно повторно отправлять содержимое, просто нужно сообщить об этом факте с кодом ответа 304. Это очень мощный способ уменьшить объём обработки как на клиенте, так и на сервере.
Окончание следует…
Источник: https://code-maze.com/aspnet-core-output-caching/
Кэширование Вывода в ASP.NET Core. Продолжение
Начало
Ключи кэша
По умолчанию ключом кэша для конечной точки является полный URL. Если добавить параметры строки запроса, ключ также будет включать их значения и отдельно кэшировать элементы. Если мы хотим иметь больший контроль над тем, как создаётся ключ кэша, можно явно это указать.
1. Вариация по запросу
Изменим политику CacheTenSec:
opt.AddPolicy("CacheTenSec", c =>Здесь мы указываем, что хотим менять ключ кэша для параметра строки запроса par1. Теперь для каждого уникального значения par1 будет возвращаться свой ответ, кэшированный на 10 секунд. Однако другие параметры запроса на это влиять не будут. Например:
c.Expire(TimeSpan.FromSeconds(10))
.SetVaryByQuery("par1"));
www.site.com
и www.site.com?par2=123
вернут один и тот же кэшированный ответ. Это может быть полезно, если мы хотим чётко указать, как результаты варьируются в зависимости от параметров.2. Вариация по заголовку
opt.AddPolicy("CacheTenSec", c =>Здесь мы указываем, что хотим, чтобы кэш варьировался в зависимости от значения заголовка X-Client-Id (браузер клиента). Наиболее распространённый вариант использования вариации по заголовку — во время согласования содержимого - вариация по заголовку Accept. Например, можно по-разному кэшировать ответы JSON и XML из-за разных затрат по обработке этих запросов на сервере.
c.Expire(TimeSpan.FromSeconds(10))
.SetVaryByQuery("par1")
.SetVaryByHeader("X-Client-Id"));
3. Существует также VaryByValue, более сложная конфигурация, которая позволяет изменять ключ по вычисляемому на сервере значению.
Повторная проверка кэша
До сих пор мы возвращали полное тело кэшированного ответа, даже если оно было идентично предыдущему ответу. Но можно просто проинструктировать клиента, что ответ тот же.
Добавим ещё одну конечную точку, используя минимальные API в Program.cs:
app.MapGet("/etag", async (context) =>Здесь мы устанавливаем специальный заголовок ответа, называемый ETag, со значением GUID. Если мы сделаем запрос к URL
{
var etag = $"\"{Guid.NewGuid():n}\"";
context.Response.Headers.ETag = etag;
await context.Response.WriteAsync("hello");
}).CacheOutput();
/etag
в Postman, мы увидим в ответе:- заголовок ETag,
- код ответа 200 (ОК),
- тело ответа «hello».
Добавим в запрос специальный заголовок If-None-Match со значением только что полученного ETag (увеличьте время кэширования в настройках, чтобы успеть это сделать). Теперь мы получим ответ 304 и пустое тело ответа. Это связано с тем, что с помощью заголовка If-None-Match клиент проинструктировал сервер, что у него уже есть копия ответа с таким значением ETag, поэтому, если оно не изменилось, не нужно повторно отправлять содержимое, просто нужно сообщить об этом факте с кодом ответа 304. Это очень мощный способ уменьшить объём обработки как на клиенте, так и на сервере.
Окончание следует…
Источник: https://code-maze.com/aspnet-core-output-caching/
👍9👎1
День 1522. #ЗаметкиНаПолях
Кэширование Вывода в ASP.NET Core. Окончание
Начало
Продолжение
Удаление кэша
Представим, что данные, которые мы кэшируем, изменились. Здесь у нас два варианта:
- убедиться, что клиенты понимают, что данные могут устареть после определённого периода;
- обновить кэш, удалив элементы.
Первое обычно подходит для коротких периодов кэширования (как в нашей текущей конфигурации), но не для более длительных периодов кэширования.
Добавим ещё одну конечную точку:
Добавим сервисную конечную точку:
Итак, давайте проверим результаты. Используя утилиту, вроде Postman, вызовем сервисную конечную точку:
Это очень мощный инструмент, позволяющий обновлять кэш, когда данные устаревают, а не только по истечении срока кэширования. В нашем случае мы предоставляем эту функциональность через конечную точку, но это не лучшая практика, поскольку мы не должны позволять клиентам очищать кэш. Лучшим решением будет вызов метода EvictByTagAsync во время операции сохранения в БД данных, которые потом будут извлекаться и кэшироваться.
Итого
В этой серии мы обсудили, как настроить OutputCache в ASP.NET. Для большинства сценариев мы использовали значения по умолчанию, включая использование кэша в памяти в качестве хранилища. Однако по мере роста размера приложения и добавления серверов этот вариант становится менее предпочтительным. Есть варианты лучше, включая такой инструмент, как Redis.
Источник: https://code-maze.com/aspnet-core-output-caching/
Кэширование Вывода в ASP.NET Core. Окончание
Начало
Продолжение
Удаление кэша
Представим, что данные, которые мы кэшируем, изменились. Здесь у нас два варианта:
- убедиться, что клиенты понимают, что данные могут устареть после определённого периода;
- обновить кэш, удалив элементы.
Первое обычно подходит для коротких периодов кэширования (как в нашей текущей конфигурации), но не для более длительных периодов кэширования.
Добавим ещё одну конечную точку:
[HttpGet("database")]И политику (для этой политики добавим тэг tag-expensive:
[OutputCache(PolicyName = "Expensive")]
public string GetDatabase()
=> $"Дорогой вызов БД в: {DateTime.Now:hh:mm:ss}";
opt.AddPolicy("Expensive", с =>Также добавим тег tag-all в политику по умолчанию:
с.Expire(TimeSpan.FromMinutes(1))
.Tag("tag-expensive"));
opt.AddBasePolicy(с =>Для конечной точки /database ответ находится в кэше 1 минуту. Если данные изменились, клиенты всё равно в течение минуты будут получать старые кэшированные значения. Однако мы можем удалить их, используя только что настроенные теги.
с.Expire(TimeSpan.FromSeconds(5))
.Tag("tag-all"));
Добавим сервисную конечную точку:
[HttpDelete("cache/{tag}")]Она принимает специальную зависимость IOutputCacheStore и вызывает метод EvictByTagAsync, передавая значение тега из URL. Это удаляет из кэша элементы, соответствующие этому тегу.
public async Task DeleteCache(
IOutputCacheStore cache, string tag)
=> await cache.EvictByTagAsync(tag, default);
Итак, давайте проверим результаты. Используя утилиту, вроде Postman, вызовем сервисную конечную точку:
DELETE …/cache/tag-expensiveТеперь кэш для конечных точек с политикой Expensive очищен.
DELETE …/cache/tag-allТеперь кэш для конечных точек с политикой по умолчанию очищен.
Это очень мощный инструмент, позволяющий обновлять кэш, когда данные устаревают, а не только по истечении срока кэширования. В нашем случае мы предоставляем эту функциональность через конечную точку, но это не лучшая практика, поскольку мы не должны позволять клиентам очищать кэш. Лучшим решением будет вызов метода EvictByTagAsync во время операции сохранения в БД данных, которые потом будут извлекаться и кэшироваться.
Итого
В этой серии мы обсудили, как настроить OutputCache в ASP.NET. Для большинства сценариев мы использовали значения по умолчанию, включая использование кэша в памяти в качестве хранилища. Однако по мере роста размера приложения и добавления серверов этот вариант становится менее предпочтительным. Есть варианты лучше, включая такой инструмент, как Redis.
Источник: https://code-maze.com/aspnet-core-output-caching/
👍12
День 1523. #ЗаметкиНаПолях
Обработка CancelKeyPress с Помощью CancellationToken
Иногда нужно определить, когда консольное приложение закрывается, чтобы выполнить некоторую очистку. Console.CancelKeyPress позволяет зарегистрировать метод обратного вызова, который выполнится при нажатии Ctrl+C или Ctrl+Break в консоли. Это событие также позволяет предотвратить закрытие приложения, чтобы вы могли выполнить очистку перед завершением работы. Можно использовать Console.CancelKeyPress в паре с CancellationToken для отмены текущих асинхронных операций.
Метод, выполнение которого прерывается:
Обработка CancelKeyPress с Помощью CancellationToken
Иногда нужно определить, когда консольное приложение закрывается, чтобы выполнить некоторую очистку. Console.CancelKeyPress позволяет зарегистрировать метод обратного вызова, который выполнится при нажатии Ctrl+C или Ctrl+Break в консоли. Это событие также позволяет предотвратить закрытие приложения, чтобы вы могли выполнить очистку перед завершением работы. Можно использовать Console.CancelKeyPress в паре с CancellationToken для отмены текущих асинхронных операций.
Метод, выполнение которого прерывается:
static async Task DoAsync (Основной текст программы:
string[] args, CancellationToken ct)
{
try
{
Console.WriteLine("Ожидание…");
await Task.Delay(10_000, ct);
}
catch (OperationCanceledException)
{
Console.WriteLine("Операция отменена");
}
}
using var cts = new CancellationTokenSource();Кроме того, у токена отмены есть метод Register, позволяющий зарегистрировать методы обратного вызова, которые выполнятся в случае отмены токена:
Console.CancelKeyPress += (sender, e) =>
{
// Мы остановим процесс вручную
// с помощью токена отмены
e.Cancel = true;
// … очистка …
// Вызываем отмену на токене
cts.Cancel();
};
await DoAsync(args, cts.Token);
cts.Token.Register(Источник: https://www.meziantou.net/handling-cancelkeypress-using-a-cancellationtoken.htm
() => Console.WriteLine("Отмена…"));
👍8
День 1524. #ЗаметкиНаПолях
Храните Информацию в Её Высшей Форме. Начало
Может быть несколько представлений некоторой части информации; вы не должны ограничивать себя только одним из них. Вместо этого храните источник этой информации.
Допустим, мы создаём онлайн-кинотеатр и нам нужно хранить продолжительность фильмов в базе. Во внешнем интерфейсе она представлена как «1ч 47мин». Но в каком виде её хранить? В виде строки «1ч 47мин», но что, если мы решим изменить формат на 1:47 или «107 минут»? Поэтому нужно хранить её в форме, которую можно легко преобразовать в любой формат, то есть в виде целого количества минут. Это высшая форма информации о продолжительности фильма.
Этот совет можно перефразировать как: «Храните исходник, а не исполнение.»
Звучит тривиально. Вот более сложный пример. Есть сущность Customer и объект-значение LoyaltyPoints:
1) Когда клиент размещает заказ, объект Order вычисляет баллы лояльности на основе суммы заказа и вызывает AddPoints для клиента.
2) Клиент может использовать баллы лояльности, когда их минимальное значение составляет 250.
Приведённый выше код идеально отвечает этим требованиям.
Допустим, теперь клиент может обновить существующий заказ. Когда товар удаляется из заказа, объект заказа должен рассчитать разницу в баллах лояльности и вычесть её из суммы клиента. Вот три возможных решения:
1) Повторно использовать RedeemPoints для вычитания. Но этот метод проверяет минимальное значение 250 и выдаст исключение для клиента без баллов лояльности.
2) Использовать AddPoints, передав отрицательное число. Но по бизнес-правилам LoyaltyPoints не может быть отрицательным.
3) Ввести отдельный метод, который не проверяет минимум в 250:
Все 3 решения — лишь попытка разобраться с последствиями неправильного дизайна. Нужно хранить исходные данные расчёта, а не его результат. Остаток баллов лояльности - производное от двух частей информации: сколько баллов клиент заработал и сколько он использовал.
Вместо сохранения остатка, нужно хранить два исходных значения и вычислять остаток на лету:
- снятие заработанных баллов,
- добавление использованных баллов.
Между тем, различие между двумя вариантами использования имеет решающее значение, поскольку один из них должен содержать валидацию (использование баллов), а другой — нет (корректировка заработанных баллов). Разделив поле на две части, мы устраняем эту двусмысленность.
Хотя примеры выше различаются, принцип один. Мы вычисляем строку продолжительности фильма на лету из её источника, и также вычисляем оставшиеся баллы из заработанных и использованных. В обоих сценариях мы не замыкаемся на определённом формате. Например, в дополнение к показу покупателю оставшихся баллов система может также отображать общее количество использованных баллов, чтобы показать, сколько он сэкономил.
Окончание следует…
Источник: https://enterprisecraftsmanship.com/posts/storing-information-in-its-highest-form/
Храните Информацию в Её Высшей Форме. Начало
Может быть несколько представлений некоторой части информации; вы не должны ограничивать себя только одним из них. Вместо этого храните источник этой информации.
Допустим, мы создаём онлайн-кинотеатр и нам нужно хранить продолжительность фильмов в базе. Во внешнем интерфейсе она представлена как «1ч 47мин». Но в каком виде её хранить? В виде строки «1ч 47мин», но что, если мы решим изменить формат на 1:47 или «107 минут»? Поэтому нужно хранить её в форме, которую можно легко преобразовать в любой формат, то есть в виде целого количества минут. Это высшая форма информации о продолжительности фильма.
Этот совет можно перефразировать как: «Храните исходник, а не исполнение.»
Звучит тривиально. Вот более сложный пример. Есть сущность Customer и объект-значение LoyaltyPoints:
public class Customer : EntityУ нас два варианта использования:
{
public LoyaltyPoints Points { get; private set; }
public void AddPoints(LoyaltyPoints pts)
=> Points += pts;
public void RedeemPoints(LoyaltyPoints pts)
{
if (Points < 250 || pts > Points)
throw new Exception();
Points -= pts;
}
}
1) Когда клиент размещает заказ, объект Order вычисляет баллы лояльности на основе суммы заказа и вызывает AddPoints для клиента.
2) Клиент может использовать баллы лояльности, когда их минимальное значение составляет 250.
Приведённый выше код идеально отвечает этим требованиям.
Допустим, теперь клиент может обновить существующий заказ. Когда товар удаляется из заказа, объект заказа должен рассчитать разницу в баллах лояльности и вычесть её из суммы клиента. Вот три возможных решения:
1) Повторно использовать RedeemPoints для вычитания. Но этот метод проверяет минимальное значение 250 и выдаст исключение для клиента без баллов лояльности.
2) Использовать AddPoints, передав отрицательное число. Но по бизнес-правилам LoyaltyPoints не может быть отрицательным.
3) Ввести отдельный метод, который не проверяет минимум в 250:
public void SubtractPoints(LoyaltyPoints pts)Но теперь у нас два публичных метода, которые выполняют вычитание, и неочевидно, какой когда использовать. Это является признаком того, что мы раскрываем детали реализации.
=> Points -= points;
Все 3 решения — лишь попытка разобраться с последствиями неправильного дизайна. Нужно хранить исходные данные расчёта, а не его результат. Остаток баллов лояльности - производное от двух частей информации: сколько баллов клиент заработал и сколько он использовал.
Вместо сохранения остатка, нужно хранить два исходных значения и вычислять остаток на лету:
public class Customer : EntityОдно поле не передавало должным образом значение метода SubtractPoints. Это может означать любой из двух вариантов использования:
{
public LoyaltyPoints Earned { get; private set; }
public LoyaltyPoints Redeemed { get; private set; }
public LoyaltyPoints Points
=> Earned - Redeemed;
public void IncreasePoints(LoyaltyPoints pts)
=> Earned += pts;
public void ReducePoints(LoyaltyPoints pts)
=> Earned -= pts;
…
}
- снятие заработанных баллов,
- добавление использованных баллов.
Между тем, различие между двумя вариантами использования имеет решающее значение, поскольку один из них должен содержать валидацию (использование баллов), а другой — нет (корректировка заработанных баллов). Разделив поле на две части, мы устраняем эту двусмысленность.
Хотя примеры выше различаются, принцип один. Мы вычисляем строку продолжительности фильма на лету из её источника, и также вычисляем оставшиеся баллы из заработанных и использованных. В обоих сценариях мы не замыкаемся на определённом формате. Например, в дополнение к показу покупателю оставшихся баллов система может также отображать общее количество использованных баллов, чтобы показать, сколько он сэкономил.
Окончание следует…
Источник: https://enterprisecraftsmanship.com/posts/storing-information-in-its-highest-form/
👍19
День 1525. #ЗаметкиНаПолях
Храните Информацию в Её Высшей Форме. Окончание
Начало
Мы можем пойти дальше. Заработанные баллы – это сумма заработанных баллов по всем заказам. А использованные – сумма по всем использованиям. Поэтому мы можем хранить баллы лояльности, полученные/использованные в каждом заказе, в классе Order и список заказов в поле класса Customer:
Однако важно соблюдать баланс. Насколько детальной должна быть исходная информация, зависит от потребностей вашего проекта. В примере выше можно было бы остановиться на двух полях (Earned и Redeemed), если только не появятся новые требования, которые это решение не может удовлетворить.
Баланс здесь заключается между гибкостью с одной стороны, и требованиями к хранилищу, сложностью и производительностью - с другой. Сохранять источник информации - более гибко, но вы заплатите за это дополнительными требованиями к хранилищу, повышенной сложностью и даже снижением производительности:
- Если мы решим предварительно агрегировать данные, мы потеряем в гибкости и потенциально даже теряем ценную информацию (например, с одним полем Points мы уже не можем сказать, сколько всего баллов клиент заработал).
- С другой стороны, мы можем излишне усложнить наше решение, постоянно считая суммы баллов и храня заказы. На практике потребность в дополнительной гибкости может никогда не возникнуть.
Каждый проект отличается и трудно сформулировать общее правило. Можно сказать, что следует хранить источники, пока влияние недостатков от их хранения минимально:
- Для продолжительности фильма нет никакой разницы между сохранением целого числа и строки, так что это не проблема.
- Для баллов лояльности два поля также мало чем отличаются от одного — они оба могут храниться в одной таблице базы данных.
- Сохранение баллов в классе Order может быть уместным, если количество заказов на клиента невелико, и можно сделать Заказ частью агрегата Клиента.
- А если количество заказов на одного клиента велико и нужно сделать Заказ агрегатом, то лучше остановился на решении с двумя полями Earned и Redeemed.
Итого
Храните информацию в ее высшей форме. Другой способ сформулировать это правило: хранить исходник, а не его представление. Однако соблюдайте баланс, следуя этому правилу. Дополнительная гибкость достигается за счёт требований к хранилищу, повышения сложности и снижения производительности.
Источник: https://enterprisecraftsmanship.com/posts/storing-information-in-its-highest-form/
Храните Информацию в Её Высшей Форме. Окончание
Начало
Мы можем пойти дальше. Заработанные баллы – это сумма заработанных баллов по всем заказам. А использованные – сумма по всем использованиям. Поэтому мы можем хранить баллы лояльности, полученные/использованные в каждом заказе, в классе Order и список заказов в поле класса Customer:
public class Customer : EntityБольше нет необходимости увеличивать/уменьшать заработанные баллы, так как они теперь контролируются классом Order.
{
public Order[] Orders { get; private set; }
public LoyaltyPoints Earned
=> Orders.Sum(x => x.PointsEarned);
public LoyaltyPoints Redeemed
=> Orders.Sum(x => x.PointsRedeemed);
public LoyaltyPoints Points
=> Earned - Redeemed;
…
}
Однако важно соблюдать баланс. Насколько детальной должна быть исходная информация, зависит от потребностей вашего проекта. В примере выше можно было бы остановиться на двух полях (Earned и Redeemed), если только не появятся новые требования, которые это решение не может удовлетворить.
Баланс здесь заключается между гибкостью с одной стороны, и требованиями к хранилищу, сложностью и производительностью - с другой. Сохранять источник информации - более гибко, но вы заплатите за это дополнительными требованиями к хранилищу, повышенной сложностью и даже снижением производительности:
- Если мы решим предварительно агрегировать данные, мы потеряем в гибкости и потенциально даже теряем ценную информацию (например, с одним полем Points мы уже не можем сказать, сколько всего баллов клиент заработал).
- С другой стороны, мы можем излишне усложнить наше решение, постоянно считая суммы баллов и храня заказы. На практике потребность в дополнительной гибкости может никогда не возникнуть.
Каждый проект отличается и трудно сформулировать общее правило. Можно сказать, что следует хранить источники, пока влияние недостатков от их хранения минимально:
- Для продолжительности фильма нет никакой разницы между сохранением целого числа и строки, так что это не проблема.
- Для баллов лояльности два поля также мало чем отличаются от одного — они оба могут храниться в одной таблице базы данных.
- Сохранение баллов в классе Order может быть уместным, если количество заказов на клиента невелико, и можно сделать Заказ частью агрегата Клиента.
- А если количество заказов на одного клиента велико и нужно сделать Заказ агрегатом, то лучше остановился на решении с двумя полями Earned и Redeemed.
Итого
Храните информацию в ее высшей форме. Другой способ сформулировать это правило: хранить исходник, а не его представление. Однако соблюдайте баланс, следуя этому правилу. Дополнительная гибкость достигается за счёт требований к хранилищу, повышения сложности и снижения производительности.
Источник: https://enterprisecraftsmanship.com/posts/storing-information-in-its-highest-form/
👍12
День 1526. #ЧтоНовенького
Иерархические Данные в Entity Framework Core 8 Превью 2
Пакет EntityFrameworkCore.SqlServer.HierarchyId — это неофициальный способ добавления использования иерархических данных в Entity Framework, который доступен уже несколько лет (версия 1.0.0 пакета доступна с апреля 2020 г.), однако в EF Core 8 preview 2 эта функция имеет официальную реализацию, основанную на этом пакете от сообщества. Новый официальный пакет — Microsoft.EntityFrameworkCore.SqlServer.HierarchyId.
HierarchyId включается путем установки вышеупомянутого пакета и следующего кода в startup приложения:
В самом типе сущности этот тип используется как любой другой тип свойства:
-
-
-
-
Пути
Хотя этот формат удобочитаем в коде, сам SQL Server использует компактный двоичный формат для хранения этого идентификатора (varbinary).
Тип также имеет некоторые ограничения непосредственно в SQL Server:
- Иерархический идентификатор SQL Server сам по себе не представляет древовидную структуру. Приложение несёт ответственность за присвоение значений hierarchyId так, чтобы отношения между строками в таблице представляли дерево.
- Нет гарантии, что иерархия будет уникальной, поэтому приложения также несут ответственность за обеспечение надлежащего контроля параллелизма.
- Нет ограничений внешнего ключа на значения hierarchyId. Поэтому, например, приложение должно следить за тем, чтобы все потомки узла в иерархии обновляли свои иерархические идентификаторы при удалении их родителя.
Предварительные версии EF8 в настоящее время можно использовать в .NET 6 LTS и .NET 7. Выпуск EF8 согласован со следующей LTS-версией .NET 8, которая запланирована на ноябрь 2023 года.
Источник: https://www.infoq.com/news/2023/04/ef-core-8-preview-2/
Иерархические Данные в Entity Framework Core 8 Превью 2
Пакет EntityFrameworkCore.SqlServer.HierarchyId — это неофициальный способ добавления использования иерархических данных в Entity Framework, который доступен уже несколько лет (версия 1.0.0 пакета доступна с апреля 2020 г.), однако в EF Core 8 preview 2 эта функция имеет официальную реализацию, основанную на этом пакете от сообщества. Новый официальный пакет — Microsoft.EntityFrameworkCore.SqlServer.HierarchyId.
HierarchyId включается путем установки вышеупомянутого пакета и следующего кода в startup приложения:
options.UseSqlServer(После установки и настройки функции HierarchyId её можно использовать для представления иерархических данных, таких как структура организации, структура папок и древовидные структуры страниц веб-сайта.
connectionString,
x => x.UseHierarchyId());
В самом типе сущности этот тип используется как любой другой тип свойства:
public HierarchyId NodePath { get; set; }Тип представляет путь сущности в древовидной структуре. Например, для узла с путем
/1/2/3
:-
/
- корень дерева,-
1
– прародитель узла,-
2
- родитель узла,-
3
— идентификатор узла.Пути
/1/2/4
и /1/2/5
являются родственными узлами исходного узла, а /1/3
и /1/4
являются родственными узлам родительского узла. Узлы также можно вставлять между двумя другими узлами, используя десятичные значения. Узел /1/3.5
находится между узлами /1/3
и /1/4
.Хотя этот формат удобочитаем в коде, сам SQL Server использует компактный двоичный формат для хранения этого идентификатора (varbinary).
Тип также имеет некоторые ограничения непосредственно в SQL Server:
- Иерархический идентификатор SQL Server сам по себе не представляет древовидную структуру. Приложение несёт ответственность за присвоение значений hierarchyId так, чтобы отношения между строками в таблице представляли дерево.
- Нет гарантии, что иерархия будет уникальной, поэтому приложения также несут ответственность за обеспечение надлежащего контроля параллелизма.
- Нет ограничений внешнего ключа на значения hierarchyId. Поэтому, например, приложение должно следить за тем, чтобы все потомки узла в иерархии обновляли свои иерархические идентификаторы при удалении их родителя.
Предварительные версии EF8 в настоящее время можно использовать в .NET 6 LTS и .NET 7. Выпуск EF8 согласован со следующей LTS-версией .NET 8, которая запланирована на ноябрь 2023 года.
Источник: https://www.infoq.com/news/2023/04/ef-core-8-preview-2/
👍8
День 1527. #TipsAndTricks #Git
Некоторые Малоизвестные Приемы в Git
Сегодня несколько полезных советов при работе с Git для любителей консоли.
1. Изменение последнего коммита
Когда вы делаете коммит в репозитории Git, вы создаёте новый снимок своего кода. Иногда вы можете обнаружить, что забыли включить файл, допустили опечатку в сообщении коммита или внесли другие небольшие изменения, которые хотели бы включить в последний коммит. Git позволяет изменить последний коммит с помощью флага --amend:
Git отслеживает все изменения, которые вы вносите в репозиторий, включая коммиты, слияния и другие операции. Reflog — это журнал всех изменений в репозитории Git, включая все коммиты, изменения веток и другие операции. Вы можете использовать журнал ссылок для восстановления потерянных коммитов, возврата к предыдущему состоянию или отмены перебазирования.
Чтобы просмотреть журнал ссылок, запустите:
Это мощный инструмент, который позволяет редактировать, изменять порядок или удалять коммиты перед их слиянием с основной веткой. Это особенно полезно, когда вы работаете над функциональной веткой и хотите очистить историю коммитов перед её слиянием с основной веткой:
Псевдонимы Git позволяют создавать собственные ярлыки для команд Git. Это может сэкономить ваше время и объём ввода, особенно для часто используемых команд:
Git stash позволяет временно сохранять изменения, которые вы ещё не готовы зафиксировать, без создания новой ветки:
Источник: https://dev.to/atordvairn/some-secret-git-tricks-that-come-in-handy-2k8i
Некоторые Малоизвестные Приемы в Git
Сегодня несколько полезных советов при работе с Git для любителей консоли.
1. Изменение последнего коммита
Когда вы делаете коммит в репозитории Git, вы создаёте новый снимок своего кода. Иногда вы можете обнаружить, что забыли включить файл, допустили опечатку в сообщении коммита или внесли другие небольшие изменения, которые хотели бы включить в последний коммит. Git позволяет изменить последний коммит с помощью флага --amend:
# изменить последний коммит новым сообщением2. Reflog
git commit --amend -m "New message"
# изменить, не меняя сообщения
git commit --amend --no-edit
Git отслеживает все изменения, которые вы вносите в репозиторий, включая коммиты, слияния и другие операции. Reflog — это журнал всех изменений в репозитории Git, включая все коммиты, изменения веток и другие операции. Вы можете использовать журнал ссылок для восстановления потерянных коммитов, возврата к предыдущему состоянию или отмены перебазирования.
Чтобы просмотреть журнал ссылок, запустите:
git reflog3. Интерактивное перебазирование
# возврат к предыдущему состоянию
git reset HEAD@{N}
Это мощный инструмент, который позволяет редактировать, изменять порядок или удалять коммиты перед их слиянием с основной веткой. Это особенно полезно, когда вы работаете над функциональной веткой и хотите очистить историю коммитов перед её слиянием с основной веткой:
git rebase -i HEAD~N4. Git-псевдонимы
# изменить сообщение коммита
pick 1234567 Old message
reword 2345678 New message
# изменить порядок коммитов
pick 1234567 First commit
pick 2345678 Second commit
pick 3456789 Third commit
# удалить коммит
pick 1234567 First commit
drop 2345678 Second commit
pick 3456789 Third commit
Псевдонимы Git позволяют создавать собственные ярлыки для команд Git. Это может сэкономить ваше время и объём ввода, особенно для часто используемых команд:
git config --global alias.ci commit5. Git Stash
# использование
git ci -m "Commit message"
Git stash позволяет временно сохранять изменения, которые вы ещё не готовы зафиксировать, без создания новой ветки:
git stashБольше консольных команд Git в этом посте.
# применить последние сохранённые изменения
git stash apply
# применить выбранные сохранённые изменения
git stash apply stash@{N}
# список всех изменений
git stash list
Источник: https://dev.to/atordvairn/some-secret-git-tricks-that-come-in-handy-2k8i
👍17
День 1528. #Карьера
Решение Проблем — Это не Навык
Самое главное, когда дело касается программирования, это конкретные знания, необходимые для решения задачи.
Меня всегда беспокоила мысль, что программирование — это просто решение проблем. Я думаю, потому что так обычно говорят в контексте карьерного совета в сфере разработке ПО, и это типичный ответ на вопрос «Как стать лучше в программировании?»
Совет кажется разумным и верным. Код, который вы пишете, чтобы что-то произошло, строго говоря, является решением проблемы. Код — это рецепт, по которому можно приготовить торт. С помощью кода можно буквально решить головоломку и буквально найти х в уравнении.
Но многие люди, кажется, повторяют, что программирование — это просто решение проблем, потому что легче сказать это, чем дать конкретный совет. Только когда я наткнулся на этот пост Скотта Янга «Cognitive Load Theory», я понял, почему идея о том, что решение проблем является основным мета-навыком программирования, казалась мне подозрительной.
Решение проблем — это не навык. Способ, которым мы учимся решать проблемы, заключается в наличии (а) знаний, которые помогают решить проблему, и (б) автоматических процедурных компонентов, которые помогают в решении проблем. Вероятно, не существует общих методов решения проблем, которые работали бы для каждой области. Могут существовать эвристики для решения проблем внутри предметной области. Тем не менее, их значение несравнимо со способностью иметь в памяти тонны выученных паттернов. Это объясняет, почему передача знаний ненадёжна и почему опыт имеет тенденцию быть конкретным для области применения.
Другими словами, чтобы решить задачу в программировании, вам нужно иметь определённые знания в области, позволяющие её решить. Конечно, Шерлок Холмс с его способностью к дедукции и логическому мышлению мог бы придумать что-то близкое к псевдокоду для решения FizzBuzz, но, если он не знает, как писать на языке программирования, он не сможет действительно решить задачу. Ему нужно знать разницу между чётными и нечётными числами (я уверен, что знает), а также специфический синтаксис языка программирования, например операторы if и что делает оператор деления по модулю %.
Проблема при приравнивании программирования к решению проблем, заключается в том, что это одна из тех идей, которые приятно слышать. Потому что все хороши в решении проблем, по крайней мере, некоторых типов проблем. Кто-то хорош в кроссвордах, кто-то в судоку, кто-то в шахматах, кто-то чинит машины, а кто-то готовит кофе. Но эти навыки не обязательно применимы к программированию.
Их навык заключается не в решении проблемы, а в том, чтобы хорошо играть в шахматы или чинить машины. Вы можете быть действительно хороши в судоку, но вы, вероятно, не поймете, как построить неориентированный граф, имея только общие способности и волю к решению задач.
Независимо от того, сколько методов решения сферических проблем в вакууме, по вашему мнению, у вас есть, само по себе решение проблем на самом деле не является навыком. Самое главное, когда дело касается программирования, — это конкретные знания, необходимые для решения конкретной задачи на программирование. Как замечает Скотт Янг, в действительности вы не можете натренировать навык решения проблем. Вы можете практиковаться и узнавать только определённые вещи в той области, которую изучаете.
Источник: https://betterprogramming.pub/problem-solving-isnt-a-skill-a32c22b71602
Автор оригинала: Jay Cruz
Решение Проблем — Это не Навык
Самое главное, когда дело касается программирования, это конкретные знания, необходимые для решения задачи.
Меня всегда беспокоила мысль, что программирование — это просто решение проблем. Я думаю, потому что так обычно говорят в контексте карьерного совета в сфере разработке ПО, и это типичный ответ на вопрос «Как стать лучше в программировании?»
Совет кажется разумным и верным. Код, который вы пишете, чтобы что-то произошло, строго говоря, является решением проблемы. Код — это рецепт, по которому можно приготовить торт. С помощью кода можно буквально решить головоломку и буквально найти х в уравнении.
Но многие люди, кажется, повторяют, что программирование — это просто решение проблем, потому что легче сказать это, чем дать конкретный совет. Только когда я наткнулся на этот пост Скотта Янга «Cognitive Load Theory», я понял, почему идея о том, что решение проблем является основным мета-навыком программирования, казалась мне подозрительной.
Решение проблем — это не навык. Способ, которым мы учимся решать проблемы, заключается в наличии (а) знаний, которые помогают решить проблему, и (б) автоматических процедурных компонентов, которые помогают в решении проблем. Вероятно, не существует общих методов решения проблем, которые работали бы для каждой области. Могут существовать эвристики для решения проблем внутри предметной области. Тем не менее, их значение несравнимо со способностью иметь в памяти тонны выученных паттернов. Это объясняет, почему передача знаний ненадёжна и почему опыт имеет тенденцию быть конкретным для области применения.
Другими словами, чтобы решить задачу в программировании, вам нужно иметь определённые знания в области, позволяющие её решить. Конечно, Шерлок Холмс с его способностью к дедукции и логическому мышлению мог бы придумать что-то близкое к псевдокоду для решения FizzBuzz, но, если он не знает, как писать на языке программирования, он не сможет действительно решить задачу. Ему нужно знать разницу между чётными и нечётными числами (я уверен, что знает), а также специфический синтаксис языка программирования, например операторы if и что делает оператор деления по модулю %.
Проблема при приравнивании программирования к решению проблем, заключается в том, что это одна из тех идей, которые приятно слышать. Потому что все хороши в решении проблем, по крайней мере, некоторых типов проблем. Кто-то хорош в кроссвордах, кто-то в судоку, кто-то в шахматах, кто-то чинит машины, а кто-то готовит кофе. Но эти навыки не обязательно применимы к программированию.
Их навык заключается не в решении проблемы, а в том, чтобы хорошо играть в шахматы или чинить машины. Вы можете быть действительно хороши в судоку, но вы, вероятно, не поймете, как построить неориентированный граф, имея только общие способности и волю к решению задач.
Независимо от того, сколько методов решения сферических проблем в вакууме, по вашему мнению, у вас есть, само по себе решение проблем на самом деле не является навыком. Самое главное, когда дело касается программирования, — это конкретные знания, необходимые для решения конкретной задачи на программирование. Как замечает Скотт Янг, в действительности вы не можете натренировать навык решения проблем. Вы можете практиковаться и узнавать только определённые вещи в той области, которую изучаете.
Источник: https://betterprogramming.pub/problem-solving-isnt-a-skill-a32c22b71602
Автор оригинала: Jay Cruz
👍11👎4
День 1529. #TipsAndTricks
9 Советов по Fluent Assertions, Которые Сэкономят Вам Время. Начало
Хороший программист всегда думает о будущем ПО, о том, как написать код, который будет легко читать и понимать.
Один из лучших способов улучшить читаемость юнит-тестов — использовать Fluent Assertions https://fluentassertions.com/ — набор методов расширения для утверждений, делающий их более читаемыми и понятными. Fluent API означает, что библиотека полагается на цепочку методов. Вы комбинируете несколько методов в одном операторе без необходимости сохранять промежуточные результаты в переменных. Вот несколько примеров.
1. Идентификация субъекта – Be()
Первый пример простой. Мы хотим проверить, равно ли целое число 5:
Expected number to be 5 because that is the correct amount, but found 6. (Ожидалось, что number будет 5, потому что это правильное количество, но найдено 6.)
2. Основные утверждения
Для всех ссылочных типов:
Для числовых типов:
BeEquivalentTo — позволяет сравнить, имеют ли два объекта одинаковые свойства с одинаковыми значениями. Два объекта не обязательно должны быть одного типа. Вот примечание к этому методу от авторов:
Объекты эквивалентны, когда оба графа объектов имеют свойства с одинаковыми именами и одинаковыми значениями, независимо от типа этих объектов. Два свойства также равны, если один тип может быть преобразован в другой, и результат равен. Тип свойства коллекции игнорируется до тех пор, пока коллекция реализует System.Collections.Generic.IEnumerable’1 и все элементы коллекции структурно одинаковы. Обратите внимание, что фактическое поведение определяется глобальными значениями по умолчанию, управляемыми FluentAssertions.AssertionOptions.
Довольно часто встречаются классы с одинаковыми свойствами. Это особенно актуально, когда методы API обычно принимают DTO в качестве параметра:
4. Цепочки утверждений - And
Чтобы связать несколько утверждений, вы можете использовать ограничение And. Пример:
Источник: https://methodpoet.com/fluent-assertions/
9 Советов по Fluent Assertions, Которые Сэкономят Вам Время. Начало
Хороший программист всегда думает о будущем ПО, о том, как написать код, который будет легко читать и понимать.
Один из лучших способов улучшить читаемость юнит-тестов — использовать Fluent Assertions https://fluentassertions.com/ — набор методов расширения для утверждений, делающий их более читаемыми и понятными. Fluent API означает, что библиотека полагается на цепочку методов. Вы комбинируете несколько методов в одном операторе без необходимости сохранять промежуточные результаты в переменных. Вот несколько примеров.
1. Идентификация субъекта – Be()
Первый пример простой. Мы хотим проверить, равно ли целое число 5:
int number = 5;Можно добавить сообщение. Отличительной особенностью сообщений Fluent Assertions, является то, что вы добавляете объяснение в середину сообщения:
number.Should().Be(5);
int number = 6;В итоге получается следующее сообщение:
number.Should().Be(5,
"because that is the correct amount");
Expected number to be 5 because that is the correct amount, but found 6. (Ожидалось, что number будет 5, потому что это правильное количество, но найдено 6.)
2. Основные утверждения
Для всех ссылочных типов:
sut.Should().BeNull();Для строк:
….NotBeNull();
….BeOfType<Customer>();
// для сравнения используется Equals
….Be(otherCustomer);
"string".Should().BeNullOrEmpty();Для логических типов доступны
….BeNullOrWhiteSpace();
….NotBeNullOrEmpty();
….NotBeNullOrWhiteSpace();
BeTrue
и BeFalse
.Для числовых типов:
number.Should().Be(1);3. BeEquivalentTo — сравнение графов объектов
….NotBe(10);
….BePositive();
….BeNegative();
….BeGreaterThanOrEqualTo(88);
….BeGreaterThan(66);
….BeLessThanOrEqualTo(56);
….BeLessThan(61);
….BeInRange(1, 5);
….NotBeInRange(6, 9);
BeEquivalentTo — позволяет сравнить, имеют ли два объекта одинаковые свойства с одинаковыми значениями. Два объекта не обязательно должны быть одного типа. Вот примечание к этому методу от авторов:
Объекты эквивалентны, когда оба графа объектов имеют свойства с одинаковыми именами и одинаковыми значениями, независимо от типа этих объектов. Два свойства также равны, если один тип может быть преобразован в другой, и результат равен. Тип свойства коллекции игнорируется до тех пор, пока коллекция реализует System.Collections.Generic.IEnumerable’1 и все элементы коллекции структурно одинаковы. Обратите внимание, что фактическое поведение определяется глобальными значениями по умолчанию, управляемыми FluentAssertions.AssertionOptions.
Довольно часто встречаются классы с одинаковыми свойствами. Это особенно актуально, когда методы API обычно принимают DTO в качестве параметра:
customer.Should()Разница между Be и BeEquivalentTo в том, что Be сравнивает два объекта на основе реализации System.Object.Equals(System.Object). BeEquivalentTo сравнивает свойства и требует, чтобы свойства имели одинаковые имена, независимо от фактического типа свойств.
.BeEquivalentTo(customerDto);
4. Цепочки утверждений - And
Чтобы связать несколько утверждений, вы можете использовать ограничение And. Пример:
collectionToTest.Should().Contain("first")Продолжение следует…
.And.HaveElementAt(2, "third");
Источник: https://methodpoet.com/fluent-assertions/
👍17
День 1530. #TipsAndTricks
9 Советов по Fluent Assertions, Которые Сэкономят Вам Время. Продолжение
Начало
5. Методы расширения коллекции
Вы можете использовать AssertionScope для объединения нескольких утверждений в одно исключение. Это делает код чище и позволяет проверить сразу все утверждения, вместо завершения теста после первой неудачи:
Expected numberOfDocuments to be 6, but found 5.
Expected string to be "Last Name" with a length of 9, but "First Name" has a length of 10, differs near "Fir" (index 0).
(Ожидалось, что numberOfDocuments будет равно 6, но найдено 5.
Ожидалось, что строка будет «Last Name» длиной 9, но «First Name» имеет длину 10 и отличается рядом с «Fir» (индекс 0)).
Окончание следует…
Источник: https://methodpoet.com/fluent-assertions/
9 Советов по Fluent Assertions, Которые Сэкономят Вам Время. Продолжение
Начало
5. Методы расширения коллекции
var coll = new List<string>Методов расширения для коллекций множество. Думаю, имена говорят сами за себя:
{ "first", "second" };
var coll2 = new List<string>
{ "first", "second" };
coll.Should().NotBeEmpty();6. Утверждения даты и времени
….HaveCount(2);
….Equal(coll2);
….Equal("first", "second");
….BeEquivalentTo(coll2);
….NotBeEquivalentTo(
new List<string> { "1", "2" });
….OnlyHaveUniqueItems();
….HaveCountGreaterThan(1);
….HaveCountGreaterThanOrEqualTo(2);
….HaveCountLessThanOrEqualTo(5);
….HaveCountLessThan(5);
….NotHaveCount(1);
….StartWith("first");
….StartWith(
new List<string> { "first" });
….EndWith("second");
….EndWith(
new List<string> { "first", "second" });
….Contain("first")
.And.HaveElementAt(1, "first");
….BeEmpty();
….BeNullOrEmpty();
….NotBeNullOrEmpty();
….ContainInOrder(
new List<string> { "first", "second" });
….NotContainInOrder(
new List<string> { "1", "2", "3" });
….BeInAscendingOrder();
….BeInDescendingOrder();
….NotBeInAscendingOrder();
….NotBeInDescendingOrder();
DateTime date = new DateTime(2022, 2, 7);7. Область действия утверждений - AssertionScope
date.Should().Be(7.February(2022).At(0,0));
….BeAfter(6.February(2022).At(23,59));
….BeBefore(8.February(2022).At(0,1));
….BeSameDateAs(7.February(2022));
….NotBe(6.February(2022));
….NotBeAfter(8.February(2022).At(10, 28));
….NotBeBefore(1.January(2022));
….NotBeSameDateAs(27.February(2022));
Вы можете использовать AssertionScope для объединения нескольких утверждений в одно исключение. Это делает код чище и позволяет проверить сразу все утверждения, вместо завершения теста после первой неудачи:
int numberOfDocuments = 5;В примере выше будут отображены оба сбоя и возникнет исключение в момент удаления AssertionScope:
using (new AssertionScope())
{
numberOfDocuments.Should().Be(6);
"First Name".Should().Be("Last Name");
}
Expected numberOfDocuments to be 6, but found 5.
Expected string to be "Last Name" with a length of 9, but "First Name" has a length of 10, differs near "Fir" (index 0).
(Ожидалось, что numberOfDocuments будет равно 6, но найдено 5.
Ожидалось, что строка будет «Last Name» длиной 9, но «First Name» имеет длину 10 и отличается рядом с «Fir» (индекс 0)).
Окончание следует…
Источник: https://methodpoet.com/fluent-assertions/
👍10