.NET Разработчик
6.51K subscribers
427 photos
2 videos
14 files
2.04K links
Дневник сертифицированного .NET разработчика.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 1623. #ProjectManagement
Управляем Здоровьем Команды, Техническим Долгом и Предотвращаем Переписывание с Нуля. Продолжение
Начало

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Источник:
https://dev.to/stripe/designing-apis-for-humans-design-patterns-5847
👍5
День 1633. #ЗаметкиНаПолях
Разработка API для Людей.
Часть 3. Принципы разработки. Окончание
Начало

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

await PublishDomainEventsAsync();

return result;
}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Источник: https://postgres.ai/blog/20230722-10-postgres-tips-for-beginners
👍26👎1
День 1639. #ЗаметкиНаПолях
Использовать Записи, Классы или Структуры
Классы и структуры были частью C#, начиная с версии 1.0, но совсем недавно были добавлены записи. Сегодня сравним, где что лучше использовать.

Классы
Классы - наиболее универсальная из доступных структур данных, но требует больше ресурсов. Это не значит, что классы — плохой выбор. Высоко оптимизированные классы предлагают широкий спектр функций, что делает их мощным инструментом для написания кода. Вот типичное определение класса:
public class ApiClient : HttpClient
{
private string _myField = "";
public string MyProperty { get; set; }
public ApiClient()
{
BaseAddress = new Uri("https://api.com");
}
public string MyMethod()
=> "hello";
}

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

Структуры
Структуры лучше всего использовать для представления простых данных с небольшим поведением или без него. Вот типичная реализация структуры:
public struct GeoLocation
{
private double _latitude;
private double _longitude;
public GeoLocation(double lat, double lng)
{
_latitude = lat;
_longitude = lng;
}
public override string ToString()
=> $"(lat: {_latitude}, lon:{_longitude})";
}

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

См. подробнее, когда использовать структуру

Записи
Записи — это облегчённые ссылочные типы с семантикой значений. Кроме того, компилятор «из коробки» предлагает некоторые функции, такие как равенство по значению и форматирование при выводе. Вот два способа определения записи:
// обычный
public record ApiData
{
public int Id { get; init; }
}
// с позиционными параметрами
public record ApiData(int Id);

Особенности
- Записи по умолчанию - ссылочные типы.
- Можно определить конструкторы и свойства.
- Можно использовать наследование.
- Используются как небольшие неизменяемые структуры данных.
- в C# 10 появились структуры-записи (record struct), которые являются структурами, поддерживают все свойства записей, кроме наследования.

Запись стоит использовать, когда нужно инкапсулировать данные без сложного поведения (распространённый пример этого сценария — объекты передачи данных — DTO). Если структура данных будет выполнять сложное поведение и будет большим экземпляром, это признаки того, что нужно использовать класс. Кроме того, лучше выбрать класс, если понадобится изменение данных экземпляра после создания.

Источник: https://code-maze.com/csharp-should-we-use-records-classes-or-structs/
👍15
День 1640. #ЗаметкиНаПолях
Разработка API для Людей.
Часть 4. Шаблоны разработки. Начало

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

В этой части серии рассмотрим некоторые шаблоны проектирования, которые достаточно универсальны, чтобы быть полезными практически всем, кто участвует в проектировании API.

1. Язык
Называть вещи трудно. Проблема в том, что, как и в случае с именами переменных и функций, вы хотите, чтобы маршруты API, поля и типы были понятными, но лаконичными.

Используйте простой язык
Это очевидно, но на практике довольно сложно сделать и может привести к «эффекту велосипедного сарая». Постарайтесь выразить самую суть понятия и не бойтесь использовать словарь синонимов. Например, не путайте понятия пользователя и клиента. Пользователь напрямую использует ваш API, клиент (или конечный пользователь) – тот, кто покупает товары или услуги, которые может предлагать ваш пользователь через ваш API.

Избегайте жаргона
Не думайте, что ваш пользователь знает всё о вашей конкретной отрасли. Например, 16-значный номер кредитной карты называется основным номером счета (Primary Account Number, PAN). В финтех-кругах, люди говорят о PAN, DPAN и FPAN, поэтому поймут, если в платёжном API будет:
card.pan = 4242424242424242;
Но для более широкой аудитории всё же лучше подойдёт:
card.number = 4242424242424242;
Это особенно важно, когда вы думаете о том, кто является аудиторией вашего API. Скорее всего, это разработчик, не знакомый с финансовыми терминами, поэтому лучше предположить, что люди не знакомы с жаргоном вашей отрасли.

