День 2659. #Карьера
Дорожная Карта .NET Разработчика в 2026. Окончание
Начало
III. Продвинутые навыки
Сеньоров определяет не количество изученных фреймворков, а образ мышления.
1. Проектирование и архитектура систем
- Структура и модульность приложений;
- Компромиссы между монолитами и распределёнными системами;
- Шаблоны масштабируемости и отказоустойчивости;
- Мониторинг и диагностика.
2. Контейнеры и доставка
Современное ПО не ограничивается принципом «работает на моей машине»:
- Концепции контейнеризации;
- Локальные и производственные среды;
- Автоматизированные сборки и развёртывания;
- Понимание конвейеров выпуска.
3. Облачные технологии
- Как размещаются приложения в облаке;
- Конфигурация среды;
- Основы безопасности;
- Вопросы стоимости и производительности.
4. Инструменты, упрощающие жизнь
Библиотеки и инструменты для:
- Логирования;
- Валидации;
- Маппинга;
- Тестирования;
- Фоновых задач;
- UI-компонентов.
Инструменты меняются. Концепции остаются. Сосредоточьтесь на том, почему они вам нужны, а не только на том, как их использовать.
IV. Часто игнорируемый навык: Коммуникация
Отличные разработчики:
- Пишут чёткие сообщения коммитов;
- Просто объясняют технические решения;
- Документируют системы для будущих коллег;
- Задают правильные вопросы.
Эти навыки часто важнее, чем знание ещё одного фреймворка.
Последовательность обучения
1. Git и основы программирования
2. Основы C#
3. Основы ASP.NET Core
4. Базы данных и доступ к данным
5. Выбор специализации
6. Создание реальных проектов
7. Развёртывание и архитектура
Итого
В разработке ПО нет финишной линии. Цель не в том, чтобы «знать всё», а в том, чтобы стать человеком, способным постоянно учиться и решать важные проблемы. Будьте последовательны. Создавайте. Размышляйте.
Источник: https://www.c-sharpcorner.com/article/a-practical-net-developer-roadmap-for-2026/
Дорожная Карта .NET Разработчика в 2026. Окончание
Начало
III. Продвинутые навыки
Сеньоров определяет не количество изученных фреймворков, а образ мышления.
1. Проектирование и архитектура систем
- Структура и модульность приложений;
- Компромиссы между монолитами и распределёнными системами;
- Шаблоны масштабируемости и отказоустойчивости;
- Мониторинг и диагностика.
2. Контейнеры и доставка
Современное ПО не ограничивается принципом «работает на моей машине»:
- Концепции контейнеризации;
- Локальные и производственные среды;
- Автоматизированные сборки и развёртывания;
- Понимание конвейеров выпуска.
3. Облачные технологии
- Как размещаются приложения в облаке;
- Конфигурация среды;
- Основы безопасности;
- Вопросы стоимости и производительности.
4. Инструменты, упрощающие жизнь
Библиотеки и инструменты для:
- Логирования;
- Валидации;
- Маппинга;
- Тестирования;
- Фоновых задач;
- UI-компонентов.
Инструменты меняются. Концепции остаются. Сосредоточьтесь на том, почему они вам нужны, а не только на том, как их использовать.
IV. Часто игнорируемый навык: Коммуникация
Отличные разработчики:
- Пишут чёткие сообщения коммитов;
- Просто объясняют технические решения;
- Документируют системы для будущих коллег;
- Задают правильные вопросы.
Эти навыки часто важнее, чем знание ещё одного фреймворка.
Последовательность обучения
1. Git и основы программирования
2. Основы C#
3. Основы ASP.NET Core
4. Базы данных и доступ к данным
5. Выбор специализации
6. Создание реальных проектов
7. Развёртывание и архитектура
Итого
В разработке ПО нет финишной линии. Цель не в том, чтобы «знать всё», а в том, чтобы стать человеком, способным постоянно учиться и решать важные проблемы. Будьте последовательны. Создавайте. Размышляйте.
Источник: https://www.c-sharpcorner.com/article/a-practical-net-developer-roadmap-for-2026/
👍11👎2
День 2660. #ЗаметкиНаПолях
Отключаем HTTP-Кэширование по Умолчанию в ASP.NET Core API
При создании API в ASP.NET Core крайне важно явно контролировать поведение кэширования. В отличие от веб-страниц, где кэширование часто улучшает пользовательский опыт, ответы API не следует кэшировать по умолчанию, если вы намеренно не предусмотрели возможность их кэширования. Непреднамеренное кэширование может привести к серьёзным проблемам, включая устаревшие данные, уязвимости безопасности и трудновоспроизводимые ошибки.
Стандарты кэширования в HTTP
Кэширование HTTP регулируется RFC 7234 (Кэширование в HTTP/1.1) и RFC 9111 (HTTP Кэширование). В соответствии с этими спецификациями, кэши разных уровней могут хранить ответы и предоставлять их без обращения к исходному серверу, используя заголовки, такие как
При отсутствии заголовков кэши могут применять эвристическое истечение срока действия, кэшируя ответы API даже без явных на то указаний. Это особенно проблематично, поскольку браузеры и промежуточные кэши (такие как CDN, прокси или кэши шлюзов) могут использовать собственные алгоритмы для определения того, как долго хранить ответ.
Реализация промежуточного ПО с запретом кэширования
Чтобы гарантировать, что ответы API не будут кэшироваться по умолчанию, реализуйте промежуточное ПО, которое устанавливает соответствующие заголовки управления кэшированием. Оно добавляет необходимые заголовки, чтобы предотвратить кэширование, если вы явно не переопределите их в конкретных контроллерах или действиях.
Вот как создать такое промежуточное ПО:
Регистрируем наше промежуточное ПО в конвейере:
Источник: https://www.meziantou.net/disable-http-caching-by-default-in-asp-net-core-apis.htm
Отключаем HTTP-Кэширование по Умолчанию в ASP.NET Core API
При создании API в ASP.NET Core крайне важно явно контролировать поведение кэширования. В отличие от веб-страниц, где кэширование часто улучшает пользовательский опыт, ответы API не следует кэшировать по умолчанию, если вы намеренно не предусмотрели возможность их кэширования. Непреднамеренное кэширование может привести к серьёзным проблемам, включая устаревшие данные, уязвимости безопасности и трудновоспроизводимые ошибки.
Стандарты кэширования в HTTP
Кэширование HTTP регулируется RFC 7234 (Кэширование в HTTP/1.1) и RFC 9111 (HTTP Кэширование). В соответствии с этими спецификациями, кэши разных уровней могут хранить ответы и предоставлять их без обращения к исходному серверу, используя заголовки, такие как
Cache-Control, Expires, ETag и Last-Modified.При отсутствии заголовков кэши могут применять эвристическое истечение срока действия, кэшируя ответы API даже без явных на то указаний. Это особенно проблематично, поскольку браузеры и промежуточные кэши (такие как CDN, прокси или кэши шлюзов) могут использовать собственные алгоритмы для определения того, как долго хранить ответ.
Реализация промежуточного ПО с запретом кэширования
Чтобы гарантировать, что ответы API не будут кэшироваться по умолчанию, реализуйте промежуточное ПО, которое устанавливает соответствующие заголовки управления кэшированием. Оно добавляет необходимые заголовки, чтобы предотвратить кэширование, если вы явно не переопределите их в конкретных контроллерах или действиях.
Вот как создать такое промежуточное ПО:
internal sealed class
NoCacheMiddleware(RequestDelegate next)
{
public async Task InvokeAsync(HttpContext ctx)
{
ctx.Response.OnStarting(() =>
{
// Устанавливаем заголовки no-cache, только если не были установлены явно
if (ctx.Response.Headers.CacheControl.Count is 0)
ctx.Response.Headers.CacheControl =
"no-cache,no-store,must-revalidate";
return Task.CompletedTask;
});
await next(ctx);
}
}
Регистрируем наше промежуточное ПО в конвейере:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseMiddleware<NoCacheMiddleware>();
app.MapGet("/", () => "Hello World!");
app.Run();
Источник: https://www.meziantou.net/disable-http-caching-by-default-in-asp-net-core-apis.htm
👍11👎1
День 2661. #МоиИнструменты #PG
Инструменты Оптимизации Запросов в PostgreSQL. Часть 9
9. HypoPG (Гипотетические индексы для PostgreSQL)
Что даёт: тестирование влияния индексов без их создания.
Зачем нужен
Создание индексов на больших таблицах — дорогостоящий процесс. HypoPG позволяет тестировать эффективность индексов (видеть изменения плана запроса) без их фактического создания.
Использование
1. Анализируем долгий запрос:
Результат:
2. Создаём гипотетический индекс:
ID индекса: 18001
3. Анализируем запрос повторно. Результат:
Улучшение производительности: 2341мс → 0,112мс (в 20000 раз быстрее)
Решение: создать реальный индекс.
4. Удаляем гипотетический индекс и создаём реальный.
Тестируем разные варианты индекса
Когда неясно, какой индекс лучше:
Когда использовать
- Большие таблицы (создание индекса дорого);
- Не уверены, какой индекс создать;
- Хотите протестировать перед внедрением в прод;
- База в проде (нельзя экспериментировать).
Когда отказаться
- Небольшие таблицы (просто создайте индекс, это быстро);
- Очевидно, какой индекс нужен.
Скрытая функция
Автоматическая рекомендация индекса на основе рабочей нагрузки запросов (совместно с pg_stat_statements):
Предложит индексы для самых медленных запросов. Останется протестировать каждый с помощью EXPLAIN.
С осторожностью
Гипотетические индексы сохраняются в сессии и влияют на все запросы в сессии.
EXPLAIN использует гипотетический индекс, что исказит результаты.
Решения
1. Использовать отдельную сессию для тестов;
2. Удалять гипотетические индексы после тестов:
3. Использовать транзакции:
Источник: https://medium.com/@reliabledataengineering/15-sql-optimization-tools-that-make-queries-10x-faster-8629ac451d97
Инструменты Оптимизации Запросов в PostgreSQL. Часть 9
9. HypoPG (Гипотетические индексы для PostgreSQL)
Что даёт: тестирование влияния индексов без их создания.
Зачем нужен
Создание индексов на больших таблицах — дорогостоящий процесс. HypoPG позволяет тестировать эффективность индексов (видеть изменения плана запроса) без их фактического создания.
Использование
-- Установка расширения
CREATE EXTENSION hypopg;
1. Анализируем долгий запрос:
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE customer_id = 12345
AND order_date >= '2024-01-01';
Результат:
Seq Scan on orders (cost=0.00..185234.25) (actual time=2341.234ms)
2. Создаём гипотетический индекс:
SELECT hypopg_create_index('CREATE INDEX ON orders(customer_id, order_date)');
-- Вывод: (18001,"<18001>btree_orders(customer_id, order_date)")ID индекса: 18001
3. Анализируем запрос повторно. Результат:
Index Scan using <18001>btree_orders (cost=0.43..8.45) (actual time=0.112ms)
Улучшение производительности: 2341мс → 0,112мс (в 20000 раз быстрее)
Решение: создать реальный индекс.
4. Удаляем гипотетический индекс и создаём реальный.
SELECT hypopg_drop_index(18001);
CREATE INDEX CONCURRENTLY idx_orders_customer_date
ON orders(customer_id, order_date);
Тестируем разные варианты индекса
Когда неясно, какой индекс лучше:
-- 1: (customer_id, order_date)
SELECT hypopg_create_index('CREATE INDEX ON orders(customer_id, order_date)');
EXPLAIN SELECT … FROM orders WHERE customer_id = ? AND order_date >= ?;
-- Cost: 8.45
-- 2: (order_date, customer_id) – другой порядок
SELECT hypopg_reset(); -- Удаляем предыдущие гипот. индексы
SELECT hypopg_create_index('CREATE INDEX ON orders(order_date, customer_id)');
EXPLAIN SELECT … FROM orders WHERE customer_id = ? AND order_date >= ?;
-- Cost: 12.34
-- 3: Частичный (только последние заказы)
SELECT hypopg_reset();
SELECT hypopg_create_index('CREATE INDEX ON orders(customer_id) WHERE order_date >= ''2024-01-01''');
EXPLAIN SELECT … FROM orders WHERE customer_id = ? AND order_date >= '2024-01-01';
-- Cost: 6.12 (лучше!)
-- Создаём оптимальный индекс
CREATE INDEX CONCURRENTLY idx_orders_customer_recent
ON orders(customer_id)
WHERE order_date >= '2024-01-01';
Когда использовать
- Большие таблицы (создание индекса дорого);
- Не уверены, какой индекс создать;
- Хотите протестировать перед внедрением в прод;
- База в проде (нельзя экспериментировать).
Когда отказаться
- Небольшие таблицы (просто создайте индекс, это быстро);
- Очевидно, какой индекс нужен.
Скрытая функция
Автоматическая рекомендация индекса на основе рабочей нагрузки запросов (совместно с pg_stat_statements):
CREATE EXTENSION pg_stat_statements;
CREATE EXTENSION hypopg;
-- Ищем запросы, которым нужны индексы
SELECT
calls,
total_exec_time,
query,
hypopg_create_index(
'CREATE INDEX ON ' ||
regexp_replace(query, '^.*FROM\s+(\w+).*WHERE\s+(\w+)\s*=.*',
'\1(\2)')
) AS suggested_index
FROM pg_stat_statements
WHERE calls > 100
AND total_exec_time > 10000 -- 10+ секунд
AND query ~* 'WHERE.*=' -- Содержат WHERE
ORDER BY total_exec_time DESC
LIMIT 10;
Предложит индексы для самых медленных запросов. Останется протестировать каждый с помощью EXPLAIN.
С осторожностью
Гипотетические индексы сохраняются в сессии и влияют на все запросы в сессии.
SELECT hypopg_create_index('CREATE INDEX ON orders(customer_id)');
-- …
-- Позже в той же сессии:
EXPLAIN SELECT * FROM orders;EXPLAIN использует гипотетический индекс, что исказит результаты.
Решения
1. Использовать отдельную сессию для тестов;
2. Удалять гипотетические индексы после тестов:
SELECT hypopg_reset();
3. Использовать транзакции:
BEGIN;
SELECT hypopg_create_index(…);
EXPLAIN SELECT …;
ROLLBACK; -- Очистит гипот. индексы
Источник: https://medium.com/@reliabledataengineering/15-sql-optimization-tools-that-make-queries-10x-faster-8629ac451d97
👍11
День 2662. #ЧтоНовенького #NET11
В .NET 11 Исключения Фоновых Сервисов Будут Всплывать
Ошибка, которая давно существовала в .NET: если BackgroundService генерировал исключение после первого await, хост перехватывал его, писал критическое сообщение в лог, а затем корректно завершал работу с кодом результата 0. Т.е. все думали, что процесс успешно завершился. Теперь это исправлено!
Проблема
Рассмотрим этот рабочий процесс:
До .NET 11 вывод был таким:
Процесс завершён с кодом 0 ??? Критическая ошибка в логе, но код результата всё равно 0.
Почему это так работало?
Если метод помечен как асинхронный, исключение перехватывалось функцией TryExecuteBackgroundServiceAsync глубоко внутри инфраструктуры, логировалось как критическое, после чего хост инициировал корректное завершение работы. Корректное завершение работы => код результата 0.
Если метод не помечен как асинхронный и выбрасывалось исключение, то процесс завершался с кодом ошибки.
Основная причина в том, что host.RunAsync() возвращает Task (а не Task<int>), и не было механизма для сигнализации о завершении процесса с кодом ошибки для фонового сервиса, который бы указывал на сбой.
Исправление в .NET 11
Теперь IHost.RunAsync() и IHost.StopAsync() будут передавать исключение из BackgroundService, а не игнорировать его. Теперь вы получите:
Если вам нужно старое поведение
Вы можете установить для параметра обработки исключений значение «Игнорировать»:
См. оригинальный ишью.
Источник: https://steven-giesel.com/blogPost/00fcb870-6bf7-4f97-824f-8eab1b8838be/backgroundservice-exceptions-now-propagate-in-net-11
В .NET 11 Исключения Фоновых Сервисов Будут Всплывать
Ошибка, которая давно существовала в .NET: если BackgroundService генерировал исключение после первого await, хост перехватывал его, писал критическое сообщение в лог, а затем корректно завершал работу с кодом результата 0. Т.е. все думали, что процесс успешно завершился. Теперь это исправлено!
Проблема
Рассмотрим этот рабочий процесс:
public class Worker : BackgroundService
{
protected override async Task
ExecuteAsync(CancellationToken stopToken)
{
await Task.Delay(TimeSpan.FromSeconds(1), stopToken);
throw new Exception("something went wrong");
}
}
До .NET 11 вывод был таким:
crit: Microsoft.Extensions.Hosting.Internal.Host[10]
The HostOptions.BackgroundServiceExceptionBehavior is configured to StopHost.
A BackgroundService has thrown an unhandled exception, and the IHost instance is stopping.
...
System.Exception: something went wrong
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
Процесс завершён с кодом 0 ??? Критическая ошибка в логе, но код результата всё равно 0.
Почему это так работало?
Если метод помечен как асинхронный, исключение перехватывалось функцией TryExecuteBackgroundServiceAsync глубоко внутри инфраструктуры, логировалось как критическое, после чего хост инициировал корректное завершение работы. Корректное завершение работы => код результата 0.
Если метод не помечен как асинхронный и выбрасывалось исключение, то процесс завершался с кодом ошибки.
Основная причина в том, что host.RunAsync() возвращает Task (а не Task<int>), и не было механизма для сигнализации о завершении процесса с кодом ошибки для фонового сервиса, который бы указывал на сбой.
Исправление в .NET 11
Теперь IHost.RunAsync() и IHost.StopAsync() будут передавать исключение из BackgroundService, а не игнорировать его. Теперь вы получите:
crit: Microsoft.Extensions.Hosting.Internal.Host[10]
…
System.Exception: something went wrong
Unhandled exception. System.Exception: something went wrong
at Worker.ExecuteAsync(…)
Process finished with exit code 134. (или 1 в Windows)
Если вам нужно старое поведение
Вы можете установить для параметра обработки исключений значение «Игнорировать»:
services.Configure<HostOptions>(options =>
{
options.BackgroundServiceExceptionBehavior = BackgroundServiceExceptionBehavior.Ignore;
});
См. оригинальный ишью.
Источник: https://steven-giesel.com/blogPost/00fcb870-6bf7-4f97-824f-8eab1b8838be/backgroundservice-exceptions-now-propagate-in-net-11
👍18
День 2663. #ЗаметкиНаПолях #DDD
Что Такое Инварианты? Начало
Во многих «DDD-подобных» кодовых базах .NET бизнес правила разбросаны по обработчикам, валидаторам и контроллерам, и почти не затрагивают саму доменную модель. Каждая копия одного и того же правила со временем немного меняется, и валидность данного объекта начинает зависеть от того, каким путём вызывающий код к нему добрался. Вы, безусловно, можете построить работающую систему таким образом. Но есть более чистый способ, и он начинается с одной идеи.
Что такое инвариант?
Инвариант — это правило об объекте, которое должно оставаться верным до тех пор, пока объект существует.
Не только когда вы его сохраняете или когда запускается валидатор. Правило должно оставаться верным каждый раз, когда вы обращаетесь к объекту, независимо от того, как он попал в память.
Несколько примеров:
- У курса всегда есть непустое название.
- Итоговая сумма заказа всегда равна сумме стоимостей позиций в нём.
- Подписка находится ровно в одном состоянии: пробная, активная, истекшая или отменённая.
Ни в одном из этих случаев не упоминаются валидация, сохранение данных или HTTP. Это утверждения о предметной области, и они должны быть истинными независимо от способа загрузки объекта.
Где процедурный код дает сбои
Возьмём простой курс, написанный так, как это до сих пор делают большинство CRUD-подобных .NET-приложений:
В классе нет конструктора, и у каждого свойства есть публичный сеттер, поэтому он готов принимать любые комбинации значений.
Чтобы данные оставались корректными, правила разбросаны по всему приложению:
- CreateCourseValidator проверяет, не пуст ли заголовок.
- PublishCourseHandler устанавливает статус «Опубликован» и дату PublishedOn и не забывает проверить, не был ли курс уже в статусе «Опубликован».
- ChangePriceHandler проверяет, не отправлен ли курс в архив.
Появляется новая конечная точка, кто-то копирует существующий обработчик, и проверка архивности незаметно исчезает.
Каждое правило находится в месте, которое случайно оказывается на пути запроса. Ничто в самом классе Course не мешает ему перейти в недействительное состояние.
В этом и заключается реальная цена анемичной модели. Дело не в отсутствии поведения в классе. Дело в том, что класс не даёт никаких гарантий, поэтому каждый вызывающий объект должен самостоятельно обеспечивать соблюдение правил.
Решение простое: модель никогда не должна принимать недействительное состояние.
Если у вас есть ссылка на Course, вы можете ей доверять. Вам не нужна проверка
Окончание следует…
Источник: https://www.milanjovanovic.tech/blog/what-invariants-are-and-why-a-domain-model-is-the-best-place-to-enforce-them
Что Такое Инварианты? Начало
Во многих «DDD-подобных» кодовых базах .NET бизнес правила разбросаны по обработчикам, валидаторам и контроллерам, и почти не затрагивают саму доменную модель. Каждая копия одного и того же правила со временем немного меняется, и валидность данного объекта начинает зависеть от того, каким путём вызывающий код к нему добрался. Вы, безусловно, можете построить работающую систему таким образом. Но есть более чистый способ, и он начинается с одной идеи.
Что такое инвариант?
Инвариант — это правило об объекте, которое должно оставаться верным до тех пор, пока объект существует.
Не только когда вы его сохраняете или когда запускается валидатор. Правило должно оставаться верным каждый раз, когда вы обращаетесь к объекту, независимо от того, как он попал в память.
Несколько примеров:
- У курса всегда есть непустое название.
- Итоговая сумма заказа всегда равна сумме стоимостей позиций в нём.
- Подписка находится ровно в одном состоянии: пробная, активная, истекшая или отменённая.
Ни в одном из этих случаев не упоминаются валидация, сохранение данных или HTTP. Это утверждения о предметной области, и они должны быть истинными независимо от способа загрузки объекта.
Где процедурный код дает сбои
Возьмём простой курс, написанный так, как это до сих пор делают большинство CRUD-подобных .NET-приложений:
public class Course
{
public string Title { get; set; }
public CourseStatus Status { get; set; }
public DateTime? PublishedOn { get; set; }
public decimal Price { get; set; }
}
В классе нет конструктора, и у каждого свойства есть публичный сеттер, поэтому он готов принимать любые комбинации значений.
Чтобы данные оставались корректными, правила разбросаны по всему приложению:
- CreateCourseValidator проверяет, не пуст ли заголовок.
- PublishCourseHandler устанавливает статус «Опубликован» и дату PublishedOn и не забывает проверить, не был ли курс уже в статусе «Опубликован».
- ChangePriceHandler проверяет, не отправлен ли курс в архив.
Появляется новая конечная точка, кто-то копирует существующий обработчик, и проверка архивности незаметно исчезает.
Каждое правило находится в месте, которое случайно оказывается на пути запроса. Ничто в самом классе Course не мешает ему перейти в недействительное состояние.
В этом и заключается реальная цена анемичной модели. Дело не в отсутствии поведения в классе. Дело в том, что класс не даёт никаких гарантий, поэтому каждый вызывающий объект должен самостоятельно обеспечивать соблюдение правил.
Решение простое: модель никогда не должна принимать недействительное состояние.
Если у вас есть ссылка на Course, вы можете ей доверять. Вам не нужна проверка
if (course.Title is null), не нужно вызывать валидатор и не нужно надеяться, что обработчик до вас выполнил правильную проверку.Окончание следует…
Источник: https://www.milanjovanovic.tech/blog/what-invariants-are-and-why-a-domain-model-is-the-best-place-to-enforce-them
👍16👎1
День 2664. #ЗаметкиНаПолях #DDD
Что Такое Инварианты? Окончание
Начало
Модель как источник истины
Три шага к цели
1. Запрет создания недействительных объектов
Курс без названия не должен существовать — делаем это невозможным:
Приватный конструктор со статической фабрикой предоставляет единственный вариант создания объекта, и именно там выполняется валидация. Теперь любой код, работающий с объектом Course, может предполагать, что у него есть допустимое название.
Объекты-значения, такие как Money, применяют ту же идею: Money не может быть отрицательным или обязан иметь тип валюты.
2. Инкапсуляция переходов состояния
Класс должен контролировать, как он изменяется, вместо того чтобы оставлять это на усмотрение его обработчиков. Никаких сеттеров, и каждое изменение проходит через метод, который знает правила:
Обработчику не нужно знать, был ли курс уже опубликован, или помнить о проверке на наличие уроков. Он вызывает Publish и использует полученный результат. Правило находится рядом с состоянием, которое оно защищает.
3. Инкапсуляция агрегата
Некоторые правила охватывают несколько сущностей в пределах одного агрегата. Корень агрегата — правильное место для обеспечения их соблюдения.
Например, правило: опубликованный курс должен содержать как минимум один урок, и уроки нельзя удалять после публикации.
Неправильный подход — предоставлять Lessons как изменяемую коллекцию и полагаться на то, что обработчики будут соблюдать правило везде. Правильный подход — держать коллекцию приватной и принудительно обрабатывать каждое изменение через корень агрегата:
Когда правило должно охватывать два агрегата вместо одного, это уже другая проблема, тут лучше использовать событие предметной области, а не позволять одному агрегату влиять на другой.
Итого
Можно написать ту же систему процедурно, и она может хорошо работать, но вы теряете доверие. В процедурной системе каждый вызывающий объект несёт ответственность за то, чтобы не нарушать правила. В постоянно действительной модели эта ответственность лежит на самой модели. Разница накапливается со временем:
- Валидаторы не меняются, потому что нечего дублировать.
- Проверки кода фокусируются на поведении, а не на «не забыли ли мы проверить Х?».
- Новые конечные точки не могут случайно обойти правило, находящееся в сущности.
- В тестах не нужно проверять недействительные сценарии.
- Модель из пассивного носителя данных превращается в минимальное и чёткое место хранения бизнес-правил.
В основе этого лежит инкапсуляция. Модель инкапсулирует правила, управляющие её состоянием, а остальная часть системы взаимодействует с ней через чётко определённый интерфейс. Это приводит к более чистому коду, меньшему количеству ошибок и в целом к более удобной в сопровождении системе.
Источник: https://www.milanjovanovic.tech/blog/what-invariants-are-and-why-a-domain-model-is-the-best-place-to-enforce-them
Что Такое Инварианты? Окончание
Начало
Модель как источник истины
Три шага к цели
1. Запрет создания недействительных объектов
Курс без названия не должен существовать — делаем это невозможным:
public class Course
{
private Course(CourseId id, string title, Money price)
{
Id = id;
Title = title;
Price = price;
Status = CourseStatus.Draft;
}
public static Result<Course>
Create(string title, Money price)
{
if (string.IsNullOrWhiteSpace(title))
return CourseErrors.TitleRequired;
return new Course(CourseId.New(), title, price);
}
Приватный конструктор со статической фабрикой предоставляет единственный вариант создания объекта, и именно там выполняется валидация. Теперь любой код, работающий с объектом Course, может предполагать, что у него есть допустимое название.
Объекты-значения, такие как Money, применяют ту же идею: Money не может быть отрицательным или обязан иметь тип валюты.
2. Инкапсуляция переходов состояния
Класс должен контролировать, как он изменяется, вместо того чтобы оставлять это на усмотрение его обработчиков. Никаких сеттеров, и каждое изменение проходит через метод, который знает правила:
public Result Publish(IDateTimeProvider clock)
{
if (Status != CourseStatus.Draft)
return CourseErrors.AlreadyPublished;
if (_lessons.Count == 0)
return CourseErrors.CannotPublishWithoutLessons;
Status = CourseStatus.Published;
PublishedOn = clock.UtcNow;
return Result.Success();
}
Обработчику не нужно знать, был ли курс уже опубликован, или помнить о проверке на наличие уроков. Он вызывает Publish и использует полученный результат. Правило находится рядом с состоянием, которое оно защищает.
3. Инкапсуляция агрегата
Некоторые правила охватывают несколько сущностей в пределах одного агрегата. Корень агрегата — правильное место для обеспечения их соблюдения.
Например, правило: опубликованный курс должен содержать как минимум один урок, и уроки нельзя удалять после публикации.
Неправильный подход — предоставлять Lessons как изменяемую коллекцию и полагаться на то, что обработчики будут соблюдать правило везде. Правильный подход — держать коллекцию приватной и принудительно обрабатывать каждое изменение через корень агрегата:
public class Course
{
private readonly List<Lesson> _lessons = [];
public IReadOnlyCollection<Lesson>
Lessons => _lessons.AsReadOnly();
public Result RemoveLesson(LessonId id)
{
if (Status == CourseStatus.Published)
return CourseErrors.CannotModifyPublishedLessons;
var lesson = _lessons.FirstOrDefault(l => l.Id == id);
if (lesson is null)
return CourseErrors.LessonNotFound;
_lessons.Remove(lesson);
return Result.Success();
}
}
Когда правило должно охватывать два агрегата вместо одного, это уже другая проблема, тут лучше использовать событие предметной области, а не позволять одному агрегату влиять на другой.
Итого
Можно написать ту же систему процедурно, и она может хорошо работать, но вы теряете доверие. В процедурной системе каждый вызывающий объект несёт ответственность за то, чтобы не нарушать правила. В постоянно действительной модели эта ответственность лежит на самой модели. Разница накапливается со временем:
- Валидаторы не меняются, потому что нечего дублировать.
- Проверки кода фокусируются на поведении, а не на «не забыли ли мы проверить Х?».
- Новые конечные точки не могут случайно обойти правило, находящееся в сущности.
- В тестах не нужно проверять недействительные сценарии.
- Модель из пассивного носителя данных превращается в минимальное и чёткое место хранения бизнес-правил.
В основе этого лежит инкапсуляция. Модель инкапсулирует правила, управляющие её состоянием, а остальная часть системы взаимодействует с ней через чётко определённый интерфейс. Это приводит к более чистому коду, меньшему количеству ошибок и в целом к более удобной в сопровождении системе.
Источник: https://www.milanjovanovic.tech/blog/what-invariants-are-and-why-a-domain-model-is-the-best-place-to-enforce-them
👍11👎1
День 2665. #ЧтоНовенького #NET11
Улучшения API процессов в .NET 11. Обзор
Класс System.Diagnostics.Process — это основной способ создания процессов и взаимодействия с ними в .NET. В .NET 11 в него внесли самое масштабное обновление за последние годы. Изменения добавляют высокоуровневые API, которые упрощают запуск процесса и перехват его вывода без взаимоблокировок, предоставляют полный контроль над наследованием дескрипторов и стандартным перенаправлением дескрипторов, вводят функции управления временем жизни, такие как KillOnParentExit, и включают в себя облегченный API на основе SafeProcessHandle, более удобный для оптимизации. В этой серии постов рассмотрим нововведения подробно.
Новые функции
1. Однострочник для выполнения процесса -
Запускает процесс, перехватывает вывод/ошибку, ожидает завершения — всё за один вызов.
2. Выполнение без перехвата вывода -
Запускает процесс и ожидает завершения без перехвата вывода.
3. Запустить и забыть -
Запускает процесс, возвращает его PID и немедленно освобождает все ресурсы.
4. Перехват вывода без взаимоблокировок -
Читает stdout и stderr одновременно с использованием мультиплексирования, избегая взаимоблокировок буфера канала.
5. Перенаправление -
Перенаправляет стандартные дескрипторы на файлы, каналы, null или любой SafeFileHandle.
6. Контролируемое наследование -
Точно указывает, какие дескрипторы наследуются дочерними процессами, предотвращая случайные утечки памяти.
7. Завершение при выходе родительского процесса -
Гарантирует завершение дочерних процессов при выходе родительского процесса (Windows и Linux).
8. Отсоединённые процессы -
Запускает процесс, который сохраняется после выхода родительского процесса, сигнала или закрытия терминала.
9. Легковесный дескриптор процесса -
Удобный для сокращения количества процессов низкоуровневый API для запуска и управления процессами без Process.
10. Подробная информация о завершении процесса -
Сообщает код завершения, сигнал завершения (Unix) и был ли процесс завершён из-за тайм-аута/отмены.
11. Нулевой дескриптор -
Открывает дескриптор, который отбрасывает операции записи и возвращает EOF при чтении.
12. Анонимные каналы -
Создаёт пару подключённых каналов с дополнительной поддержкой асинхронности.
13. Обработчики консоли -
Получает базовый дескриптор операционной системы для стандартных потоков.
14. Определение типа дескриптора -
Определяет, является ли дескриптор файлом, каналом, сокетом и т. д.
Далее рассмотрим новые функции подробнее.
Продолжение следует…
Источник: https://devblogs.microsoft.com/dotnet/process-api-improvements-in-dotnet-11/
Улучшения API процессов в .NET 11. Обзор
Класс System.Diagnostics.Process — это основной способ создания процессов и взаимодействия с ними в .NET. В .NET 11 в него внесли самое масштабное обновление за последние годы. Изменения добавляют высокоуровневые API, которые упрощают запуск процесса и перехват его вывода без взаимоблокировок, предоставляют полный контроль над наследованием дескрипторов и стандартным перенаправлением дескрипторов, вводят функции управления временем жизни, такие как KillOnParentExit, и включают в себя облегченный API на основе SafeProcessHandle, более удобный для оптимизации. В этой серии постов рассмотрим нововведения подробно.
Новые функции
1. Однострочник для выполнения процесса -
Process.RunAndCaptureText[Async]Запускает процесс, перехватывает вывод/ошибку, ожидает завершения — всё за один вызов.
2. Выполнение без перехвата вывода -
Process.Run[Async]Запускает процесс и ожидает завершения без перехвата вывода.
3. Запустить и забыть -
Process.StartAndForgetЗапускает процесс, возвращает его PID и немедленно освобождает все ресурсы.
4. Перехват вывода без взаимоблокировок -
Process.ReadAllText/Bytes/Lines[Async]Читает stdout и stderr одновременно с использованием мультиплексирования, избегая взаимоблокировок буфера канала.
5. Перенаправление -
ProcessStartInfo.Standard[Input/Output/Error]HandleПеренаправляет стандартные дескрипторы на файлы, каналы, null или любой SafeFileHandle.
6. Контролируемое наследование -
ProcessStartInfo.InheritedHandlesТочно указывает, какие дескрипторы наследуются дочерними процессами, предотвращая случайные утечки памяти.
7. Завершение при выходе родительского процесса -
ProcessStartInfo.KillOnParentExitГарантирует завершение дочерних процессов при выходе родительского процесса (Windows и Linux).
8. Отсоединённые процессы -
ProcessStartInfo.StartDetachedЗапускает процесс, который сохраняется после выхода родительского процесса, сигнала или закрытия терминала.
9. Легковесный дескриптор процесса -
SafeProcessHandle.Start/WaitForExit/Kill/SignalУдобный для сокращения количества процессов низкоуровневый API для запуска и управления процессами без Process.
10. Подробная информация о завершении процесса -
ProcessExitStatusСообщает код завершения, сигнал завершения (Unix) и был ли процесс завершён из-за тайм-аута/отмены.
11. Нулевой дескриптор -
File.OpenNullHandle()Открывает дескриптор, который отбрасывает операции записи и возвращает EOF при чтении.
12. Анонимные каналы -
SafeFileHandle.CreateAnonymousPipeСоздаёт пару подключённых каналов с дополнительной поддержкой асинхронности.
13. Обработчики консоли -
Console.OpenStandard[Input/Output/Error]Handle()Получает базовый дескриптор операционной системы для стандартных потоков.
14. Определение типа дескриптора -
SafeFileHandle.TypeОпределяет, является ли дескриптор файлом, каналом, сокетом и т. д.
Далее рассмотрим новые функции подробнее.
Продолжение следует…
Источник: https://devblogs.microsoft.com/dotnet/process-api-improvements-in-dotnet-11/
👍16
День 2666. #ЧтоНовенького #NET11
Улучшения API процессов в .NET 11. Продолжение
Обзор
Перехват выходных данных процесса без взаимоблокировок
Почему захват выходных данных процесса может приводить к зависанию
При перенаправлении стандартного вывода (stdout) и ошибок (stderr) процесса возможны взаимоблокировки. Понимание причин этого крайне важно для понимания сути новинок. Создадим приложение, которое пытается прочитать все выходные данные и ошибки процесса.
Прежде всего, нужно перенаправить stdout и stderr, чтобы иметь возможность их прочитать. Это делается путём установки свойств RedirectStandardOutput и RedirectStandardError объекта ProcessStartInfo в true. Перед запуском процесса создаются два выделенных канала (для вывода и для ошибок). Дочерний процесс записывает данные в свои stdout и stderr, как обычно, но вместо вывода в консоль данные записываются в каналы.
Каналы имеют ограниченный размер буфера (обычно 4КБ в Windows и 64КБ в Unix). Когда производитель (в нашем случае, дочерний процесс) записывает данные в канал, они сохраняются в буфере до тех пор, пока их не прочтёт потребитель (родительский процесс). Если производитель записывает больше данных, чем размер буфера, а потребитель одновременно не читает из канала, производитель будет заблокирован на операции записи, ожидая, пока потребитель прочитает из канала и освободит место в буфере.
Если потребитель ожидает завершения работы производителя (например, вызывая WaitForExit) без чтения из канала, он будет заблокирован, как только производитель заполнит буфер:
Перемещение
Причина в том, что мы читаем два потока последовательно, а не одновременно. Чтобы избежать этого, нужно одновременно очищать и стандартный вывод, и ошибку. До сих пор у нас было два варианта:
1. Использовать асинхронные операции чтения для StandardOutput и StandardError
2. Использовать события OutputDataReceived и ErrorDataReceived
События возникают при записи строки в stdout и stderr соответственно:
Существующие API не оптимальны с точки зрения простоты и производительности.
Продолжение следует…
Источник: https://devblogs.microsoft.com/dotnet/process-api-improvements-in-dotnet-11/
Улучшения API процессов в .NET 11. Продолжение
Обзор
Перехват выходных данных процесса без взаимоблокировок
Почему захват выходных данных процесса может приводить к зависанию
При перенаправлении стандартного вывода (stdout) и ошибок (stderr) процесса возможны взаимоблокировки. Понимание причин этого крайне важно для понимания сути новинок. Создадим приложение, которое пытается прочитать все выходные данные и ошибки процесса.
Прежде всего, нужно перенаправить stdout и stderr, чтобы иметь возможность их прочитать. Это делается путём установки свойств RedirectStandardOutput и RedirectStandardError объекта ProcessStartInfo в true. Перед запуском процесса создаются два выделенных канала (для вывода и для ошибок). Дочерний процесс записывает данные в свои stdout и stderr, как обычно, но вместо вывода в консоль данные записываются в каналы.
ProcessStartInfo startInfo = new("dotnet", "--help")
{
RedirectStandardOutput = true,
RedirectStandardError = true
};
using Process p = new() { StartInfo = startInfo };Каналы имеют ограниченный размер буфера (обычно 4КБ в Windows и 64КБ в Unix). Когда производитель (в нашем случае, дочерний процесс) записывает данные в канал, они сохраняются в буфере до тех пор, пока их не прочтёт потребитель (родительский процесс). Если производитель записывает больше данных, чем размер буфера, а потребитель одновременно не читает из канала, производитель будет заблокирован на операции записи, ожидая, пока потребитель прочитает из канала и освободит место в буфере.
Если потребитель ожидает завершения работы производителя (например, вызывая WaitForExit) без чтения из канала, он будет заблокирован, как только производитель заполнит буфер:
p.Start();
p.WaitForExit();
var output = p.StandardOutput.ReadToEnd();
var error = p.StandardError.ReadToEnd();
Перемещение
process.WaitForExit(); в конец тоже не помогает. ReadToEnd — блокирующий вызов: он читает до тех пор, пока поток не достигнет конца файла (EOF), что происходит только тогда, когда дочерний процесс закрывает свою часть канала (обычно при завершении). Поэтому в приведённом выше коде мы сначала блокируемся на stdout до завершения дочернего процесса, и только затем начинаем читать stderr. Если дочерний процесс записывает в stderr больше, чем может вместить буфер канала, он блокируется на своей записи — и мы застреваем во взаимоблокировке.Причина в том, что мы читаем два потока последовательно, а не одновременно. Чтобы избежать этого, нужно одновременно очищать и стандартный вывод, и ошибку. До сих пор у нас было два варианта:
1. Использовать асинхронные операции чтения для StandardOutput и StandardError
p.Start();
var outTask = p.StandardOutput.ReadToEndAsync();
var errTask = process.StandardError.ReadToEndAsync();
await Task.WhenAll(outTask, errTask, p.WaitForExitAsync());
var output = await outTask;
var error = await errTask;
2. Использовать события OutputDataReceived и ErrorDataReceived
События возникают при записи строки в stdout и stderr соответственно:
StringBuilder stdOut = new(), stdErr = new();
p.OutputDataReceived += (sender, e) => stdOut.AppendLine(e.Data);
p.ErrorDataReceived += (sender, e) => stdErr.AppendLine(e.Data);
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();
p.WaitForExit();
Существующие API не оптимальны с точки зрения простоты и производительности.
Продолжение следует…
Источник: https://devblogs.microsoft.com/dotnet/process-api-improvements-in-dotnet-11/
👍8
День 2667. #ЧтоНовенького #NET11
Улучшения API процессов в .NET 11. Продолжение
Обзор
Проблемы существующего API
Чтобы решить проблему, описанную в предыдущем посте, были добавлены несколько новых функций.
Process.ReadAllText и Process.ReadAllTextAsync
Одновременно обрабатывают стандартный вывод и ошибки, что помогает избежать взаимоблокировок. Они декодируют вывод, используя кодировку, указанную в ProcessStartInfo.Standard[Output/Error]Encoding (или кодировку по умолчанию, если она не указана), и возвращают результат в виде строки. Теперь код с чтением вывода и ошибок процесса стал намного проще:
Process.RunAndCaptureText и Process.RunAndCaptureTextAsync
Поскольку код выше – очень распространенная практика, эти методы производят запуск процесса, чтение всех выходных данных и ошибок и ожидание завершения процесса в одном вызове:
Process.Run и Process.RunAsync
Не захватывают вывод и ошибки, а просто ждут завершения:
Process.ReadAllLines и Process.ReadAllLinesAsync
Пригодятся, если нужно получать вывод и ошибки в виде набора строк. Возвращают перечислимый объект ProcessOutputLine:
Process.ReadAllBytes и Process.ReadAllBytesAsync
Позволяют получить вывод и ошибки в виде массивов байт.
Тайм-ауты и отмена
Все вышеупомянутые методы поддерживают тайм-ауты и отмену. Если тайм-аут достигнут или токен отмены отменён до достижения конца потока, методы выбросят исключение TimeoutException или OperationCanceledException соответственно. Методы верхнего уровня RunAndCaptureText[Async] и Run[Async] также попытаются завершить процесс.
Мультиплексирование и другие оптимизации «под капотом»
Новые методы не только проще в использовании, но и быстрее. В фоновом режиме синхронные методы Process.RunAndCaptureText и Process.ReadAll[Bytes/Text] используют мультиплексирование (poll в Unix и WaitForMultipleObjects в Windows) для чтения как из stdout, так и из stderr с использованием одного потока. Они также реализуют ряд других оптимизаций, таких как использование ArrayPool для уменьшения выделения памяти. Асинхронные методы Process.RunAndCaptureTextAsync и Process.ReadAllTextAsync используют асинхронные операции ввода-вывода без блокировки каких-либо потоков.
Окончание следует…
Источник: https://devblogs.microsoft.com/dotnet/process-api-improvements-in-dotnet-11/
Улучшения API процессов в .NET 11. Продолжение
Обзор
Проблемы существующего API
Чтобы решить проблему, описанную в предыдущем посте, были добавлены несколько новых функций.
Process.ReadAllText и Process.ReadAllTextAsync
Одновременно обрабатывают стандартный вывод и ошибки, что помогает избежать взаимоблокировок. Они декодируют вывод, используя кодировку, указанную в ProcessStartInfo.Standard[Output/Error]Encoding (или кодировку по умолчанию, если она не указана), и возвращают результат в виде строки. Теперь код с чтением вывода и ошибок процесса стал намного проще:
ProcessStartInfo startInfo = new("dotnet", "--help")
{
RedirectStandardOutput = true,
RedirectStandardError = true
};
using Process p = new() { StartInfo = startInfo };
p.Start();
(string output, string error) = p.ReadAllText();
p.WaitForExit();Process.RunAndCaptureText и Process.RunAndCaptureTextAsync
Поскольку код выше – очень распространенная практика, эти методы производят запуск процесса, чтение всех выходных данных и ошибок и ожидание завершения процесса в одном вызове:
ProcessTextOutput output =
Process.RunAndCaptureText("dotnet", ["--help"]);
Process.Run и Process.RunAsync
Не захватывают вывод и ошибки, а просто ждут завершения:
ProcessExitStatus status = Process.Run("dotnet", ["build", "-c", "Release"]);Process.ReadAllLines и Process.ReadAllLinesAsync
Пригодятся, если нужно получать вывод и ошибки в виде набора строк. Возвращают перечислимый объект ProcessOutputLine:
using Process p = Process.Start(…);
await foreach (var line in p.ReadAllLinesAsync())
{
if (line.StandardError)
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(line.Content);
Console.ResetColor();
}
Process.ReadAllBytes и Process.ReadAllBytesAsync
Позволяют получить вывод и ошибки в виде массивов байт.
Тайм-ауты и отмена
Все вышеупомянутые методы поддерживают тайм-ауты и отмену. Если тайм-аут достигнут или токен отмены отменён до достижения конца потока, методы выбросят исключение TimeoutException или OperationCanceledException соответственно. Методы верхнего уровня RunAndCaptureText[Async] и Run[Async] также попытаются завершить процесс.
Мультиплексирование и другие оптимизации «под капотом»
Новые методы не только проще в использовании, но и быстрее. В фоновом режиме синхронные методы Process.RunAndCaptureText и Process.ReadAll[Bytes/Text] используют мультиплексирование (poll в Unix и WaitForMultipleObjects в Windows) для чтения как из stdout, так и из stderr с использованием одного потока. Они также реализуют ряд других оптимизаций, таких как использование ArrayPool для уменьшения выделения памяти. Асинхронные методы Process.RunAndCaptureTextAsync и Process.ReadAllTextAsync используют асинхронные операции ввода-вывода без блокировки каких-либо потоков.
Окончание следует…
Источник: https://devblogs.microsoft.com/dotnet/process-api-improvements-in-dotnet-11/
👍4
День 2668. #ЧтоНовенького #NET11
Улучшения API процессов в .NET 11. Окончание
Обзор
Проблемы существующего API
Разбор новых методов
Управление жизненным циклом
Process.StartAndForget
Распространено заблуждение, что при очистке переменной процесса, процесс также завершается. Это не так, поскольку Process.Dispose только освобождает ресурсы, связанные с процессом, но не завершает его.
Чтобы упростить запуск процесса без необходимости беспокоиться о его завершении, добавлен метод Process.StartAndForget, который запускает процесс, возвращает его ID и освобождает все связанные с ним ресурсы.
ProcessStartInfo.KillOnParentExit
Процессы, запущенные родительским процессом, не завершаются автоматически при завершении родительского процесса. Это может привести к появлению «осиротевших» процессов, продолжающих работать в фоновом режиме, что нежелательно во многих сценариях. Для решения этой проблемы добавлено свойство ProcessStartInfo.KillOnParentExit, которое гарантирует завершение дочернего процесса при завершении родительского процесса (включая случаи принудительного завершения и сбоев).
Это достигается за счёт использования специфических для платформы функций, таких как JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE в Windows и PR_SET_PDEATHSIG в Linux и Android. В отличие от других API, поведение немного отличается на разных платформах:
- В Windows используется объект Job, чтобы гарантировать завершение дочернего процесса при завершении родительского процесса. Объекты Job по умолчанию наследуются всеми дочерними процессами, поэтому, если дочерний процесс порождает другой процесс (внука), этот внук также будет завершён при завершении родительского процесса.
- В Linux и Android используется PR_SET_PDEATHSIG для указания сигнала SIGKILL, который ядро отправит дочернему процессу при завершении потока, создавшего процесс. Поскольку как потоки пула потоков, так и пользовательские потоки могут быть завершены в любое время, поддерживается выделенный поток, используемый только для порождения процессов с включенным KillOnParentExit, чтобы гарантировать завершение дочерних процессов при завершении родительского процесса. Таким образом, когда запускается несколько процессов с KillOnParentExit, используется механизм синхронизации, гарантирующий, что выделенный поток запускает только один процесс за раз.
ProcessStartInfo.StartDetached
Свойство ProcessStartInfo.StartDetached позволяет запустить процесс, отсоединённый от родительского процесса, что означает, что он будет продолжать работать, даже если родительский процесс завершится, получит сигнал или будет закрыт терминал. Это достигается с помощью платформенно-специфических функций, таких как флаг DETACHED_PROCESS в Windows и setsid в Unix (PR).
Более того, если StartDetached установлено в true и не указано перенаправление для стандартных обработчиков выводы, они будут перенаправлены на нулевой дескриптор, чтобы избежать ненужного удержания родительских стандартных обработчиков открытыми.
Источник: https://devblogs.microsoft.com/dotnet/process-api-improvements-in-dotnet-11/
Улучшения API процессов в .NET 11. Окончание
Обзор
Проблемы существующего API
Разбор новых методов
Управление жизненным циклом
Process.StartAndForget
Распространено заблуждение, что при очистке переменной процесса, процесс также завершается. Это не так, поскольку Process.Dispose только освобождает ресурсы, связанные с процессом, но не завершает его.
Чтобы упростить запуск процесса без необходимости беспокоиться о его завершении, добавлен метод Process.StartAndForget, который запускает процесс, возвращает его ID и освобождает все связанные с ним ресурсы.
int processId = Process.StartAndForget("notepad.exe");ProcessStartInfo.KillOnParentExit
Процессы, запущенные родительским процессом, не завершаются автоматически при завершении родительского процесса. Это может привести к появлению «осиротевших» процессов, продолжающих работать в фоновом режиме, что нежелательно во многих сценариях. Для решения этой проблемы добавлено свойство ProcessStartInfo.KillOnParentExit, которое гарантирует завершение дочернего процесса при завершении родительского процесса (включая случаи принудительного завершения и сбоев).
Это достигается за счёт использования специфических для платформы функций, таких как JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE в Windows и PR_SET_PDEATHSIG в Linux и Android. В отличие от других API, поведение немного отличается на разных платформах:
- В Windows используется объект Job, чтобы гарантировать завершение дочернего процесса при завершении родительского процесса. Объекты Job по умолчанию наследуются всеми дочерними процессами, поэтому, если дочерний процесс порождает другой процесс (внука), этот внук также будет завершён при завершении родительского процесса.
- В Linux и Android используется PR_SET_PDEATHSIG для указания сигнала SIGKILL, который ядро отправит дочернему процессу при завершении потока, создавшего процесс. Поскольку как потоки пула потоков, так и пользовательские потоки могут быть завершены в любое время, поддерживается выделенный поток, используемый только для порождения процессов с включенным KillOnParentExit, чтобы гарантировать завершение дочерних процессов при завершении родительского процесса. Таким образом, когда запускается несколько процессов с KillOnParentExit, используется механизм синхронизации, гарантирующий, что выделенный поток запускает только один процесс за раз.
ProcessStartInfo.StartDetached
Свойство ProcessStartInfo.StartDetached позволяет запустить процесс, отсоединённый от родительского процесса, что означает, что он будет продолжать работать, даже если родительский процесс завершится, получит сигнал или будет закрыт терминал. Это достигается с помощью платформенно-специфических функций, таких как флаг DETACHED_PROCESS в Windows и setsid в Unix (PR).
Более того, если StartDetached установлено в true и не указано перенаправление для стандартных обработчиков выводы, они будут перенаправлены на нулевой дескриптор, чтобы избежать ненужного удержания родительских стандартных обработчиков открытыми.
Источник: https://devblogs.microsoft.com/dotnet/process-api-improvements-in-dotnet-11/
👍2
День 2669. #ЗаметкиНаПолях #Blazor
Создаём Базовый Компонент для Всех Компонентов в Blazor
При разработке Blazor-приложения может понадобиться пользовательский базовый компонент для всех остальных компонентов. Это полезно для совместного использования общих функций, таких как токены отмены, логирование или управление состоянием, во всех компонентах. Вместо добавления
Создадим файл
Файл
Пример: CustomComponentBase с CancellationToken
Вот пример базового компонента, который предоставляет CancellationToken всем производным компонентам. Это полезно для отмены асинхронных операций при удалении компонента:
Теперь все наши компоненты могут получить доступ к свойству CancellationToken без какой-либо дополнительной настройки:
Переопределение базового компонента для конкретных компонентов
Если конкретному компоненту требуется другой базовый компонент или вообще никакой, вы можете объявить @inherits непосредственно в файле этого компонента. Явное объявление имеет приоритет над _Imports.razor:
Организация файлов _Imports.razor
Можно иметь несколько файлов
Источник: https://www.meziantou.net/blazor-how-to-set-a-base-component-for-all-razor-components-using-viewstart-razo.htm
Создаём Базовый Компонент для Всех Компонентов в Blazor
При разработке Blazor-приложения может понадобиться пользовательский базовый компонент для всех остальных компонентов. Это полезно для совместного использования общих функций, таких как токены отмены, логирование или управление состоянием, во всех компонентах. Вместо добавления
@inherits YourBaseComponent в каждый файл Razor, вы можете использовать файл _Imports.razor для глобальной установки базового компонента.Создадим файл
_Imports.razor в папке, к компонентам которой нужно применить базовый компонент. Все файлы Razor в этой папке и её подпапках будут наследовать указанный базовый компонент.@inherits YourNamespace.CustomComponentBase
Файл
_Imports.razor обрабатывается перед любым компонентом Razor в том же каталоге или его подкаталогах. Все компоненты затем автоматически наследуют от CustomComponentBase без необходимости объявлять @inherits в каждом файле.Пример: CustomComponentBase с CancellationToken
Вот пример базового компонента, который предоставляет CancellationToken всем производным компонентам. Это полезно для отмены асинхронных операций при удалении компонента:
@* CustomComponentBase.razor (Razor) *@
@implements IDisposable
@code {
private readonly CancellationTokenSource
_cts = new CancellationTokenSource();
public CancellationToken
CancellationToken => _cts.Token;
public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
}
}
Теперь все наши компоненты могут получить доступ к свойству CancellationToken без какой-либо дополнительной настройки:
@* MyComponent.razor (Razor) *@
@* Не нужно использовать @inherits, т.к. _Imports.razor импортируется автоматически *@
<h3>Мой компонент</h3>
@code {
protected override async Task OnInitializedAsync()
{
// Используем CancellationToken из базового компонента
await LoadDataAsync(CancellationToken);
}
private async Task LoadDataAsync(CancellationToken ct)
{
// … асинхронный код …
await Task.Delay(1000, ct);
}
}
Переопределение базового компонента для конкретных компонентов
Если конкретному компоненту требуется другой базовый компонент или вообще никакой, вы можете объявить @inherits непосредственно в файле этого компонента. Явное объявление имеет приоритет над _Imports.razor:
@* MyComponent.razor (Razor) *@
@inherits ComponentBase
@* Этот компонент будет использовать ComponentBase вместо CustomComponentBase *@
Организация файлов _Imports.razor
Можно иметь несколько файлов
_Imports.razor в разных папках, чтобы применять разные базовые компоненты к различным разделам приложения. Приоритет имеет ближайший файл _Imports.razor в иерархии каталогов. Например, /Components/_Imports.razor применяется ко всем компонентам в этой папке /Components/Admin/_Imports.razor применяется специально к компонентам папки Admin. Такой иерархический подход обеспечивает точный контроль над тем, какие компоненты наследуют от каких базовых классов.Источник: https://www.meziantou.net/blazor-how-to-set-a-base-component-for-all-razor-components-using-viewstart-razo.htm
👍4👎1
День 2670. #ЧтоНовенького #NET11
Сжатие Zstandard в .NET 11
В .NET уже некоторое время существуют DeflateStream, GZipStream, ZLibStream и BrotliStream. В .NET 11 к ним присоединяется новый: ZstandardStream.
Zstandard (или Zstd) — это алгоритм сжатия от Facebook. Разработан для обеспечения коэффициента сжатия, сравнимого с алгоритмом DEFLATE, но более быстрого, особенно для декомпрессии. Он настраивается с уровнями сжатия от -7 (самый быстрый) до 22 (самый медленный по скорости сжатия, но с наилучшим коэффициентом сжатия). Zstd используется в ядре Linux, дампах памяти FreeBSD, AWS Redshift, менеджерах пакетов Fedora и ArchLinux, а также архивах игр Nintendo Switch.
Новый API
Дизайн соответствует существующему шаблону Brotli*, поэтому, если вы использовали BrotliStream или BrotliEncoder, он покажется вам знакомым.
В System.IO.Compression 4 основных новых типа:
- ZstandardStream — обёртка потока,
- ZstandardEncoder/ZstandardDecoder — кодировщик/декодировщик,
- ZstandardDictionary — для эффективного сжатия множества маленьких файлов,
- ZstandardCompressionOptions — настройки: качество, размер окна, контрольная сумма и т.п.
Простейший пример применения:
Если не требуется потоковая обработка, есть API проще:
Уровни качества
Zstd имеет диапазон качества от ZSTD_minCLevel() (который может быть отрицательным для сверхбыстрых режимов) до 22, по умолчанию 3. CompressionLevel.Fastest - минимальное значение, CompressionLevel.SmallestSize — 22.
Использование в ASP.NET Core
ZstandardCompressionProvider автоматически подключает заголовок согласования содержимого
Сжатие по словарю
При сжатии большого количества небольших, похожих данных (ответы JSON API, сериализованные события, блоки конфигурации) общий словарь может улучшить коэффициент сжатия.
Храните словарь в месте, доступном как производителю, так и потребителю. Рекомендуемый размер — до 100 КБ, обучение проводится на репрезентативных данных, размер которых примерно в 100 раз превышает размер сжимаемых данных.
Источник: https://steven-giesel.com/blogPost/6066abb6-640a-4225-ac33-3f4d5a1a1d16/zstandard-compression-in-net-11
Сжатие Zstandard в .NET 11
В .NET уже некоторое время существуют DeflateStream, GZipStream, ZLibStream и BrotliStream. В .NET 11 к ним присоединяется новый: ZstandardStream.
Zstandard (или Zstd) — это алгоритм сжатия от Facebook. Разработан для обеспечения коэффициента сжатия, сравнимого с алгоритмом DEFLATE, но более быстрого, особенно для декомпрессии. Он настраивается с уровнями сжатия от -7 (самый быстрый) до 22 (самый медленный по скорости сжатия, но с наилучшим коэффициентом сжатия). Zstd используется в ядре Linux, дампах памяти FreeBSD, AWS Redshift, менеджерах пакетов Fedora и ArchLinux, а также архивах игр Nintendo Switch.
Новый API
Дизайн соответствует существующему шаблону Brotli*, поэтому, если вы использовали BrotliStream или BrotliEncoder, он покажется вам знакомым.
В System.IO.Compression 4 основных новых типа:
- ZstandardStream — обёртка потока,
- ZstandardEncoder/ZstandardDecoder — кодировщик/декодировщик,
- ZstandardDictionary — для эффективного сжатия множества маленьких файлов,
- ZstandardCompressionOptions — настройки: качество, размер окна, контрольная сумма и т.п.
Простейший пример применения:
// сжатие
using var output = new MemoryStream();
using (var zstd =
new ZstandardStream(output, CompressionMode.Compress))
{
await inputStream.CopyToAsync(zstd);
}
// декомпрессия
using var zstd = new
ZstandardStream(compressedStream, CompressionMode.Decompress);
await zstd.CopyToAsync(outputStream);
Если не требуется потоковая обработка, есть API проще:
byte[] src = File.ReadAllBytes("data.bin");
// сжатие
int maxLen = ZstandardEncoder
.GetMaxCompressedLength(src.Length);
var compressed = new byte[maxLen];
ZstandardEncoder.TryCompress(src, compressed, out int bytesWritten);
// декомпрессия
int decompressedSize = ZstandardDecoder.GetMaxDecompressedLength(compressed.AsSpan(0, bytesWritten));
var decompressed = new byte[decompressedSize];
ZstandardDecoder.TryDecompress(compressed.AsSpan(0, bytesWritten), decompressed, out int bytesRead);Уровни качества
Zstd имеет диапазон качества от ZSTD_minCLevel() (который может быть отрицательным для сверхбыстрых режимов) до 22, по умолчанию 3. CompressionLevel.Fastest - минимальное значение, CompressionLevel.SmallestSize — 22.
var options = new ZstandardCompressionOptions
{
Quality = 6
};
using var zstd = new ZstandardStream(output, options);
Использование в ASP.NET Core
// Program.cs
builder.Services.AddResponseCompression(x =>
{
x.EnableForHttps = true;
x.Providers.Add<ZstandardCompressionProvider>();
});
ZstandardCompressionProvider автоматически подключает заголовок согласования содержимого
encoding: zstd. Распаковка запроса также осуществляется простым способом через IDecompressionProvider.Сжатие по словарю
При сжатии большого количества небольших, похожих данных (ответы JSON API, сериализованные события, блоки конфигурации) общий словарь может улучшить коэффициент сжатия.
// обучаем словарь на примерах
byte[] allSamples = …; // примеры всех сжимаемых объектов
int[] lengths = …; // длина каждого примера
using ZstandardDictionary dict = ZstandardDictionary.TrainFromSamples(allSamples, lengths, maxDictionarySize: 100_000);
// Сжатие по словарю — обе стороны должны использовать один словарь
using var encoder = new ZstandardEncoder(dict);
encoder.Compress(payload, compressed, out int consumed, out int written, isFinalBlock: true);
Храните словарь в месте, доступном как производителю, так и потребителю. Рекомендуемый размер — до 100 КБ, обучение проводится на репрезентативных данных, размер которых примерно в 100 раз превышает размер сжимаемых данных.
Источник: https://steven-giesel.com/blogPost/6066abb6-640a-4225-ac33-3f4d5a1a1d16/zstandard-compression-in-net-11
1👍7
День 2671. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.
34. Лучшие практики RESTful-сервисов
«Давайте обсудим лучшие практики проектирования и реализации RESTful-сервисов в приложениях .NET. Приведите примеры того, как эти практики обеспечивают эффективные и поддерживаемые API-сервисы».
Хороший ответ
Разработка RESTful-сервисов требует соблюдения определённых принципов, обеспечивающих масштабируемость, удобство сопровождения и интуитивность этих сервисов. При реализации RESTful API в приложениях .NET можно следовать нескольким передовым практикам:
1. Правильное использование HTTP-методов: Каждый HTTP-метод (GET, POST, PUT, DELETE и т. д.) имеет определённое семантическое значение и должен использоваться в соответствии с представляемым действием. Например, GET следует использовать для получения данных, и он должен быть идемпотентным, то есть не изменять состояние сервера.
2. Именование ресурсов: Ресурсы должны быть названы логично и последовательно, как правило, с использованием существительных, представляющих сущности. Пути URI должны отражать иерархию ресурсов. Например:
3. Без сохранения состояния: Каждый запрос от клиента к серверу должен содержать всю информацию, необходимую серверу для понимания запроса, без использования какого-либо сохранённого контекста на сервере. Это делает API масштабируемым и упрощает управление при высокой нагрузке.
4. Правильное использование кодов состояния: HTTP предоставляет ряд кодов ответа, указывающих на успех или неудачу запросов. Правильное использование этих кодов, таких как 200 OK, 404 Not Found и 500 Internal Server Error, помогает клиенту корректно обрабатывать ответы.
5. Версионирование: По мере развития API версионирование становится критически важным для поддержания совместимости с существующими клиентами, позволяя при этом вносить улучшения и изменения. Этого можно достичь с помощью пути URI, строки запроса или пользовательских заголовков, например:
6. Безопасность: Реализация аутентификации и авторизации, Обеспечение шифрования данных при передаче (HTTPS) является основополагающим принципом безопасности RESTful API.
7. Документация и гипермедиа: Хорошая документация имеет решающее значение для разработчиков, чтобы они могли понимать и эффективно использовать API. Использование гипермедиа (HATEOAS) может динамически направлять пользователей по операциям API.
Часто встречающийся неверный ответ
«Надо просто убедиться, что API работает хорошо и возвращает правильные данные. Конкретные HTTP-методы или коды состояния, которые вы используете, не так уж важны, главное, чтобы клиент получал то, что ему нужно».
Почему это неверно
- Недостаточное понимание принципов REST: ответ демонстрирует непонимание фундаментальных принципов REST, таких как важность использования правильных HTTP-методов и кодов состояния, которые определяют RESTful-сервисы.
- Недооценка лучших практик: ответ недооценивает важность лучших практик, таких как отсутствие состояния, именование ресурсов и безопасность, которые имеют решающее значение для масштабируемости, удобства сопровождения и безопасности API.
- Пренебрежение клиентским опытом: Несоблюдение RESTful-соглашений может привести к созданию API, который будет сложнее использовать и интегрировать для клиентов, что может повлиять на внедрение и удобство использования API.
Этот часто встречающийся неверный ответ может быть следствием недостаточного формального понимания веб-сервисов или опыта работы в средах, где приоритет отдавался быстрым результатам, а не соблюдению стандартов и лучших практик.
Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.
34. Лучшие практики RESTful-сервисов
«Давайте обсудим лучшие практики проектирования и реализации RESTful-сервисов в приложениях .NET. Приведите примеры того, как эти практики обеспечивают эффективные и поддерживаемые API-сервисы».
Хороший ответ
Разработка RESTful-сервисов требует соблюдения определённых принципов, обеспечивающих масштабируемость, удобство сопровождения и интуитивность этих сервисов. При реализации RESTful API в приложениях .NET можно следовать нескольким передовым практикам:
1. Правильное использование HTTP-методов: Каждый HTTP-метод (GET, POST, PUT, DELETE и т. д.) имеет определённое семантическое значение и должен использоваться в соответствии с представляемым действием. Например, GET следует использовать для получения данных, и он должен быть идемпотентным, то есть не изменять состояние сервера.
2. Именование ресурсов: Ресурсы должны быть названы логично и последовательно, как правило, с использованием существительных, представляющих сущности. Пути URI должны отражать иерархию ресурсов. Например:
GET /api/customers - получить список клиентов,GET /api/customers/{id} - получить клиента по ID,POST /api/customers - создать клиента,PUT /api/customers/{id} - обновить данные клиента,DELETE /api/customers/{id} - удалить данные клиента.3. Без сохранения состояния: Каждый запрос от клиента к серверу должен содержать всю информацию, необходимую серверу для понимания запроса, без использования какого-либо сохранённого контекста на сервере. Это делает API масштабируемым и упрощает управление при высокой нагрузке.
4. Правильное использование кодов состояния: HTTP предоставляет ряд кодов ответа, указывающих на успех или неудачу запросов. Правильное использование этих кодов, таких как 200 OK, 404 Not Found и 500 Internal Server Error, помогает клиенту корректно обрабатывать ответы.
5. Версионирование: По мере развития API версионирование становится критически важным для поддержания совместимости с существующими клиентами, позволяя при этом вносить улучшения и изменения. Этого можно достичь с помощью пути URI, строки запроса или пользовательских заголовков, например:
/api/v1/customers, /api/v2/customers6. Безопасность: Реализация аутентификации и авторизации, Обеспечение шифрования данных при передаче (HTTPS) является основополагающим принципом безопасности RESTful API.
7. Документация и гипермедиа: Хорошая документация имеет решающее значение для разработчиков, чтобы они могли понимать и эффективно использовать API. Использование гипермедиа (HATEOAS) может динамически направлять пользователей по операциям API.
Часто встречающийся неверный ответ
«Надо просто убедиться, что API работает хорошо и возвращает правильные данные. Конкретные HTTP-методы или коды состояния, которые вы используете, не так уж важны, главное, чтобы клиент получал то, что ему нужно».
Почему это неверно
- Недостаточное понимание принципов REST: ответ демонстрирует непонимание фундаментальных принципов REST, таких как важность использования правильных HTTP-методов и кодов состояния, которые определяют RESTful-сервисы.
- Недооценка лучших практик: ответ недооценивает важность лучших практик, таких как отсутствие состояния, именование ресурсов и безопасность, которые имеют решающее значение для масштабируемости, удобства сопровождения и безопасности API.
- Пренебрежение клиентским опытом: Несоблюдение RESTful-соглашений может привести к созданию API, который будет сложнее использовать и интегрировать для клиентов, что может повлиять на внедрение и удобство использования API.
Этот часто встречающийся неверный ответ может быть следствием недостаточного формального понимания веб-сервисов или опыта работы в средах, где приоритет отдавался быстрым результатам, а не соблюдению стандартов и лучших практик.
Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👎2
День 2672. #Оффтоп
Построй Свой Собственный X
Обнаружил интересный ресурс для тех, кто любит переизобретать велосипед. Хотя, не будем себя обманывать, все мы иногда нет-нет, да и да. Так вот, если вам когда-нибудь приходила в голову идея создать свою операционную систему, движок виртуальной машины, базу данных, докер, поисковый движок, 3D-рендерер, Git, командную строку или хотя бы Тетрис, этот ресурс для вас.
Здесь собраны десятки (если не сотни) руководств по созданию известных нам всем вещей (в самом простом варианте, естественно) на самых разных языках программирования, в том числе на C#. Смысл, конечно, не в том, чтобы помочь вам написать новый Google и сказочно разбогатеть (хотя, кто знает). Смысл в том, чтобы объяснить, как это устроено, и в процессе научить вас создавать вещи.
Единственный минус – всё на английском.
https://build-your-own-x.vercel.app/
Построй Свой Собственный X
«Чего я не могу создать, того я не понимаю»
- Ричард Фейнман
Обнаружил интересный ресурс для тех, кто любит переизобретать велосипед. Хотя, не будем себя обманывать, все мы иногда нет-нет, да и да. Так вот, если вам когда-нибудь приходила в голову идея создать свою операционную систему, движок виртуальной машины, базу данных, докер, поисковый движок, 3D-рендерер, Git, командную строку или хотя бы Тетрис, этот ресурс для вас.
Здесь собраны десятки (если не сотни) руководств по созданию известных нам всем вещей (в самом простом варианте, естественно) на самых разных языках программирования, в том числе на C#. Смысл, конечно, не в том, чтобы помочь вам написать новый Google и сказочно разбогатеть (хотя, кто знает). Смысл в том, чтобы объяснить, как это устроено, и в процессе научить вас создавать вещи.
Единственный минус – всё на английском.
https://build-your-own-x.vercel.app/
build-your-own-x.vercel.app
Build Your Own X
This repo is forked from awesome anistefanovic/build-your-own-x ,
site is created and maintained by Kalan which converted github README.md to
website, and probably will add more opinionated resource. Follow me on
Twitter or contrinute your article…
site is created and maintained by Kalan which converted github README.md to
website, and probably will add more opinionated resource. Follow me on
Twitter or contrinute your article…
👍14
День 2673. #ЗаметкиНаПолях
Как Масштабировать Длительные Запросы API
В каждой системе рано или поздно возникает конечная точка, выполнение которой занимает минуты (или даже больше). В итоге пользователи несколько минут ждут ответа, а API удерживает запрос открытым всё это время, расходуя поток, соединение и прочие ресурсы. Небольшой всплеск трафика на этой одной конечной точке потенциально может привести к сбою всего API. Рассмотрим последовательность действий для решения этой проблемы.
0. Наивная версия
Оставим всё, как есть. В этом подходе нет ничего плохого — просто за корректность приходится платить доступностью. Пользовательский опыт плох, а радиус поражения велик: каждый принятый длительный запрос — это заблокированные ресурсы, которые могли бы быть потрачены на обработку других запросов.
1. Принимаем работу, но не выполняем её
Добавим таблицу заданий для работы, которую нужно выполнить. Теперь API:
- Проверяет запрос.
- Вставляет строку в таблицу заданий со статусом «Ожидание».
- Возвращает
Фоновый обработчик внутри того же API, читает строки со статусом «Ожидание» и обрабатывает их. Клиент либо опрашивает конечную точку
Это уже даёт много преимуществ. Конечная точка быстро возвращает результат, а всплеск входящих запросов просто превращается во всплеск строк в таблице. Запись в таблицу обходится дёшево.
2. Отделение обработчика от API
Фоновый обработчик из шага 1 по-прежнему конкурирует с API за процессор, память и пул соединений. Если обработка становится ресурсоёмкой или медленной, API начинает это ощущать. Решение в том, чтобы вынести фоновый обработчик в отдельно развёртываемый компонент и разместить между ними очередь.
API публикует сообщение в очередь. Пул фоновых обработчиков получает данные из очереди и выполняет фактическую работу.
- Очередь поглощает пиковые нагрузки. API может продолжать принимать задачи с постоянной скоростью, в то время как рабочие процессы выполняют задания в своём темпе.
- Вы масштабируете рабочие процессы отдельно от API. Рост числа фоновых задач не требует увеличения количества экземпляров API.
- Сбой рабочего процесса — просто сообщение, которое возвращается в очередь, а не ошибка 500 для пользователя.
Вы также получаете возможность повторных попыток, приостановку/возобновление, структурированную обработку ошибок и очередь недоставленных сообщений — фактически бесплатно, поскольку инфраструктура очередей уже предоставляет их.
Чего это стоит
Это дополнительные компоненты, которые нужно развёртывать и отслеживать. Теперь клиент должен спрашивать, готов ли ответ, или вы должны его оповещать. И каждая задача должна быть идемпотентной, потому что доставка «хотя-бы-раз» означает, что рабочие процессы будут видеть дубликаты.
Если у вас только одна медленная конечная точка и умеренный трафик, это избыточно. Простое «запустить и забыть» с опросом статуса внутри того же процесса вполне подойдет.
Облачный сервис
Не обязательно делать всё самому. Вот несколько альтернатив:
- AWS SQS + Lambda или Azure Service Bus + Azure Functions, когда нужно масштабировать пул рабочих процессов до нуля и не охота управлять хостами.
- Azure Durable Functions или AWS Step Functions - для многоэтапных рабочих процессов с таймерами, повторными попытками и подтверждением человека. Оркестрация - их сильная сторона.
- Temporal — когда рабочий процесс длится долго (часы, дни), и необходимо первоклассное стабильное выполнение, версионирование и прозрачность между запусками.
Компромисс, как обычно, заключается в меньшем объёме операционной работы, но большей зависимости от поставщика и ценообразовании, которое необходимо тщательно оценить при росте нагрузки.
Источник: https://www.milanjovanovic.tech/blog/how-to-scale-long-running-api-requests
Как Масштабировать Длительные Запросы API
В каждой системе рано или поздно возникает конечная точка, выполнение которой занимает минуты (или даже больше). В итоге пользователи несколько минут ждут ответа, а API удерживает запрос открытым всё это время, расходуя поток, соединение и прочие ресурсы. Небольшой всплеск трафика на этой одной конечной точке потенциально может привести к сбою всего API. Рассмотрим последовательность действий для решения этой проблемы.
0. Наивная версия
Оставим всё, как есть. В этом подходе нет ничего плохого — просто за корректность приходится платить доступностью. Пользовательский опыт плох, а радиус поражения велик: каждый принятый длительный запрос — это заблокированные ресурсы, которые могли бы быть потрачены на обработку других запросов.
1. Принимаем работу, но не выполняем её
Добавим таблицу заданий для работы, которую нужно выполнить. Теперь API:
- Проверяет запрос.
- Вставляет строку в таблицу заданий со статусом «Ожидание».
- Возвращает
202 Accepted с ID задания.Фоновый обработчик внутри того же API, читает строки со статусом «Ожидание» и обрабатывает их. Клиент либо опрашивает конечную точку
GET /jobs/{id}, либо — что лучше — сервер отправляет обновления через SignalR, Server-Sent Events или email, когда задание выполнено.Это уже даёт много преимуществ. Конечная точка быстро возвращает результат, а всплеск входящих запросов просто превращается во всплеск строк в таблице. Запись в таблицу обходится дёшево.
2. Отделение обработчика от API
Фоновый обработчик из шага 1 по-прежнему конкурирует с API за процессор, память и пул соединений. Если обработка становится ресурсоёмкой или медленной, API начинает это ощущать. Решение в том, чтобы вынести фоновый обработчик в отдельно развёртываемый компонент и разместить между ними очередь.
API публикует сообщение в очередь. Пул фоновых обработчиков получает данные из очереди и выполняет фактическую работу.
- Очередь поглощает пиковые нагрузки. API может продолжать принимать задачи с постоянной скоростью, в то время как рабочие процессы выполняют задания в своём темпе.
- Вы масштабируете рабочие процессы отдельно от API. Рост числа фоновых задач не требует увеличения количества экземпляров API.
- Сбой рабочего процесса — просто сообщение, которое возвращается в очередь, а не ошибка 500 для пользователя.
Вы также получаете возможность повторных попыток, приостановку/возобновление, структурированную обработку ошибок и очередь недоставленных сообщений — фактически бесплатно, поскольку инфраструктура очередей уже предоставляет их.
Чего это стоит
Это дополнительные компоненты, которые нужно развёртывать и отслеживать. Теперь клиент должен спрашивать, готов ли ответ, или вы должны его оповещать. И каждая задача должна быть идемпотентной, потому что доставка «хотя-бы-раз» означает, что рабочие процессы будут видеть дубликаты.
Если у вас только одна медленная конечная точка и умеренный трафик, это избыточно. Простое «запустить и забыть» с опросом статуса внутри того же процесса вполне подойдет.
Облачный сервис
Не обязательно делать всё самому. Вот несколько альтернатив:
- AWS SQS + Lambda или Azure Service Bus + Azure Functions, когда нужно масштабировать пул рабочих процессов до нуля и не охота управлять хостами.
- Azure Durable Functions или AWS Step Functions - для многоэтапных рабочих процессов с таймерами, повторными попытками и подтверждением человека. Оркестрация - их сильная сторона.
- Temporal — когда рабочий процесс длится долго (часы, дни), и необходимо первоклассное стабильное выполнение, версионирование и прозрачность между запусками.
Компромисс, как обычно, заключается в меньшем объёме операционной работы, но большей зависимости от поставщика и ценообразовании, которое необходимо тщательно оценить при росте нагрузки.
Источник: https://www.milanjovanovic.tech/blog/how-to-scale-long-running-api-requests
👍2👎2
День 2674. #МоиИнструменты #PG
Инструменты Оптимизации Запросов в PostgreSQL. Часть 10
10. pg_qualstats (Статистика Предикатов Запросов)
Что даёт: статистику, какие условия WHERE больше всего выиграют от индексов.
Зачем нужен
Создание индексов — это гадание на кофейной гуще без данных. Какие столбцы на самом деле запрашиваются чаще всего? Какие предикаты больше всего нагружают базу? pg_qualstats отслеживает использование условий WHERE во всех запросах, точно показывая, какие индексы окажут максимальное влияние.
Установка
1. Устанавливаем расширение
2. Добавляем в postgresql.conf
3. Перезапускаем PostgreSQL.
Использование
После работы производственной базы (часы/дни), выполняем запрос на поиск наиболее используемых предикатов:
Пример вывода:
Т.е. "customer_id = ?" использовалось 450 тыс раз. Это явный кандидат на индекс.
Предложения индексов
Вывод: предложения в порядке значимости
1. orders(customer_id) – 450 тыс раз, фильтрует 99.9% строк
2. orders(order_date) - 234 тыс раз, фильтрует 95% строк
3. orders(status) - 123 тыс раз, фильтрует 80% строк
Когда использовать
- Большая БД с неясными потребностями в индексировании;
- Необходимы обоснованные решения по индексам;
- Кампания по оптимизации индексов.
Когда отказаться
- Небольшая БД (достаточно ручного анализа);
- Индексы уже хорошо оптимизированы;
- Не используете PostgreSQL.
Скрытая функция
Выявление неиспользуемых индексов (противоположный вариант использования). Объедините pg_qualstats с pg_stat_user_indexes, чтобы найти индексы, которые можно удалить.
Вывод: индексы, занимающие место, но не используемые, которые можно удалить.
С осторожностью
Настройки по умолчанию могут вызывать накладные расходы в размере 5-10% при очень высокой частоте запросов в секунду. Решение: снижение накладных расходов путём выборки. В postgresql.conf:
Проверка нагрузки
Если tracking_ratio > 0.1 на частых запросах, уменьшите sample_rate.
Источник: https://medium.com/@reliabledataengineering/15-sql-optimization-tools-that-make-queries-10x-faster-8629ac451d97
Инструменты Оптимизации Запросов в PostgreSQL. Часть 10
10. pg_qualstats (Статистика Предикатов Запросов)
Что даёт: статистику, какие условия WHERE больше всего выиграют от индексов.
Зачем нужен
Создание индексов — это гадание на кофейной гуще без данных. Какие столбцы на самом деле запрашиваются чаще всего? Какие предикаты больше всего нагружают базу? pg_qualstats отслеживает использование условий WHERE во всех запросах, точно показывая, какие индексы окажут максимальное влияние.
Установка
1. Устанавливаем расширение
CREATE EXTENSION pg_qualstats;
2. Добавляем в postgresql.conf
shared_preload_libraries = 'pg_stat_statements,pg_qualstats'
pg_qualstats.enabled = on
pg_qualstats.track_constants = on
3. Перезапускаем PostgreSQL.
Использование
После работы производственной базы (часы/дни), выполняем запрос на поиск наиболее используемых предикатов:
SELECT
qualid,
queryid,
userid,
dbid,
lrelid::regclass AS table_name,
lattnum AS column_number,
opno::regoperator AS operator,
eval_type,
count AS execution_count
FROM pg_qualstats
ORDER BY count DESC
LIMIT 20;
Пример вывода:
table_name | column | operator | execution_count
orders | customer_id | = | 450,234
orders | order_date | >= | 234,567
orders | status | = | 123,456
customers | email | = | 89,234
Т.е. "customer_id = ?" использовалось 450 тыс раз. Это явный кандидат на индекс.
Предложения индексов
SELECT
v.relname,
v.attnames,
sum(v.execution_count) as execution_count,
sum(v.nbfiltered) as rows_filtered
FROM (
SELECT
lrelid::regclass AS relname,
array_agg(DISTINCT attname) AS attnames,
count AS execution_count,
nbfiltered
FROM pg_qualstats
JOIN pg_attribute ON (attrelid = lrelid AND attnum = lattnum)
WHERE nbfiltered > 100 -- Только избирательные предикаты
GROUP BY lrelid, lattnum, count, nbfiltered
) v
GROUP BY v.relname, v.attnames
ORDER BY execution_count DESC;
Вывод: предложения в порядке значимости
1. orders(customer_id) – 450 тыс раз, фильтрует 99.9% строк
2. orders(order_date) - 234 тыс раз, фильтрует 95% строк
3. orders(status) - 123 тыс раз, фильтрует 80% строк
Когда использовать
- Большая БД с неясными потребностями в индексировании;
- Необходимы обоснованные решения по индексам;
- Кампания по оптимизации индексов.
Когда отказаться
- Небольшая БД (достаточно ручного анализа);
- Индексы уже хорошо оптимизированы;
- Не используете PostgreSQL.
Скрытая функция
Выявление неиспользуемых индексов (противоположный вариант использования). Объедините pg_qualstats с pg_stat_user_indexes, чтобы найти индексы, которые можно удалить.
SELECT
schemaname,
tablename,
indexname,
idx_scan AS index_scans,
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
FROM pg_stat_user_indexes
WHERE idx_scan = 0 -- не используется
AND indexrelid NOT IN (
-- Исключаем индексы из pg_qualstats
SELECT DISTINCT lrelid
FROM pg_qualstats
WHERE count > 0
)
ORDER BY pg_relation_size(indexrelid) DESC;
Вывод: индексы, занимающие место, но не используемые, которые можно удалить.
С осторожностью
Настройки по умолчанию могут вызывать накладные расходы в размере 5-10% при очень высокой частоте запросов в секунду. Решение: снижение накладных расходов путём выборки. В postgresql.conf:
# Отслеживаем 10% запросов
pg_qualstats.sample_rate = 0.1
# Исключаем пользователей/базы
pg_qualstats.exclude_users = 'readonly_user,report_user'
Проверка нагрузки
SELECT
pg_stat_statements.query,
pg_qualstats.count AS qualstats_calls,
pg_stat_statements.calls AS total_calls,
(pg_qualstats.count::float / pg_stat_statements.calls) AS tracking_ratio
FROM pg_qualstats
JOIN pg_stat_statements USING (queryid);
Если tracking_ratio > 0.1 на частых запросах, уменьшите sample_rate.
Источник: https://medium.com/@reliabledataengineering/15-sql-optimization-tools-that-make-queries-10x-faster-8629ac451d97
👍5
День 2675. #ЗаметкиНаПолях
Реализуем Немедленный Отзыв Токенов в .NET 10. Начало
Представьте себе этот кошмарный сценарий. У клиента банка украли телефон, где ваше мобильное приложение авторизовано, предоставляя вору полный доступ к счетам клиента. В службу поддержки поступает звонок. Каждая секунда на счету. Какова скорость вашей реакции для отмены активной сессии и обеспечения безопасности средств клиента?
Если вы полагаетесь на стандартные автономные JWT-токены, честный ответ может быть «до часа», в зависимости от срока действия токена. Этого недостаточно. Рассмотрим, как референтные токены предоставляют вам кнопку экстренной остановки именно в таких ситуациях, и как это реализовать в Duende IdentityServer в .NET 10.
Проблема с автономными JWT
Автономные JWT — это основной инструмент современной авторизации. Они содержат все необходимые API данные для принятия решений о доступе внутри себя. Никаких обращений к БД или сетевых вызовов к поставщику идентификации. API проверяет подпись, проверяет срок действия, и вы получаете доступ. Просто и быстро.
Но это палка о двух концах. После выдачи JWT поставщику идентификации больше нечего о нём сказать. Токен действителен в течение срока действия, обычно от 5 до 60 минут. Если устройство украдено, учётная запись пользователя скомпрометирована или обнаружена угроза, вы не можете отозвать этот токен. Вы вынуждены ждать, пока он истечёт.
Для многих приложений такой компромисс вполне приемлем. Но для сред с высоким уровнем безопасности, таких как банковские, государственные системы или здравоохранение, это недопустимый компромисс.
Референтные токены
Вместо того чтобы встраивать все утверждения непосредственно в токен, IdentityServer хранит содержимое токена на стороне сервера в своём постоянном хранилище предоставленных прав и передаёт клиенту идентификатор (дескриптор). Когда API получает этот дескриптор, он вызывает конечную точку интроспекции IdentityServer для проверки токена и получения утверждений.
Поскольку данные токена хранятся на сервере, вы можете удалить их в любое время. Отзыв происходит немедленно. В следующий раз, когда API вызовет конечную точку интроспекции, он получит в ответ
Однако, каждый вызов API требует обращения к конечной точке интроспекции. Для публичных API в масштабах сети интернет это излишне. Для внутренних сервисов и сред с высоким уровнем безопасности – это разумная цена за возможность мгновенного отключения.
Когда использовать референтные токены
Референтные токены не являются универсальной заменой JWT. Они проявляют свои преимущества в определённых сценариях:
- Немедленное аннулирование является жёстким требованием (банковская сфера, здравоохранение, системы, ориентированные на соответствие нормативным требованиям);
- Внутреннее взаимодействие между сервисами, где время отклика при интроспекции незначительно;
- Операции с высоким риском, где преимущества в безопасности перевешивают затраты на производительность.
Для общедоступных API в масштабе, где задержка аннулирования приемлема, автономные JWT с коротким сроком действия остаются надёжным выбором. Вы даже можете комбинировать: использовать референтные токены для клиентов с конфиденциальными данными и JWT для клиентов с более низким риском, и всё это в рамках одного развёртывания IdentityServer.
Далее рассмотрим реализацию.
Окончание следует…
Источник: https://duendesoftware.com/blog/20260428-the-emergency-stop-button-implementing-immediate-token-revocation-in-dotnet-10
Реализуем Немедленный Отзыв Токенов в .NET 10. Начало
Представьте себе этот кошмарный сценарий. У клиента банка украли телефон, где ваше мобильное приложение авторизовано, предоставляя вору полный доступ к счетам клиента. В службу поддержки поступает звонок. Каждая секунда на счету. Какова скорость вашей реакции для отмены активной сессии и обеспечения безопасности средств клиента?
Если вы полагаетесь на стандартные автономные JWT-токены, честный ответ может быть «до часа», в зависимости от срока действия токена. Этого недостаточно. Рассмотрим, как референтные токены предоставляют вам кнопку экстренной остановки именно в таких ситуациях, и как это реализовать в Duende IdentityServer в .NET 10.
Проблема с автономными JWT
Автономные JWT — это основной инструмент современной авторизации. Они содержат все необходимые API данные для принятия решений о доступе внутри себя. Никаких обращений к БД или сетевых вызовов к поставщику идентификации. API проверяет подпись, проверяет срок действия, и вы получаете доступ. Просто и быстро.
Но это палка о двух концах. После выдачи JWT поставщику идентификации больше нечего о нём сказать. Токен действителен в течение срока действия, обычно от 5 до 60 минут. Если устройство украдено, учётная запись пользователя скомпрометирована или обнаружена угроза, вы не можете отозвать этот токен. Вы вынуждены ждать, пока он истечёт.
Для многих приложений такой компромисс вполне приемлем. Но для сред с высоким уровнем безопасности, таких как банковские, государственные системы или здравоохранение, это недопустимый компромисс.
Референтные токены
Вместо того чтобы встраивать все утверждения непосредственно в токен, IdentityServer хранит содержимое токена на стороне сервера в своём постоянном хранилище предоставленных прав и передаёт клиенту идентификатор (дескриптор). Когда API получает этот дескриптор, он вызывает конечную точку интроспекции IdentityServer для проверки токена и получения утверждений.
Поскольку данные токена хранятся на сервере, вы можете удалить их в любое время. Отзыв происходит немедленно. В следующий раз, когда API вызовет конечную точку интроспекции, он получит в ответ
"active": false, и доступ будет запрещён.Однако, каждый вызов API требует обращения к конечной точке интроспекции. Для публичных API в масштабах сети интернет это излишне. Для внутренних сервисов и сред с высоким уровнем безопасности – это разумная цена за возможность мгновенного отключения.
Когда использовать референтные токены
Референтные токены не являются универсальной заменой JWT. Они проявляют свои преимущества в определённых сценариях:
- Немедленное аннулирование является жёстким требованием (банковская сфера, здравоохранение, системы, ориентированные на соответствие нормативным требованиям);
- Внутреннее взаимодействие между сервисами, где время отклика при интроспекции незначительно;
- Операции с высоким риском, где преимущества в безопасности перевешивают затраты на производительность.
Для общедоступных API в масштабе, где задержка аннулирования приемлема, автономные JWT с коротким сроком действия остаются надёжным выбором. Вы даже можете комбинировать: использовать референтные токены для клиентов с конфиденциальными данными и JWT для клиентов с более низким риском, и всё это в рамках одного развёртывания IdentityServer.
Далее рассмотрим реализацию.
Окончание следует…
Источник: https://duendesoftware.com/blog/20260428-the-emergency-stop-button-implementing-immediate-token-revocation-in-dotnet-10
👍6
День 2676. #ЗаметкиНаПолях
Реализуем Немедленный Отзыв Токенов в .NET 10. Окончание
Начало
Настройка токенов
Переключение клиента на использование референтных токенов - одна строка конфигурации. При определении клиента в IdentityServer установите AccessTokenType:
Теперь токены, выданные этому клиенту, будут представлять собой непрозрачные дескрипторы вместо самодостаточных JWT.
Настройка API для интроспекции
Ваш API должен знать, как проверять эти непрозрачные токены. Вместо (или в дополнение к) проверке JWT настроим интроспекцию OAuth 2.0. Сначала определите ресурс API с секретом, который API будет использовать для аутентификации с конечной точкой интроспекции:
Затем в Program.cs вашего API зарегистрируйте обработчик интроспекции. Обратите внимание, что обработчик должен использовать ту же схему аутентификации, что и та, которую вы хотите использовать для интроспекции:
Если вам необходимо поддерживать как JWT, так и референтные токены (например, во время миграции), вы можете зарегистрировать обработчики обоих типов и использовать переадресацию для направления токенов к нужному обработчику:
Отзыв токена
Теперь ваша система поддержки (или автоматизированный конвейер обнаружения угроз) сможет немедленно отозвать токен, используя конечную точку отзыва IdentityServer, которая реализует RFC 7009:
После отзыва токен удаляется из хранилища предоставленных прав IdentityServer. Следующий запрос на проверку подлинности от любого API подтвердит, что токен больше не активен.
Не забывайте: вы также можете (и должны) отозвать токен обновления пользователя, чтобы предотвратить незаметное получение клиентом нового токена доступа:
Обратите внимание, что как интроспекция, так и аннулирование генерируют события аудита, которые можно использовать для реализации журналов аудита в регулируемых отраслях.
Источник: https://duendesoftware.com/blog/20260428-the-emergency-stop-button-implementing-immediate-token-revocation-in-dotnet-10
Реализуем Немедленный Отзыв Токенов в .NET 10. Окончание
Начало
Настройка токенов
Переключение клиента на использование референтных токенов - одна строка конфигурации. При определении клиента в IdentityServer установите AccessTokenType:
new Client
{
ClientId = "banking_app",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code,
// Вот эта строка
AccessTokenType = AccessTokenType.Reference,
AllowOfflineAccess = true,
RedirectUris = { "https://banking.example.com/signin-oidc" },
AllowedScopes = { "openid", "profile", "accounts.read", "transfers.write" }
};
Теперь токены, выданные этому клиенту, будут представлять собой непрозрачные дескрипторы вместо самодостаточных JWT.
Настройка API для интроспекции
Ваш API должен знать, как проверять эти непрозрачные токены. Вместо (или в дополнение к) проверке JWT настроим интроспекцию OAuth 2.0. Сначала определите ресурс API с секретом, который API будет использовать для аутентификации с конечной точкой интроспекции:
new ApiResource("banking_api")
{
Scopes = { "accounts.read", "transfers.write" },
ApiSecrets = { new Secret("api_secret".Sha256()) }
};Затем в Program.cs вашего API зарегистрируйте обработчик интроспекции. Обратите внимание, что обработчик должен использовать ту же схему аутентификации, что и та, которую вы хотите использовать для интроспекции:
builder.Services.AddAuthentication("token")
.AddOAuth2Introspection("token", opts =>
{
opts.Authority = "https://identity.banking.example.com";
opts.ClientId = "banking_api";
opts.ClientSecret = "api_secret";
});Если вам необходимо поддерживать как JWT, так и референтные токены (например, во время миграции), вы можете зарегистрировать обработчики обоих типов и использовать переадресацию для направления токенов к нужному обработчику:
builder.Services.AddAuthentication("token")
.AddJwtBearer("token", opts =>
{
opts.Authority = "https://identity.banking.example.com";
opts.Audience = "banking_api";
opts.TokenValidationParameters.ValidTypes = ["at+jwt"];
opts.ForwardDefaultSelector = Selector.ForwardReferenceToken("introspection");
})
.AddOAuth2Introspection("introspection", opts =>
{
opts.Authority = "https://identity.banking.example.com";
opts.ClientId = "banking_api";
opts.ClientSecret = "api_secret";
});Отзыв токена
Теперь ваша система поддержки (или автоматизированный конвейер обнаружения угроз) сможет немедленно отозвать токен, используя конечную точку отзыва IdentityServer, которая реализует RFC 7009:
using Duende.IdentityModel.Client;
var client = new HttpClient();
var result = await client.RevokeTokenAsync(
new TokenRevocationRequest
{
Address = "https://identity.banking.example.com/connect/revocation",
ClientId = "banking_app",
ClientSecret = "secret",
Token = stolenAccessToken
});
if (result.IsError)
logger.LogError("Token revocation failed: {Error}", result.Error);
После отзыва токен удаляется из хранилища предоставленных прав IdentityServer. Следующий запрос на проверку подлинности от любого API подтвердит, что токен больше не активен.
Не забывайте: вы также можете (и должны) отозвать токен обновления пользователя, чтобы предотвратить незаметное получение клиентом нового токена доступа:
await client.RevokeTokenAsync(
new TokenRevocationRequest
{
Address = "https://identity.banking.example.com/connect/revocation",
ClientId = "banking_app",
ClientSecret = "secret",
Token = refreshToken
});
Обратите внимание, что как интроспекция, так и аннулирование генерируют события аудита, которые можно использовать для реализации журналов аудита в регулируемых отраслях.
Источник: https://duendesoftware.com/blog/20260428-the-emergency-stop-button-implementing-immediate-token-revocation-in-dotnet-10
День 2677. #Карьера
15 Уроков Программирования от Опытного Разработчика. Начало
За время вашей карьеры вам может случиться работать как в крупных корпорациях с устаревшим кодом, так и в небольших стартапах, которые меняют стратегию почти каждую неделю. Возможно, вы будете наставником других разработчиков, помогая им расти. У вас будут и провальные, и успешные проекты. Вот вещи, которые пригодятся вам для построения успешной карьеры.
1. Придерживайтесь одного фреймворка
В первые 3-5 лет работы программистом старайтесь сосредоточиться на одном фреймворке. Игнорируйте постоянный шум и новости об очередной крутой системе. Вместо этого выберите одну и освойте её. Когда вы действительно понимаете одну технологию, будет гораздо проще изучать другие.
Многие разработчики совершают ошибку, перескакивая с одного модного фреймворка на другой. После многих лет программирования они всё ещё испытывают трудности с основами, потому что так и не освоили что-то одно досконально.
Не беспокойтесь о том, чтобы изучить всё сразу. Как только вы станете опытным разработчиком в какой-либо технологии или стеке, изучение новых станет намного проще — и многие коллеги будут считать вас опытным в том, что вы недавно изучили.
2. В случае сомнений сосредоточьтесь на основах
Фреймворки и библиотеки, которые вы используете сегодня, могут исчезнуть через 10 лет. Но основы никогда не меняются. Это особенно важно, если вы самоучка. Построение карьеры разработчика на слабых основах подобно строительству замка на песке — он в конце концов рухнет. Сосредоточьтесь на изучении структур данных, алгоритмов, решении проблем, чистом коде и проектировании систем. Эти навыки помогут адаптироваться к любой технологии в будущем.
3. Верьте в себя
Независимо от того, сколько вы знаете, вы не будете расти, если не верите в себя. Многие разработчики никогда не достигают более высокого уровня, потому что боятся пробовать. Они думают, что недостаточно хороши, и перестают высказываться или пробовать что-то новое. Вы можете достичь гораздо большего, чем думаете. Верьте в себя и продолжайте двигаться вперёд.
4. Игнорируйте хейтеров
Негатив повсюду в разработке ПО — в код-ревью, на собеседованиях или командных обсуждениях. Некоторые оставляют резкие комментарии или ненужную критику. Помните: люди, которые негативно относятся к вам, обычно негативно относятся ко всем. Не позволяйте их отношению влиять на вашу уверенность или мотивацию. Научитесь устанавливать границы. Вы учите людей, как с вами обращаться. Что вы можете сделать, с хейтерами — игнорировать их и двигаться дальше. Берегите свою энергию, уверенность и настрой.
5. Не принимайте ничего на свой счёт
В разработке вы будете получать много обратной связи. Не принимайте её на свой счёт. В большинстве случаев люди критикуют код, а не вас как личность. Используйте обратную связь как возможность учиться и совершенствоваться. Воспринимайте карьеру в разработке как игру. Иногда вы выигрываете, иногда проигрываете. Важно, чтобы вы продолжали учиться, совершенствоваться и двигаться вперёд.
6. Будьте готовы к неудачам (многочисленным)
Неудачи — часть работы разработчика. Некоторые проекты будут проваливаться. Вы можете упустить повышение, получить отказ на собеседовании или увидеть, как вашу работу отменяют. Это нормально — даже для сеньоров или технических директоров. Учитесь на неудачах. Чем больше вы пробуете, тем больше узнаёте. Не тратьте энергию на беспокойство о том, что может пойти не так. Сосредоточьтесь на решении проблем и ежедневном совершенствовании.
7. Научитесь отладке
Умение правильно отлаживать код значительно упростит вашу жизнь. Хорошие навыки отладки помогают решать критические проблемы, когда приложения ломаются. К сожалению, многие разработчики игнорируют этот навык. Если вы освоите отладку, тестирование, работу с устаревшим кодом и написание документации, вы будете выделяться как надёжный и сильный разработчик.
Окончание следует…
Источник: https://medium.com/@sunil17bbmp/15-programming-lessons-i-learned-from-10-years-of-coding-321a47d14b72
15 Уроков Программирования от Опытного Разработчика. Начало
За время вашей карьеры вам может случиться работать как в крупных корпорациях с устаревшим кодом, так и в небольших стартапах, которые меняют стратегию почти каждую неделю. Возможно, вы будете наставником других разработчиков, помогая им расти. У вас будут и провальные, и успешные проекты. Вот вещи, которые пригодятся вам для построения успешной карьеры.
1. Придерживайтесь одного фреймворка
В первые 3-5 лет работы программистом старайтесь сосредоточиться на одном фреймворке. Игнорируйте постоянный шум и новости об очередной крутой системе. Вместо этого выберите одну и освойте её. Когда вы действительно понимаете одну технологию, будет гораздо проще изучать другие.
Многие разработчики совершают ошибку, перескакивая с одного модного фреймворка на другой. После многих лет программирования они всё ещё испытывают трудности с основами, потому что так и не освоили что-то одно досконально.
Не беспокойтесь о том, чтобы изучить всё сразу. Как только вы станете опытным разработчиком в какой-либо технологии или стеке, изучение новых станет намного проще — и многие коллеги будут считать вас опытным в том, что вы недавно изучили.
2. В случае сомнений сосредоточьтесь на основах
Фреймворки и библиотеки, которые вы используете сегодня, могут исчезнуть через 10 лет. Но основы никогда не меняются. Это особенно важно, если вы самоучка. Построение карьеры разработчика на слабых основах подобно строительству замка на песке — он в конце концов рухнет. Сосредоточьтесь на изучении структур данных, алгоритмов, решении проблем, чистом коде и проектировании систем. Эти навыки помогут адаптироваться к любой технологии в будущем.
3. Верьте в себя
Независимо от того, сколько вы знаете, вы не будете расти, если не верите в себя. Многие разработчики никогда не достигают более высокого уровня, потому что боятся пробовать. Они думают, что недостаточно хороши, и перестают высказываться или пробовать что-то новое. Вы можете достичь гораздо большего, чем думаете. Верьте в себя и продолжайте двигаться вперёд.
4. Игнорируйте хейтеров
Негатив повсюду в разработке ПО — в код-ревью, на собеседованиях или командных обсуждениях. Некоторые оставляют резкие комментарии или ненужную критику. Помните: люди, которые негативно относятся к вам, обычно негативно относятся ко всем. Не позволяйте их отношению влиять на вашу уверенность или мотивацию. Научитесь устанавливать границы. Вы учите людей, как с вами обращаться. Что вы можете сделать, с хейтерами — игнорировать их и двигаться дальше. Берегите свою энергию, уверенность и настрой.
5. Не принимайте ничего на свой счёт
В разработке вы будете получать много обратной связи. Не принимайте её на свой счёт. В большинстве случаев люди критикуют код, а не вас как личность. Используйте обратную связь как возможность учиться и совершенствоваться. Воспринимайте карьеру в разработке как игру. Иногда вы выигрываете, иногда проигрываете. Важно, чтобы вы продолжали учиться, совершенствоваться и двигаться вперёд.
6. Будьте готовы к неудачам (многочисленным)
Неудачи — часть работы разработчика. Некоторые проекты будут проваливаться. Вы можете упустить повышение, получить отказ на собеседовании или увидеть, как вашу работу отменяют. Это нормально — даже для сеньоров или технических директоров. Учитесь на неудачах. Чем больше вы пробуете, тем больше узнаёте. Не тратьте энергию на беспокойство о том, что может пойти не так. Сосредоточьтесь на решении проблем и ежедневном совершенствовании.
7. Научитесь отладке
Умение правильно отлаживать код значительно упростит вашу жизнь. Хорошие навыки отладки помогают решать критические проблемы, когда приложения ломаются. К сожалению, многие разработчики игнорируют этот навык. Если вы освоите отладку, тестирование, работу с устаревшим кодом и написание документации, вы будете выделяться как надёжный и сильный разработчик.
Окончание следует…
Источник: https://medium.com/@sunil17bbmp/15-programming-lessons-i-learned-from-10-years-of-coding-321a47d14b72
👍12👎2
День 2678. #Карьера
15 Уроков Программирования от Опытного Разработчика. Окончание
Начало
8. ИИ не заменит разработчиков (в ближайшее время)
ИИ может помочь в написании кода, объяснении концепций и ускорении разработки, но ему по-прежнему нужны разработчики-люди для руководства. Большая часть работы разработчика — понимание требований, решение проблем, общение с командами и принятие решений. Это сложно автоматизировать. Поэтому вместо того, чтобы беспокоиться об ИИ, используйте его как инструмент для повышения своей продуктивности.
9. Будьте добры к начинающим
Начинающие иногда могут задавать много вопросов или оспаривать ваши идеи. Но все мы были новичками. Большинство начинающих просто пытаются учиться и делать всё возможное. Часть вашей роли как старшего разработчика — направлять и поддерживать их. Они могут ещё не знать, чего они не знают. Будьте терпеливы, помогайте им расти и относитесь к ним с уважением.
10. Работайте умнее, а не усерднее
Полезная привычка — иметь чёткое «время окончания работы», чтобы сосредоточиться на здоровье, хобби и личной жизни. Планирование дня и уменьшение отвлекающих факторов также могут помочь. Такие методы, как техника Помодоро, могут улучшить концентрацию и продуктивность. Работать эффективнее означает управлять своим временем, энергией и концентрацией, а не просто работать дольше.
11. Думайте в долгую
Всегда думайте о долгосрочных последствиях своих решений: о навыках, которые вы приобретаете, о компаниях, в которые вы устраиваетесь, и о людях, с которыми вы работаете. Многие сосредотачиваются на быстрых результатах и мгновенном вознаграждении. Но построение успешной карьеры разработчика требует времени и последовательности.
Краткосрочное планирование часто приводит к техническому долгу или постоянной смене направлений без освоения чего-либо. Успешные разработчики, как правило, те, кто остаётся последовательным и продолжает двигаться вперед, даже когда становится трудно.
12. Наибольшее повышение зарплаты часто происходит при смене работы
Во многих компаниях индексация зарплаты невелика. При смене работы повышение зарплаты иногда может быть намного выше. Поэтому многие разработчики быстрее увеличивают доход, меняя компании после получения опыта. Однако нужно быть готовым к техническим собеседованиям, задачам по программированию и обсуждениям проектирования систем.
Многие разработчики избегают собеседований, но улучшение этого навыка может открыть лучшие карьерные возможности. Каждое собеседование — это также опыт обучения.
13. Не угрожайте начальнику
Вы можете расстроиться, если не получите повышения или признания. Но избегайте угроз или шантажа начальника, чтобы получить желаемое. Даже если это сработает один раз, это может подорвать доверие и навредить вашим долгосрочным отношениям. Оставайтесь профессиональными и спокойными. Постарайтесь найти позитивные решения как для себя, так и для своего руководителя. Если ситуация не улучшится, лучшим вариантом может стать поиск возможностей в другом месте.
14. Не бойтесь тяжёлой работы
Если вы хотите стать отличным разработчиком, вы должны быть готовы много работать. В интернете много советов, коротких путей и «лайфхаков», но ни один из них не заменит постоянных усилий и практики. Совершенствование навыков требует времени, терпения и целеустремлённости. В то же время, помните о необходимости делать перерывы и избегать выгорания.
15. Качество важнее количества
В мире технологий много шума — постоянно появляются новые инструменты, фреймворки и тренды. Один из важных навыков для разработчиков — умение сосредотачиваться на том, что действительно важно.
Это правило применимо к коду, проектам и даже к людям, с которыми вы работаете — всегда выбирайте качество, а не количество. Новые технологии будут продолжать появляться. Но разработчики, которые остаются сосредоточенными, последовательными и дисциплинированными, всегда будут преуспевать.
Источник: https://medium.com/@sunil17bbmp/15-programming-lessons-i-learned-from-10-years-of-coding-321a47d14b72
15 Уроков Программирования от Опытного Разработчика. Окончание
Начало
8. ИИ не заменит разработчиков (в ближайшее время)
ИИ может помочь в написании кода, объяснении концепций и ускорении разработки, но ему по-прежнему нужны разработчики-люди для руководства. Большая часть работы разработчика — понимание требований, решение проблем, общение с командами и принятие решений. Это сложно автоматизировать. Поэтому вместо того, чтобы беспокоиться об ИИ, используйте его как инструмент для повышения своей продуктивности.
9. Будьте добры к начинающим
Начинающие иногда могут задавать много вопросов или оспаривать ваши идеи. Но все мы были новичками. Большинство начинающих просто пытаются учиться и делать всё возможное. Часть вашей роли как старшего разработчика — направлять и поддерживать их. Они могут ещё не знать, чего они не знают. Будьте терпеливы, помогайте им расти и относитесь к ним с уважением.
10. Работайте умнее, а не усерднее
Полезная привычка — иметь чёткое «время окончания работы», чтобы сосредоточиться на здоровье, хобби и личной жизни. Планирование дня и уменьшение отвлекающих факторов также могут помочь. Такие методы, как техника Помодоро, могут улучшить концентрацию и продуктивность. Работать эффективнее означает управлять своим временем, энергией и концентрацией, а не просто работать дольше.
11. Думайте в долгую
Всегда думайте о долгосрочных последствиях своих решений: о навыках, которые вы приобретаете, о компаниях, в которые вы устраиваетесь, и о людях, с которыми вы работаете. Многие сосредотачиваются на быстрых результатах и мгновенном вознаграждении. Но построение успешной карьеры разработчика требует времени и последовательности.
Краткосрочное планирование часто приводит к техническому долгу или постоянной смене направлений без освоения чего-либо. Успешные разработчики, как правило, те, кто остаётся последовательным и продолжает двигаться вперед, даже когда становится трудно.
12. Наибольшее повышение зарплаты часто происходит при смене работы
Во многих компаниях индексация зарплаты невелика. При смене работы повышение зарплаты иногда может быть намного выше. Поэтому многие разработчики быстрее увеличивают доход, меняя компании после получения опыта. Однако нужно быть готовым к техническим собеседованиям, задачам по программированию и обсуждениям проектирования систем.
Многие разработчики избегают собеседований, но улучшение этого навыка может открыть лучшие карьерные возможности. Каждое собеседование — это также опыт обучения.
13. Не угрожайте начальнику
Вы можете расстроиться, если не получите повышения или признания. Но избегайте угроз или шантажа начальника, чтобы получить желаемое. Даже если это сработает один раз, это может подорвать доверие и навредить вашим долгосрочным отношениям. Оставайтесь профессиональными и спокойными. Постарайтесь найти позитивные решения как для себя, так и для своего руководителя. Если ситуация не улучшится, лучшим вариантом может стать поиск возможностей в другом месте.
14. Не бойтесь тяжёлой работы
Если вы хотите стать отличным разработчиком, вы должны быть готовы много работать. В интернете много советов, коротких путей и «лайфхаков», но ни один из них не заменит постоянных усилий и практики. Совершенствование навыков требует времени, терпения и целеустремлённости. В то же время, помните о необходимости делать перерывы и избегать выгорания.
15. Качество важнее количества
В мире технологий много шума — постоянно появляются новые инструменты, фреймворки и тренды. Один из важных навыков для разработчиков — умение сосредотачиваться на том, что действительно важно.
Это правило применимо к коду, проектам и даже к людям, с которыми вы работаете — всегда выбирайте качество, а не количество. Новые технологии будут продолжать появляться. Но разработчики, которые остаются сосредоточенными, последовательными и дисциплинированными, всегда будут преуспевать.
Источник: https://medium.com/@sunil17bbmp/15-programming-lessons-i-learned-from-10-years-of-coding-321a47d14b72
👍9👎2