День 2651. #ЧтоНовенького
VSTest удаляет зависимость от Newtonsoft.Json
Начиная с .NET 11 Preview 4 и Visual Studio 18.8, VSTest, платформа, на которой работают dotnet test и Test Explorer, больше не будет зависеть от Newtonsoft.Json. Теперь будет использоваться System.Text.Json в .NET и JSONite в .NET Framework. Большинству проектов никаких действий не требуется. Небольшое количество проектов может столкнуться с ошибкой сборки или тестирования и решить её добавлением одной строки PackageReference.
Зачем?
VSTest годами поставлялся с Newtonsoft.Json в составе .NET SDK и Visual Studio. Все версии Newtonsoft.Json ниже 13.0.0 теперь помечены как уязвимые на NuGet.org, и наличие этой зависимости делает тестовую платформу уязвимой в компоненте, который ей больше не нужен. Удаление является частью более масштабной работы по удалению Newtonsoft.Json из .NET SDK.
Что не меняется?
- Формат передачи VSTest. Сообщения сериализуются одинаково независимо от того, используется ли Newtonsoft.Json, System.Text.Json или JSONite.
- Старые тестовые хосты остаются совместимыми с обновлённой платформой, и наоборот.
- Производительность сериализации остается прежней или улучшается.
Кого это НЕ затронет?
Большинство тестовых проектов:
- Проекты, которые не используют Newtonsoft.Json.
- Проекты, которые ссылаются на Newtonsoft.Json в виде обычной ссылки PackageReference.
- Проекты xUnit и NUnit, работающие на .NET или использующие AppDomains. Для них уже требовалась явная ссылка.
Кого это затронет и как это исправить
Каждая из перечисленных ниже ошибок не является скрытой, сообщается в ходе выполнения теста и передаётся в TRX и представления тестов Azure DevOps / GitHub.
1. Ошибка сборки: отсутствует ссылка на Newtonsoft.Json
Если тестовый проект использует типы Newtonsoft.Json (например, JObject, JsonConvert) без ссылки на этот пакет, ранее он компилировался только потому, что Newtonsoft.Json просачивался через VSTest. После обновления это будет не так.
Исправление: добавьте пакет.
2. Ошибка выполнения: FileNotFoundException для Newtonsoft.Json
Проекты, которые ссылаются на Newtonsoft.Json, но не содержат ресурс времени выполнения — например:
Предполагалось, что копия библиотеки из VSTest будет присутствовать во время тестирования. После этого обновления запуск теста завершится с ошибкой:
Исправление: уберите
3. Ошибка загрузки расширения в тестовом адаптере или сборщике данных
Адаптеры и сборщики данных, которые использовали Newtonsoft.Json, не указав его в качестве зависимости, завершатся с ошибкой во время загрузки с сообщением, подобным следующему:
В сообщении будет указано имя расширения, которое необходимо обновить, однако на данный момент о таких проблемах не сообщалось. Авторам расширений следует добавить Newtonsoft.Json в свой пакет (или отказаться от него); пользователи могут добавить Newtonsoft.Json в тестовый проект или отключить затронутое расширение до его обновления.
Изменение публичного API
VSTest предоставлял доступ к Newtonsoft.Json.Linq.JToken в одном месте своего API связи. Этот тип удаляется из публичного доступа. Он имел смысл только для кода, участвующего в протоколе VSTest, и вряд ли будет использоваться реальными пользователями, но авторам расширений следует об этом знать.
Источник: https://devblogs.microsoft.com/dotnet/vs-test-is-removing-its-newtonsoft-json-dependency/
VSTest удаляет зависимость от Newtonsoft.Json
Начиная с .NET 11 Preview 4 и Visual Studio 18.8, VSTest, платформа, на которой работают dotnet test и Test Explorer, больше не будет зависеть от Newtonsoft.Json. Теперь будет использоваться System.Text.Json в .NET и JSONite в .NET Framework. Большинству проектов никаких действий не требуется. Небольшое количество проектов может столкнуться с ошибкой сборки или тестирования и решить её добавлением одной строки PackageReference.
Зачем?
VSTest годами поставлялся с Newtonsoft.Json в составе .NET SDK и Visual Studio. Все версии Newtonsoft.Json ниже 13.0.0 теперь помечены как уязвимые на NuGet.org, и наличие этой зависимости делает тестовую платформу уязвимой в компоненте, который ей больше не нужен. Удаление является частью более масштабной работы по удалению Newtonsoft.Json из .NET SDK.
Что не меняется?
- Формат передачи VSTest. Сообщения сериализуются одинаково независимо от того, используется ли Newtonsoft.Json, System.Text.Json или JSONite.
- Старые тестовые хосты остаются совместимыми с обновлённой платформой, и наоборот.
- Производительность сериализации остается прежней или улучшается.
Кого это НЕ затронет?
Большинство тестовых проектов:
- Проекты, которые не используют Newtonsoft.Json.
- Проекты, которые ссылаются на Newtonsoft.Json в виде обычной ссылки PackageReference.
- Проекты xUnit и NUnit, работающие на .NET или использующие AppDomains. Для них уже требовалась явная ссылка.
Кого это затронет и как это исправить
Каждая из перечисленных ниже ошибок не является скрытой, сообщается в ходе выполнения теста и передаётся в TRX и представления тестов Azure DevOps / GitHub.
1. Ошибка сборки: отсутствует ссылка на Newtonsoft.Json
Если тестовый проект использует типы Newtonsoft.Json (например, JObject, JsonConvert) без ссылки на этот пакет, ранее он компилировался только потому, что Newtonsoft.Json просачивался через VSTest. После обновления это будет не так.
Исправление: добавьте пакет.
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
2. Ошибка выполнения: FileNotFoundException для Newtonsoft.Json
Проекты, которые ссылаются на Newtonsoft.Json, но не содержат ресурс времени выполнения — например:
<PackageReference Include="Newtonsoft.Json" Version="13.0.3">
<ExcludeAssets>runtime</ExcludeAssets>
</PackageReference>
Предполагалось, что копия библиотеки из VSTest будет присутствовать во время тестирования. После этого обновления запуск теста завершится с ошибкой:
System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'.
Исправление: уберите
<ExcludeAssets>runtime</ExcludeAssets>, либо установите Newtonsoft.Json без исключения ресурсов времени выполнения.3. Ошибка загрузки расширения в тестовом адаптере или сборщике данных
Адаптеры и сборщики данных, которые использовали Newtonsoft.Json, не указав его в качестве зависимости, завершатся с ошибкой во время загрузки с сообщением, подобным следующему:
Data collector 'SampleDataCollector' threw an exception during type loading, construction, or initialization: System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'.
В сообщении будет указано имя расширения, которое необходимо обновить, однако на данный момент о таких проблемах не сообщалось. Авторам расширений следует добавить Newtonsoft.Json в свой пакет (или отказаться от него); пользователи могут добавить Newtonsoft.Json в тестовый проект или отключить затронутое расширение до его обновления.
Изменение публичного API
VSTest предоставлял доступ к Newtonsoft.Json.Linq.JToken в одном месте своего API связи. Этот тип удаляется из публичного доступа. Он имел смысл только для кода, участвующего в протоколе VSTest, и вряд ли будет использоваться реальными пользователями, но авторам расширений следует об этом знать.
Источник: https://devblogs.microsoft.com/dotnet/vs-test-is-removing-its-newtonsoft-json-dependency/
👍3
День 2652. #МоиИнструменты #PG
Инструменты Оптимизации Запросов в PostgreSQL. Часть 8
8. QueryPie (совместная работа с SQL + оптимизация)
Что даёт: Совместная работа команды над оптимизацией запросов со встроенным анализом производительности.
Тип: Коммерческий (доступен бесплатный уровень)
Базы данных: Postgres, MySQL, Redshift, BigQuery, Snowflake
Зачем нужен
Большинство оптимизаций запросов происходит изолированно. Один человек пишет медленный запрос, другой в итоге замечает и исправляет его. QueryPie делает оптимизацию совместной — запросы передаются, показатели производительности видны команде, а улучшения документируются.
Как работает
Пишете запрос в IDE QueryPie -> Автоматически запускается EXPLAIN ANALYZE -> Отображаются показатели производительности -> Делитесь запросом с командой по ссылке -> Члены команды предлагают варианты оптимизации -> A/B-сравнение версий запроса -> Лучшая версия продвигается в прод.
Пример рабочего процесса
1. Аналитик данных пишет запрос (QueryPie IDE)
2. Вывод QueryPie:
3. Разработчик нажимает "Предложить оптимизацию". ИИ QueryPie предлагает:
4. Новые метрики:
5. Показывается сравнение результатов.
6. При нажатии кнопки «Принять оптимизацию» исходный запрос помечается как устаревший, команда уведомляется об улучшении.
Когда использовать
- Несколько человек пишут SQL-запросы;
- Требуется совместная оптимизация;
- Необходимо версионирование запросов;
- Предпочитаете веб-IDE локальным инструментам.
Когда отказаться
- Разработчик-одиночка (функции совместной работы не используются);
- Довольны текущей IDE (pgAdmin, DBeaver и т. д.);
- Очень ограниченный бюджет ($15-50 на пользователя в месяц).
Скрытая функция
Автоматическое обнаружение регрессии производительности запросов. QueryPie отслеживает каждое выполнение запроса и может предоставлять динамику производительности запроса во времени и рекомендации по улучшению.
С осторожностью
QueryPie хранит результаты запросов. Соображения конфиденциальности:
- Запросы выполняются через прокси QueryPie;
- Результаты кэшируются для обмена;
- Может содержать конфиденциальные данные.
Конфигурация:
- Установить политику хранения данных (по умолчанию 7 дней);
- Исключить столбцы с персональными данными из кэширования;
- Использовать локальное развёртывание для конфиденциальных данных;
- Перед развёртыванием командой проверить политики безопасности.
Источник: https://medium.com/@reliabledataengineering/15-sql-optimization-tools-that-make-queries-10x-faster-8629ac451d97
Инструменты Оптимизации Запросов в PostgreSQL. Часть 8
8. QueryPie (совместная работа с SQL + оптимизация)
Что даёт: Совместная работа команды над оптимизацией запросов со встроенным анализом производительности.
Тип: Коммерческий (доступен бесплатный уровень)
Базы данных: Postgres, MySQL, Redshift, BigQuery, Snowflake
Зачем нужен
Большинство оптимизаций запросов происходит изолированно. Один человек пишет медленный запрос, другой в итоге замечает и исправляет его. QueryPie делает оптимизацию совместной — запросы передаются, показатели производительности видны команде, а улучшения документируются.
Как работает
Пишете запрос в IDE QueryPie -> Автоматически запускается EXPLAIN ANALYZE -> Отображаются показатели производительности -> Делитесь запросом с командой по ссылке -> Члены команды предлагают варианты оптимизации -> A/B-сравнение версий запроса -> Лучшая версия продвигается в прод.
Пример рабочего процесса
1. Аналитик данных пишет запрос (QueryPie IDE)
SELECT
c.customer_name,
SUM(o.total) as revenue
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
WHERE c.country = 'US'
GROUP BY c.customer_name
ORDER BY revenue DESC
LIMIT 100;
2. Вывод QueryPie:
Execution time: 23.4s
Rows scanned: 5.2M
Cost: $0.45 (Вымышленная валюта)
Warning: LEFT JOIN inefficient (NULL handling overhead)
3. Разработчик нажимает "Предложить оптимизацию". ИИ QueryPie предлагает:
SELECT
c.customer_name,
SUM(o.total) as revenue
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id -- Добавить INNER
WHERE c.country = 'US'
AND o.total IS NOT NULL -- Добавить фильтр
GROUP BY c.customer_name
ORDER BY revenue DESC
LIMIT 100;
4. Новые метрики:
Execution time: 1.2s (19x faster)
Rows scanned: 234K (22x less)
Cost: $0.02 (22x cheaper)
5. Показывается сравнение результатов.
6. При нажатии кнопки «Принять оптимизацию» исходный запрос помечается как устаревший, команда уведомляется об улучшении.
Когда использовать
- Несколько человек пишут SQL-запросы;
- Требуется совместная оптимизация;
- Необходимо версионирование запросов;
- Предпочитаете веб-IDE локальным инструментам.
Когда отказаться
- Разработчик-одиночка (функции совместной работы не используются);
- Довольны текущей IDE (pgAdmin, DBeaver и т. д.);
- Очень ограниченный бюджет ($15-50 на пользователя в месяц).
Скрытая функция
Автоматическое обнаружение регрессии производительности запросов. QueryPie отслеживает каждое выполнение запроса и может предоставлять динамику производительности запроса во времени и рекомендации по улучшению.
С осторожностью
QueryPie хранит результаты запросов. Соображения конфиденциальности:
- Запросы выполняются через прокси QueryPie;
- Результаты кэшируются для обмена;
- Может содержать конфиденциальные данные.
Конфигурация:
- Установить политику хранения данных (по умолчанию 7 дней);
- Исключить столбцы с персональными данными из кэширования;
- Использовать локальное развёртывание для конфиденциальных данных;
- Перед развёртыванием командой проверить политики безопасности.
Источник: https://medium.com/@reliabledataengineering/15-sql-optimization-tools-that-make-queries-10x-faster-8629ac451d97
👍3
День 2653. #Оффтоп
История Архитектур Приложений в .NET. Начало
Ранние годы
Классический ASP / «Спагетти-код» (конец 1990-х). До появления .NET веб-приложения Microsoft создавались с помощью классического ASP — скриптового языка VBScript, смешанного с HTML. Реальной архитектуры не было; бизнес-логика, доступ к данным и представления были переплетены между собой. От этого отказались, потому что это было неподдерживаемо, нетестируемо и немасштабируемо.
Web Forms (2002–~2012). ASP.NET Web Forms был первым крупным веб-фреймворком .NET от Microsoft. Он пытался сделать веб-разработку похожей на разработку настольных приложений, абстрагируя HTTP с помощью модели, основанной на состоянии и событиях (ViewState, postbacks, серверные элементы управления). Его заменили, потому что абстракция сильно «протекала». Она создавала раздутый HTML, делала модульное тестирование практически невозможным, противоречила тому, как на самом деле работает веб, и давала разработчикам очень мало контроля над отображаемым результатом. К тому времени, когда появились jQuery, а затем и фронтенд-фреймворки, Web Forms казался архаичным.
Основополагающие шаблоны (по-прежнему актуальны, но эволюционировали)
Многоуровневая/трёхуровневая архитектура. Классический многоуровневый подход: представление → бизнес-логика → доступ к данным. Это был основной принцип работы корпоративного .NET на протяжении многих лет. Теоретически каждый уровень мог быть развёрнут независимо. Он не устарел сам по себе, но был усовершенствован. Проблема заключалась в том, что на практике команды часто получали «анемичный» бизнес-слой, который просто передавал данные, и тесную связь между слоями через общие DTO или модели Entity Framework, просачивающиеся вверх.
MVC (Модель-Представление-Контроллер). ASP.NET MVC был запущен примерно в 2009 году как «противоядие» от Web Forms. Он использовал HTTP, предоставлял разработчикам контроль над HTML, позволял проводить модульное тестирование и правильно соблюдал принцип разделения ответственности. MVC по-прежнему широко используется в ASP.NET Core, хотя Razor Pages и Minimal API стали использоваться в более простых сценариях. MVC остается предпочтительным вариантом для сложных веб-приложений с множеством контроллеров и представлений.
MVVM (Модель-Представление-Модель Представления). Этот шаблон стал доминирующим в настольных приложениях WPF и Silverlight, а позже и в Xamarin. Модель Представления (ViewModel) предоставляет данные и команды, к которым представление привязывается декларативно. MVVM по-прежнему является основным шаблоном для .NET MAUI (преемника Xamarin.Forms) и WPF. Silverlight мёртв, но концепция MVVM процветает.
Сервисно-ориентированные шаблоны
WCF (Windows Communication Foundation). Представлял собой унифицированную платформу Microsoft для создания сервисно-ориентированных приложений — SOAP, REST, именованные каналы, TCP, очереди сообщений — всё в рамках одной модели программирования. Был невероятно мощным и таким же сложным. Конфигурация была печально известна своей сложностью (бесконечные XML-файлы). WCF был вытеснен, потому что отрасль перешла к легковесным REST/HTTP API, и сложность WCF не была оправдана для большинства вариантов использования. ASP.NET Web API, а позже и ASP.NET Core, заняли его место. Существует поддерживаемый сообществом проект CoreWCF для сценариев миграции, но новые проекты почти никогда не выбирают WCF.
Web API / REST ASP.NET Web API (2012), а затем и ASP.NET Core сделали создание HTTP API простым. Теперь это стандарт для взаимодействия сервисов в .NET. Минимальные API в .NET 6+ ещё больше упростили его для небольших сервисов.
gRPC. Для взаимодействия между сервисами, где важна производительность, gRPC (поддерживается нативно в ASP.NET Core) предлагает бинарную сериализацию через Protocol Buffers, потоковую передачу HTTP/2 и строгие контракты. Это современная замена WCF в сценариях взаимодействия между сервисами, где не требуется совместимость с браузерами.
Окончание следует…
Источник: https://todayamerican.medium.com/net-c-application-architecture-over-time-7218f0470a02
История Архитектур Приложений в .NET. Начало
Ранние годы
Классический ASP / «Спагетти-код» (конец 1990-х). До появления .NET веб-приложения Microsoft создавались с помощью классического ASP — скриптового языка VBScript, смешанного с HTML. Реальной архитектуры не было; бизнес-логика, доступ к данным и представления были переплетены между собой. От этого отказались, потому что это было неподдерживаемо, нетестируемо и немасштабируемо.
Web Forms (2002–~2012). ASP.NET Web Forms был первым крупным веб-фреймворком .NET от Microsoft. Он пытался сделать веб-разработку похожей на разработку настольных приложений, абстрагируя HTTP с помощью модели, основанной на состоянии и событиях (ViewState, postbacks, серверные элементы управления). Его заменили, потому что абстракция сильно «протекала». Она создавала раздутый HTML, делала модульное тестирование практически невозможным, противоречила тому, как на самом деле работает веб, и давала разработчикам очень мало контроля над отображаемым результатом. К тому времени, когда появились jQuery, а затем и фронтенд-фреймворки, Web Forms казался архаичным.
Основополагающие шаблоны (по-прежнему актуальны, но эволюционировали)
Многоуровневая/трёхуровневая архитектура. Классический многоуровневый подход: представление → бизнес-логика → доступ к данным. Это был основной принцип работы корпоративного .NET на протяжении многих лет. Теоретически каждый уровень мог быть развёрнут независимо. Он не устарел сам по себе, но был усовершенствован. Проблема заключалась в том, что на практике команды часто получали «анемичный» бизнес-слой, который просто передавал данные, и тесную связь между слоями через общие DTO или модели Entity Framework, просачивающиеся вверх.
MVC (Модель-Представление-Контроллер). ASP.NET MVC был запущен примерно в 2009 году как «противоядие» от Web Forms. Он использовал HTTP, предоставлял разработчикам контроль над HTML, позволял проводить модульное тестирование и правильно соблюдал принцип разделения ответственности. MVC по-прежнему широко используется в ASP.NET Core, хотя Razor Pages и Minimal API стали использоваться в более простых сценариях. MVC остается предпочтительным вариантом для сложных веб-приложений с множеством контроллеров и представлений.
MVVM (Модель-Представление-Модель Представления). Этот шаблон стал доминирующим в настольных приложениях WPF и Silverlight, а позже и в Xamarin. Модель Представления (ViewModel) предоставляет данные и команды, к которым представление привязывается декларативно. MVVM по-прежнему является основным шаблоном для .NET MAUI (преемника Xamarin.Forms) и WPF. Silverlight мёртв, но концепция MVVM процветает.
Сервисно-ориентированные шаблоны
WCF (Windows Communication Foundation). Представлял собой унифицированную платформу Microsoft для создания сервисно-ориентированных приложений — SOAP, REST, именованные каналы, TCP, очереди сообщений — всё в рамках одной модели программирования. Был невероятно мощным и таким же сложным. Конфигурация была печально известна своей сложностью (бесконечные XML-файлы). WCF был вытеснен, потому что отрасль перешла к легковесным REST/HTTP API, и сложность WCF не была оправдана для большинства вариантов использования. ASP.NET Web API, а позже и ASP.NET Core, заняли его место. Существует поддерживаемый сообществом проект CoreWCF для сценариев миграции, но новые проекты почти никогда не выбирают WCF.
Web API / REST ASP.NET Web API (2012), а затем и ASP.NET Core сделали создание HTTP API простым. Теперь это стандарт для взаимодействия сервисов в .NET. Минимальные API в .NET 6+ ещё больше упростили его для небольших сервисов.
gRPC. Для взаимодействия между сервисами, где важна производительность, gRPC (поддерживается нативно в ASP.NET Core) предлагает бинарную сериализацию через Protocol Buffers, потоковую передачу HTTP/2 и строгие контракты. Это современная замена WCF в сценариях взаимодействия между сервисами, где не требуется совместимость с браузерами.
Окончание следует…
Источник: https://todayamerican.medium.com/net-c-application-architecture-over-time-7218f0470a02
👍9👎1
День 2654. #Оффтоп
История Архитектур Приложений в .NET. Окончание
Начало
Современные архитектурные стили
Чистая/Луковая/Гексагональная архитектура. Это вариации одной идеи: инвертирование направления зависимостей, чтобы доменная/бизнес-логика находилась в центре, без зависимостей от инфраструктуры. Инфраструктура (БД, файловая система, внешние API) зависит от домена через абстракции (интерфейсы), а не наоборот. Это вытеснило наивный многоуровневый подход, поскольку делает бизнес-логику действительно тестируемой и заменяемой. Чистая архитектура (популяризированная Робертом Мартином и в .NET шаблоном Джейсона Тейлора) — вероятно, самая распространённая «серьёзная» архитектура для приложений .NET сегодня.
CQRS (Разделение Ответственности Команд и Запросов). Разделяет операции чтения (запросы) и записи (команды). Часто используется в сочетании с MediatR в .NET. Добавляет сложности, поэтому лучше всего подходит для областей, где шаблоны чтения и записи значительно различаются.
Источники событий (Event Sourcing). Вместо хранения текущего состояния вы храните последовательность событий, которые привели к текущему состоянию. Часто используется в сочетании с CQRS. Мощный инструмент для журналов аудита и сложных доменов, но значительно усложняет систему. Это нишевый шаблон — ценный, когда он необходим, избыточный, когда нет.
Микросервисы. Разделение приложения на независимо развёртываемые сервисы, каждый из которых владеет собственными данными. В .NET имеет сильную поддержку благодаря ASP.NET Core, Docker, Kubernetes и .NET Aspire. Микросервисы вытеснили монолиты для больших команд и сложных областей, но наблюдается заметное движение против них, признающее, что микросервисы вносят сложность распределённых систем (сбои сети, конечная согласованность, операционные накладные расходы), с которой многие команды не готовы справиться.
Модульный монолит. Это набирающая популярность «разумная золотая середина» — единая развёртываемая система, внутренне организованная в чётко ограниченные модули с ясными интерфейсами между ними. Вы получаете большинство организационных преимуществ микросервисов без проблем распределённых систем. Если позже потребуется выделить модуль в отдельный сервис, границы уже будут четко определены.
Шаблоны для настольных/клиентских приложений
Windows Forms (WinForms) — всё ещё поддерживается, все ещё используется, но в значительной степени считается устаревшим фреймворком для новой разработки. Имеет событийно-ориентированный, тесно связанный код пользовательского интерфейса.
WPF. Более современный фреймворк для настольных приложений с MVVM, привязкой данных и XAML. Всё ещё активно поддерживается и используется для сложных настольных приложений.
Xamarin → .NET MAUI — кроссплатформенное мобильное/настольное приложение. Xamarin снят с поддержки; MAUI — его преемник, использующий шаблон MVVM.
Blazor. Новейший фреймворк, позволяющий писать интерактивный веб-интерфейс на C# вместо JavaScript. Доступен в серверной (на основе SignalR) и WebAssembly версиях. Не заменяет все фронтенд-фреймворки, но привлекателен для команд, которые хотят оставаться в C#.
Итого: что на что заменено
Веб-интерфейс: Веб-формы → MVC → Razor Pages/минимальные API.
Сервисы: WCF → Web API → gRPC.
Структура проекта: Многоуровневая архитектура → Чистая архитектура.
Топология развёртывания: Монолит → Микросервисы → Модульный монолит.
Все эти переходы объединены одними и теми же несколькими факторами: тестируемость, автономность команд, гибкость развёртывания и честное признание издержек, связанных со сложностью. Каждое поколение училось на ошибках предыдущего, иногда чрезмерно корректировало свои действия, а затем занимало более сбалансированную позицию.
Источник: https://todayamerican.medium.com/net-c-application-architecture-over-time-7218f0470a02
История Архитектур Приложений в .NET. Окончание
Начало
Современные архитектурные стили
Чистая/Луковая/Гексагональная архитектура. Это вариации одной идеи: инвертирование направления зависимостей, чтобы доменная/бизнес-логика находилась в центре, без зависимостей от инфраструктуры. Инфраструктура (БД, файловая система, внешние API) зависит от домена через абстракции (интерфейсы), а не наоборот. Это вытеснило наивный многоуровневый подход, поскольку делает бизнес-логику действительно тестируемой и заменяемой. Чистая архитектура (популяризированная Робертом Мартином и в .NET шаблоном Джейсона Тейлора) — вероятно, самая распространённая «серьёзная» архитектура для приложений .NET сегодня.
CQRS (Разделение Ответственности Команд и Запросов). Разделяет операции чтения (запросы) и записи (команды). Часто используется в сочетании с MediatR в .NET. Добавляет сложности, поэтому лучше всего подходит для областей, где шаблоны чтения и записи значительно различаются.
Источники событий (Event Sourcing). Вместо хранения текущего состояния вы храните последовательность событий, которые привели к текущему состоянию. Часто используется в сочетании с CQRS. Мощный инструмент для журналов аудита и сложных доменов, но значительно усложняет систему. Это нишевый шаблон — ценный, когда он необходим, избыточный, когда нет.
Микросервисы. Разделение приложения на независимо развёртываемые сервисы, каждый из которых владеет собственными данными. В .NET имеет сильную поддержку благодаря ASP.NET Core, Docker, Kubernetes и .NET Aspire. Микросервисы вытеснили монолиты для больших команд и сложных областей, но наблюдается заметное движение против них, признающее, что микросервисы вносят сложность распределённых систем (сбои сети, конечная согласованность, операционные накладные расходы), с которой многие команды не готовы справиться.
Модульный монолит. Это набирающая популярность «разумная золотая середина» — единая развёртываемая система, внутренне организованная в чётко ограниченные модули с ясными интерфейсами между ними. Вы получаете большинство организационных преимуществ микросервисов без проблем распределённых систем. Если позже потребуется выделить модуль в отдельный сервис, границы уже будут четко определены.
Шаблоны для настольных/клиентских приложений
Windows Forms (WinForms) — всё ещё поддерживается, все ещё используется, но в значительной степени считается устаревшим фреймворком для новой разработки. Имеет событийно-ориентированный, тесно связанный код пользовательского интерфейса.
WPF. Более современный фреймворк для настольных приложений с MVVM, привязкой данных и XAML. Всё ещё активно поддерживается и используется для сложных настольных приложений.
Xamarin → .NET MAUI — кроссплатформенное мобильное/настольное приложение. Xamarin снят с поддержки; MAUI — его преемник, использующий шаблон MVVM.
Blazor. Новейший фреймворк, позволяющий писать интерактивный веб-интерфейс на C# вместо JavaScript. Доступен в серверной (на основе SignalR) и WebAssembly версиях. Не заменяет все фронтенд-фреймворки, но привлекателен для команд, которые хотят оставаться в C#.
Итого: что на что заменено
Веб-интерфейс: Веб-формы → MVC → Razor Pages/минимальные API.
Сервисы: WCF → Web API → gRPC.
Структура проекта: Многоуровневая архитектура → Чистая архитектура.
Топология развёртывания: Монолит → Микросервисы → Модульный монолит.
Все эти переходы объединены одними и теми же несколькими факторами: тестируемость, автономность команд, гибкость развёртывания и честное признание издержек, связанных со сложностью. Каждое поколение училось на ошибках предыдущего, иногда чрезмерно корректировало свои действия, а затем занимало более сбалансированную позицию.
Источник: https://todayamerican.medium.com/net-c-application-architecture-over-time-7218f0470a02
👍9👎1
День 2655. #ЧтоНовенького
Атрибут ConfigurationIgnore в .NET 11
В Microsoft.Extensions.Configuration атрибут ConfigurationKeyNameAttribute существует с .NET 6 и позволяет переименовывать ключ, к которому привязано свойство. Но официального способа указать «не привязывать это свойство вообще» никогда не было. Этот пробел наконец-то устранён в .NET 11 с помощью ConfigurationIgnoreAttribute.
Проблема
Допустим, у вас есть класс параметров, который выглядит примерно так:
DefaultFormat извлекается из DefaultFormatSection после некоторой постобработки. Его никогда не следует заполнять напрямую связывателем. Но поскольку у него есть публичные геттер и сеттер, система конфигурации пытается его связать.
Единственным обходным путём до .NET 11 было злоупотребление ConfigurationKeyNameAttribute с намеренно некорректным ключом:
Это ненадёжно, вводит в заблуждение и создаёт странный код в вашей схеме конфигурации.
Решение
.NET 11 добавляет специальный атрибут ConfigurationIgnoreAttribute в Microsoft.Extensions.Configuration. Просто добавляем атрибут к тому свойству, которое не должно быть привязано:
Ещё один распространённый сценарий
Постобработка — наиболее очевидный, но не единственный вариант использования. Ещё один классический пример — парсинг исходного значения:
Можно ли просто использовать [JsonIgnore]?
Нет, в этом и проблема. System.Text.Json.Serialization.JsonIgnoreAttribute не влияет на связыватель конфигурации.
Замечание: начиная с .NET 9, связыватель уже пропускает свойства только для чтения (только-get/только-init при отсутствии соответствующего раздела). Предлагаемый здесь атрибут предназначен для свойств, доступных для записи, которые могут быть связаны, но не должны быть связаны.
См. также обсуждение проблемы на GitHub.
Источник: https://steven-giesel.com/blogPost/18a61100-6074-43ac-86ae-573ad66a2c8a/configurationignoreattribute-in-net-11
Атрибут ConfigurationIgnore в .NET 11
В Microsoft.Extensions.Configuration атрибут ConfigurationKeyNameAttribute существует с .NET 6 и позволяет переименовывать ключ, к которому привязано свойство. Но официального способа указать «не привязывать это свойство вообще» никогда не было. Этот пробел наконец-то устранён в .NET 11 с помощью ConfigurationIgnoreAttribute.
Проблема
Допустим, у вас есть класс параметров, который выглядит примерно так:
public class CsvIngestionOptions
{
// Вычисляется после загрузки – НЕ должен идти из конфигурации
public CsvFormatOptions DefaultFormat { get; set; }
// Раздел конфигурации, который мы привязываем и обрабатываем
public IConfigurationSection? DefaultFormatSection { get; set; }
// Вызывается после загрузки конфигурации, чтобы применить постобработку и значения по умолчанию
internal void OnConfigurationLoaded()
{
DefaultFormat = new CsvFormatOptions();
DefaultFormatSection?.Bind(DefaultFormat);
DefaultFormat.EnsureEncodingDefined();
}
}
DefaultFormat извлекается из DefaultFormatSection после некоторой постобработки. Его никогда не следует заполнять напрямую связывателем. Но поскольку у него есть публичные геттер и сеттер, система конфигурации пытается его связать.
Единственным обходным путём до .NET 11 было злоупотребление ConfigurationKeyNameAttribute с намеренно некорректным ключом:
// Костыль
[ConfigurationKeyName("__ignored_" + nameof(DefaultFormat))]
public CsvFormatOptions DefaultFormat { get; set; }
Это ненадёжно, вводит в заблуждение и создаёт странный код в вашей схеме конфигурации.
Решение
.NET 11 добавляет специальный атрибут ConfigurationIgnoreAttribute в Microsoft.Extensions.Configuration. Просто добавляем атрибут к тому свойству, которое не должно быть привязано:
public class CsvIngestionOptions
{
[ConfigurationIgnore]
public CsvFormatOptions DefaultFormat { get; set; }
[ConfigurationKeyName(nameof(DefaultFormat))]
public IConfigurationSection? DefaultFormatSection { get; set; }
// …
}
Ещё один распространённый сценарий
Постобработка — наиболее очевидный, но не единственный вариант использования. Ещё один классический пример — парсинг исходного значения:
public class FeatureFlags
{
public string RawFlags { get; set; } = string.Empty;
[ConfigurationIgnore]
public IReadOnlyDictionary<string, bool>
ParsedFlags { get; set; }
= new Dictionary<string, bool>();
}
Можно ли просто использовать [JsonIgnore]?
Нет, в этом и проблема. System.Text.Json.Serialization.JsonIgnoreAttribute не влияет на связыватель конфигурации.
Замечание: начиная с .NET 9, связыватель уже пропускает свойства только для чтения (только-get/только-init при отсутствии соответствующего раздела). Предлагаемый здесь атрибут предназначен для свойств, доступных для записи, которые могут быть связаны, но не должны быть связаны.
См. также обсуждение проблемы на GitHub.
Источник: https://steven-giesel.com/blogPost/18a61100-6074-43ac-86ae-573ad66a2c8a/configurationignoreattribute-in-net-11
День 2656. #SystemDesign101
Data Warehouse, Data Lake или Data Mesh
Хранение данных — это просто. Настоящая проблема — решить, где и как их организовать.
Хранилище данных (Data Warehouse) — это традиционный подход. Очищает и структурирует данные перед их сохранением. Запросы выполняются быстро, а отчёты остаются согласованными. Но добавление нового источника данных требует усилий, поскольку всё должно сначала соответствовать схеме.
Озеро данных (Data Lake) использует противоположный подход. Хранит все данные в необработанном виде, например, базы данных, журналы, изображения и видео. Обрабатывает их по мере необходимости. Гибкость — это здорово, но, если правила именования, форматирования и владения данными не установлены должным образом, вы получите дублирующиеся, устаревшие и недокументированные данные, которыми трудно управлять.
Сетка данных (Data Mesh) переносит владение данными от центральной команды к отдельным подразделениям. Например, отдел продаж публикует данные о продажах, а финансовый отдел — финансовые данные. Общие стандарты обеспечивают совместимость между командами.
Это хорошо работает в крупных организациях. Но для этого каждой команде необходимы подходящие люди и процессы для управления качеством данных, документацией и доступом к ним, что является непростой задачей.
На практике многие компании используют несколько подходов. Они используют хранилище данных для панелей мониторинга и отчетности, озеро данных для рабочих нагрузок машинного обучения и начинают применять принципы сетевой архитектуры по мере роста команд.
Источник: https://bytebytego.com/
Data Warehouse, Data Lake или Data Mesh
Хранение данных — это просто. Настоящая проблема — решить, где и как их организовать.
Хранилище данных (Data Warehouse) — это традиционный подход. Очищает и структурирует данные перед их сохранением. Запросы выполняются быстро, а отчёты остаются согласованными. Но добавление нового источника данных требует усилий, поскольку всё должно сначала соответствовать схеме.
Озеро данных (Data Lake) использует противоположный подход. Хранит все данные в необработанном виде, например, базы данных, журналы, изображения и видео. Обрабатывает их по мере необходимости. Гибкость — это здорово, но, если правила именования, форматирования и владения данными не установлены должным образом, вы получите дублирующиеся, устаревшие и недокументированные данные, которыми трудно управлять.
Сетка данных (Data Mesh) переносит владение данными от центральной команды к отдельным подразделениям. Например, отдел продаж публикует данные о продажах, а финансовый отдел — финансовые данные. Общие стандарты обеспечивают совместимость между командами.
Это хорошо работает в крупных организациях. Но для этого каждой команде необходимы подходящие люди и процессы для управления качеством данных, документацией и доступом к ним, что является непростой задачей.
На практике многие компании используют несколько подходов. Они используют хранилище данных для панелей мониторинга и отчетности, озеро данных для рабочих нагрузок машинного обучения и начинают применять принципы сетевой архитектуры по мере роста команд.
Источник: https://bytebytego.com/
👎1
День 2657. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.
33. Разработка веб-API
«Расскажите, как бы вы спроектировали и реализовали веб-API, используя возможности минимальных API? Опишите шаги, необходимые для создания минимального API, и как это упрощает разработку API по сравнению с более ранними версиями Web API».
Хороший ответ
Минимальные API обеспечивают упрощённый подход к созданию высокопроизводительных, легковесных HTTP API. Вот как я бы спроектировал и реализовал минимальный API.
1. Создать новый проект Web API можно с помощью команды:
2. В Program.cs создадим нужные конечные точки. Минимальные API используют лямбда-выражения для обработки запросов непосредственно в коде, что упрощает маршрутизацию и настройку контроллера:
3. Настроим необходимые сервисы, такие как контекст БД, в настройках конструктора:
4. Тестировать API можно с помощью Postman или Swagger. .NET поддерживает Swagger по умолчанию:
Преимущества минимальных API
- Уменьшение шаблонного кода: устраняет необходимость в отдельных контроллерах, делая кодовую базу проще и меньше.
- Простота использования: упрощает настройку маршрутизации, облегчая создание и управление API.
- Производительность: предлагает легковесную альтернативу традиционным MVC-архитектурам, потенциально повышая производительность. Проекты минимальных API могут быть скомпилированы с использованием нативного AOT для ещё большей производительности.
Этот подход использует новейшие возможности .NET для эффективного создания API, фокусируясь на простоте и производительности.
Часто встречающийся плохой ответ
«Минимальные API подходят только для тестов и самых простых проектов и не предлагают ничего такого, чего не могли бы сделать контроллеры. Лучше придерживаться стандартного шаблона MVC».
Почему это неверно
- Сопротивление новым функциям: этот ответ демонстрирует сопротивление внедрению новых фреймворков и улучшений в технологиях. Минимальные API были введены для упрощения и ускорения разработки простых API, и они специально разработаны для улучшения опыта разработчиков и производительности в подходящих сценариях.
- Непонимание сценария использования: в ответе не учитывается, что минимальные API идеально подходят для приложений, которые не требуют полной структуры MVC, особенно для микросервисов или простых CRUD-операций.
- Отсутствие гибкости: ответ указывает на отсутствие гибкости в применении новых методов, которые могут обеспечить повышение эффективности в конкретных сценариях использования.
Этот ответ часто проистекает из недостаточного понимания конкретных преимуществ новых функций или из комфорта с устоявшимися шаблонами, которые не всегда могут быть наиболее эффективным выбором в каждом сценарии.
Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.
33. Разработка веб-API
«Расскажите, как бы вы спроектировали и реализовали веб-API, используя возможности минимальных API? Опишите шаги, необходимые для создания минимального API, и как это упрощает разработку API по сравнению с более ранними версиями Web API».
Хороший ответ
Минимальные API обеспечивают упрощённый подход к созданию высокопроизводительных, легковесных HTTP API. Вот как я бы спроектировал и реализовал минимальный API.
1. Создать новый проект Web API можно с помощью команды:
dotnet new webapi2. В Program.cs создадим нужные конечные точки. Минимальные API используют лямбда-выражения для обработки запросов непосредственно в коде, что упрощает маршрутизацию и настройку контроллера:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", async (AppDbContext db) =>
await db.Products.ToListAsync());
app.MapGet("/products/{id}",
async (int id, AppDbContext db) =>
await db.Products.FindAsync(id) is Product product ?
Results.Ok(product) : Results.NotFound());
app.MapPost("/products",
async (Product product, AppDbContext db) =>
{
db.Products.Add(product);
await db.SaveChangesAsync();
return Results.Created($"/products/{product.Id}", product);
});
//…
app.MapDelete("/products/{id}",
async (int id, AppDbContext db) =>
{
if (await db.Products.FindAsync(id) is Product product)
{
db.Products.Remove(product);
await db.SaveChangesAsync();
return Results.Ok(product);
}
return Results.NotFound();
});
app.Run();
3. Настроим необходимые сервисы, такие как контекст БД, в настройках конструктора:
builder.Services.AddDbContext<AppDbContext>(opts =>
opts.UseSqlServer(builder.Configuration
.GetConnectionString("DefaultConnection")));
4. Тестировать API можно с помощью Postman или Swagger. .NET поддерживает Swagger по умолчанию:
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
Преимущества минимальных API
- Уменьшение шаблонного кода: устраняет необходимость в отдельных контроллерах, делая кодовую базу проще и меньше.
- Простота использования: упрощает настройку маршрутизации, облегчая создание и управление API.
- Производительность: предлагает легковесную альтернативу традиционным MVC-архитектурам, потенциально повышая производительность. Проекты минимальных API могут быть скомпилированы с использованием нативного AOT для ещё большей производительности.
Этот подход использует новейшие возможности .NET для эффективного создания API, фокусируясь на простоте и производительности.
Часто встречающийся плохой ответ
«Минимальные API подходят только для тестов и самых простых проектов и не предлагают ничего такого, чего не могли бы сделать контроллеры. Лучше придерживаться стандартного шаблона MVC».
Почему это неверно
- Сопротивление новым функциям: этот ответ демонстрирует сопротивление внедрению новых фреймворков и улучшений в технологиях. Минимальные API были введены для упрощения и ускорения разработки простых API, и они специально разработаны для улучшения опыта разработчиков и производительности в подходящих сценариях.
- Непонимание сценария использования: в ответе не учитывается, что минимальные API идеально подходят для приложений, которые не требуют полной структуры MVC, особенно для микросервисов или простых CRUD-операций.
- Отсутствие гибкости: ответ указывает на отсутствие гибкости в применении новых методов, которые могут обеспечить повышение эффективности в конкретных сценариях использования.
Этот ответ часто проистекает из недостаточного понимания конкретных преимуществ новых функций или из комфорта с устоявшимися шаблонами, которые не всегда могут быть наиболее эффективным выбором в каждом сценарии.
Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👎11👍2
День 2658. #Карьера
Дорожная Карта .NET Разработчика в 2026. Начало
Чтобы стать успешным .NET разработчиком, не нужно запоминать фреймворки или гнаться за каждым новым релизом. Необходимо сформировать прочные базовые знания, выбрать правильную специализацию и постепенно развиваться до уровня, позволяющего проектировать, создавать и поддерживать готовые к производству системы. Эта дорожная карта призвана помочь вам уверенно продвигаться вперёд, независимо от того, начинаете ли вы свой путь или уже работаете в экосистеме .NET.
I. Основы (обязательно)
Перед специализацией каждый .NET-разработчик должен освоить общий набор фундаментальных принципов. Их пропуск приводит к хрупкости знаний и медленному карьерному росту.
1. Основы разработки
- Как работает веб (запросы, ответы, методы HTTP);
- Базовые концепции работы с сетью;
- Принципы чистого кода и ООП;
- Системы контроля версий Git (ветвления, коммиты, пул-реквесты).
Эти навыки применимы везде, а не только в .NET.
2. Основы .NET и C#
- Современный синтаксис и особенности языка C#;
- Концепции среды выполнения .NET;
- Использование CLI для сборки и запуска проектов;
- Уверенная работа в IDE (Visual Studio, VS Code, Rider).
Не спешите с этим шагом. Прочные основы здесь окупятся на долгие годы.
3. Создание первого бэкенда
- Создание простого веб-API;
- Понимание маршрутизации, контроллеров и внедрения зависимостей;
- Подключение к реляционной БД;
- Выполнение базовых CRUD-операций;
- Использование ORM и понимание, что происходит «под капотом».
Цель — не совершенство, а уверенность.
4. ИИ как помощник в программировании
Инструменты ИИ могут ускорить повторяющиеся задачи, предложить решения и помочь быстро исследовать новые паттерны. Разбирайте генерируемый код, проверяйте его и интегрируйте продуманно. Думайте об ИИ как о помощнике, который повышает производительность, а не о чём-то, что пишет код за вас. Чем прочнее ваши базовые знания, тем эффективнее вы сможете использовать ИИ, не теряя контроля. Используйте ИИ для:
- Изучения идей;
- Повышения продуктивности;
- Быстрого выявления закономерностей.
II. Выбор направления
Настало время специализации. Не нужно изучать всё, нужно изучать то, что соответствует вашим целям.
1. Путь бэкенд-разработчика
Работа с API, данными, производительностью и масштабируемостью.
Ключевые навыки:
- Проектирование API и лучшие практики;
- Аутентификация и авторизация;
- Обработка ошибок и логирование;
- Валидация и чистая архитектура;
- Кэширование и оптимизация производительности;
- Фоновая обработка;
- Модульное и интеграционное тестирование.
Бэкенд-разработчики отвечают за надёжность и корректность — здесь глубина знаний важнее широты.
2. Путь Blazor-разработчика
Создание интерактивных веб-приложений на C#.
Ключевые навыки:
- Разработка компонентного UI;
- Маршрутизация и проектирование компоновки;
- Обработка и валидация форм;
- Стратегии управления состоянием;
- Фреймворки и стилизация UI;
- Основы JavaScript-интеропа;
- Тестирование компонентов UI.
Этот путь хорошо подходит для корпоративных приложений, панелей мониторинга и внутренних инструментов.
3. Путь фулстек-разработчика
Объединение бэкенд API с фронтенд-приложениями.
Ключевые навыки:
- Проектирование API;
- Создание UI, использующего API;
- Обработка сквозной аутентификации;
- Управление общими моделями и контрактами;
- Понимание как производительности, так и пользовательского опыта.
Этот путь требует баланса — не поверхностных знаний, а универсальности.
Окончание следует…
Источник: https://www.c-sharpcorner.com/article/a-practical-net-developer-roadmap-for-2026/
Дорожная Карта .NET Разработчика в 2026. Начало
Чтобы стать успешным .NET разработчиком, не нужно запоминать фреймворки или гнаться за каждым новым релизом. Необходимо сформировать прочные базовые знания, выбрать правильную специализацию и постепенно развиваться до уровня, позволяющего проектировать, создавать и поддерживать готовые к производству системы. Эта дорожная карта призвана помочь вам уверенно продвигаться вперёд, независимо от того, начинаете ли вы свой путь или уже работаете в экосистеме .NET.
I. Основы (обязательно)
Перед специализацией каждый .NET-разработчик должен освоить общий набор фундаментальных принципов. Их пропуск приводит к хрупкости знаний и медленному карьерному росту.
1. Основы разработки
- Как работает веб (запросы, ответы, методы HTTP);
- Базовые концепции работы с сетью;
- Принципы чистого кода и ООП;
- Системы контроля версий Git (ветвления, коммиты, пул-реквесты).
Эти навыки применимы везде, а не только в .NET.
2. Основы .NET и C#
- Современный синтаксис и особенности языка C#;
- Концепции среды выполнения .NET;
- Использование CLI для сборки и запуска проектов;
- Уверенная работа в IDE (Visual Studio, VS Code, Rider).
Не спешите с этим шагом. Прочные основы здесь окупятся на долгие годы.
3. Создание первого бэкенда
- Создание простого веб-API;
- Понимание маршрутизации, контроллеров и внедрения зависимостей;
- Подключение к реляционной БД;
- Выполнение базовых CRUD-операций;
- Использование ORM и понимание, что происходит «под капотом».
Цель — не совершенство, а уверенность.
4. ИИ как помощник в программировании
Инструменты ИИ могут ускорить повторяющиеся задачи, предложить решения и помочь быстро исследовать новые паттерны. Разбирайте генерируемый код, проверяйте его и интегрируйте продуманно. Думайте об ИИ как о помощнике, который повышает производительность, а не о чём-то, что пишет код за вас. Чем прочнее ваши базовые знания, тем эффективнее вы сможете использовать ИИ, не теряя контроля. Используйте ИИ для:
- Изучения идей;
- Повышения продуктивности;
- Быстрого выявления закономерностей.
II. Выбор направления
Настало время специализации. Не нужно изучать всё, нужно изучать то, что соответствует вашим целям.
1. Путь бэкенд-разработчика
Работа с API, данными, производительностью и масштабируемостью.
Ключевые навыки:
- Проектирование API и лучшие практики;
- Аутентификация и авторизация;
- Обработка ошибок и логирование;
- Валидация и чистая архитектура;
- Кэширование и оптимизация производительности;
- Фоновая обработка;
- Модульное и интеграционное тестирование.
Бэкенд-разработчики отвечают за надёжность и корректность — здесь глубина знаний важнее широты.
2. Путь Blazor-разработчика
Создание интерактивных веб-приложений на C#.
Ключевые навыки:
- Разработка компонентного UI;
- Маршрутизация и проектирование компоновки;
- Обработка и валидация форм;
- Стратегии управления состоянием;
- Фреймворки и стилизация UI;
- Основы JavaScript-интеропа;
- Тестирование компонентов UI.
Этот путь хорошо подходит для корпоративных приложений, панелей мониторинга и внутренних инструментов.
3. Путь фулстек-разработчика
Объединение бэкенд API с фронтенд-приложениями.
Ключевые навыки:
- Проектирование API;
- Создание UI, использующего API;
- Обработка сквозной аутентификации;
- Управление общими моделями и контрактами;
- Понимание как производительности, так и пользовательского опыта.
Этот путь требует баланса — не поверхностных знаний, а универсальности.
Окончание следует…
Источник: https://www.c-sharpcorner.com/article/a-practical-net-developer-roadmap-for-2026/
👍8👎2
День 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
👍17
День 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
👍15👎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
👍9👎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/
👍14
День 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/
👍7
День 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/
👍3
День 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
👍3👎1