2. Структура
Используйте enum вместо bool
Представим, что у нас есть API для модели подписки. Мы хотим, чтобы пользователи могли определить, активна подписка или отменена. Кажется разумным определить:
Subscription.canceled={true, false}
Это сработает, но что если вам нужно будет добавить приостановку подписки? Т.е. мы делаем перерыв в приеме платежей, но подписка активна и не отменена. Придётся добавить новое поле:
Subscription.canceled={true, false}
Subscription.paused={true, false}
Теперь, чтобы увидеть фактический статус подписки, нам нужно смотреть на два поля. А что, если они оба true? Можно ли приостановить подписку, которая была отменена?
Вместо этого проще сделать поле статуса с перечислением:
Subscription.status={"active", "canceled"}
Тогда приостановку легко добавить, добавив значение перечисления:
Subscription.status={"active", "canceled", "paused"}
Мы добавили функциональность, но сохранили сложность API на том же уровне, а также сделали его более описательным. Если мы когда-нибудь решим удалить функцию приостановки подписки, удалить значение перечисления всегда будет проще, чем удалить поле. Наверняка есть случаи, когда поле bool подойдёт лучше, но всегда рассматривайте возможность возникновения третьего варианта.

Используйте вложенные объекты для расширяемости
Пробуйте логически сгруппировать поля вместе. Это:
customer.address = {
line1: "Main Street 123",
city: "San Francisco",
postal_code: "12345"
};
выглядит гораздо понятнее, чем:
customer.address_line1 = "Main street 123";
customer.address_city = "San Francisco";
customer.address_postal_code: "12345";

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

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

Источник:
https://dev.to/stripe/common-design-patterns-at-stripe-1hb4
👍12
День 1641. #ЗаметкиНаПолях
Разработка API для Людей.
Часть 4. Шаблоны разработки. Окончание

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

Начало

3. Ответы
Возвращайте тип объекта
В большинстве случаев вызов API делается для получения или изменения данных. В последнем случае нормой является возврат изменённого ресурса. Например, если вы обновите email клиента, то в ответе вы ожидаете получить данные клиента с обновленным email.
Чтобы облегчить жизнь разработчикам, чётко укажите, что именно возвращается. Например, маршрут
/v1/customers/:customer/payment_methods/:payment_method
должен вернуть тип PaymentMethod для данного клиента. Это должно быть очевидно из маршрута, но на всякий случай, верните тип объекта в поле "object", чтобы избежать путаницы:
{
"id": "pm_123",
"object": "payment_method",
"created": 1672217299,
"customer": "cus_123",

}
Это очень помогает при поиске по логам или для добавления методов защитного программирования на клиенте:
if (response.Data.Object != "payment_method") 
{
// не тот объект, который ожидался
return;
}

4. Безопасность
Используйте систему разрешений
Допустим, для крупного клиента вы добавили новую функцию, чтобы они протестировали её в бета-версии. Новый маршрут не задокументирован, о нём никто не знает, поэтому можно не волноваться. Несколько недель спустя вы вносите изменения в функцию, о которых попросил крупный клиент… И получаете серию гневных писем от других пользователей, у которых всё сломалось. Оказывается, о вашем секретном маршруте узнали другие.
Теперь надо не только решать проблемы клиентов. Теперь «бета»-функция фактически выпущена, т.к. об изменениях в ней придётся сообщать всем.
Если вы хотите, чтобы закрытые API оставались закрытыми, убедитесь, что к ним нельзя получить доступ, без соответствующих разрешений. Самый простой способ — привязать систему разрешений к ключу API. Если ключ API не авторизован для использования маршрута, верните сообщение об ошибке со статусом 403.

Сделайте идентификаторы неугадываемыми
Если вы разрабатываете API, который возвращает объекты со связанными с ними идентификаторами, убедитесь, что эти их нельзя угадать или каким-либо иным образом реконструировать. Если идентификаторы просто последовательные, то в лучшем случае вы непреднамеренно выдаёте ненужную информацию о бизнесе, в худшем случае - сильно подрываете безопасность.
Например, после покупки на сайте я получил идентификатор подтверждения заказа «10». Я могу сделать два предположения:
- У вас не такой большой бизнес, как вы, вероятно, заявляете.
- Я потенциально могу получить информацию о 9 предыдущих заказах (и обо всех будущих), так как знаю их идентификаторы. Если указанный ниже маршрут не защищён системой разрешений, можно угадать идентификатор и возможно получить закрытую информацию о других ваших клиентах:
https://api.example.com/v1/orders/9
Делайте идентификаторы неугадываемыми, например, используя UUID. Он, по сути, представляет собой строку случайных чисел и букв, что означает, что невозможно угадать, как будет выглядеть следующий идентификатор, основываясь на том, который у вас есть. Вы теряете в удобстве (гораздо проще говорить о «заказе 42», чем о «заказе 123e4567-e89b-12d3-a456-426614174000»), но вы компенсируете это преимуществами безопасности. Не забудьте сделать его понятным для человека, добавив префиксы объектов.

Источник: https://dev.to/stripe/common-design-patterns-at-stripe-1hb4
👍10
День 1642.
Давайте сегодня обсудим. К этому меня подтолкнули рекомендации ютуба с «музыкой для программирования». Меня интересно, почему эта музыка всегда однотипная? С чего взялось убеждение, что лучше всего программируется под электро-техно-транс… вотэтовсё? Вкусы у всех разные, но почему-то «для программирования» всегда предлагается один жанр.

Что вы слушаете, когда пишете код?

Голосуйте ниже и пишите в комментариях.

Источник изображения: https://8tracks.com/explore/programming
👍6