.NET Разработчик
6.63K subscribers
446 photos
4 videos
14 files
2.15K links
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2487. #ВопросыНаСобеседовании #Архитектура
Оптимизируем Генерацию Отчёта

«Пользователь нажимает кнопку в интерфейсе, чтобы создать отчёт в Excel или PDF. Создание отчёта занимает около пяти минут (время может быть произвольным). Пользователю приходится ждать завершения. Как бы вы оптимизировали этот процесс?»

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

Но даже это заставит пользователя ждать. Если браузер зависнет, пропадёт соединение, закроется вкладка, - начинай сначала. Проблема не в производительности, а в дизайне.

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

Правильнее было бы спросить не «Как это ускорить?», а «Почему пользователь вообще ждёт?»
Если что-то выполняется минуты (или часы, дни), это не должно блокировать пользователя. Это должно происходить в фоновом режиме, вне основного потока запросов, пока пользователь занимается своими делами.

Однако, не стоит забывать оптимизировать сам код. Запросы к БД, обработка данных и генерация файлов — всё это имеет значение. Возможно, где-то отсутствует индекс, неэффективный цикл или есть более удобная библиотека для создания файлов Excel. Но эти оптимизации — лишь часть решения, а не полная картина.

Как решить проблему долгого ожидания?
Оставим ту же кнопку «Сгенерировать отчёт», но при этом бэкенд примет запрос, сохранит его где-то (например, как запись о задании в БД) и сразу же вернёт управление. В этом суть создания асинхронных API. Затем задание принимается фоновым обработчиком.

В роли обработчика может выступать фоновый сервис, задание Quartz или даже функция AWS Lambda, активируемая сообщением из очереди. Он берёт на себя всю основную работу: извлечение данных, создание файла и его загрузку в хранилище, например, S3 или Azure Blob.

После готовности отчёта обработчик обновит статус задания на «завершено» и уведомит пользователя. Это может быть email со ссылкой для скачивания или сообщение SignalR в режиме реального времени, которое отображается в приложении, со ссылкой на безопасное скачивание сохранённого отчёта.

Теперь пользователю не приходится ждать выполнения длительного HTTP-запроса. Сервер не удерживает открытые соединения в течение нескольких минут. В случае сбоя попытка может быть автоматически повторена. Вы также можете отслеживать ход выполнения или отменить задание при необходимости. А если сто пользователей одновременно запросят отчёты, система может масштабироваться без зависаний.

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

Это интересный вопрос, т.к. он показывает, как люди думают.

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

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

Единственно правильного ответа не существует. Но есть большая разница между тем, кто сосредоточен только на коде, и тем, кто может спроектировать масштабируемую систему.

Источник: https://www.milanjovanovic.tech/blog/the-interview-question-that-changed-how-i-think-about-system-design
👍22
День 2488. #SystemDesign101
Как Разработать Хороший API
Хорошо спроектированный API кажется интуитивно понятным, он просто работает. Но за этой простотой кроется набор последовательных принципов проектирования, которые делают API предсказуемым, безопасным и масштабируемым.

Вот что отличает хорошие API от плохих.

1. Идемпотентность
GET, HEAD, PUT и DELETE должны быть идемпотентными. Отправка одного и того же запроса несколько раз даёт один и тот же результат и не содержит непреднамеренных побочных эффектов.

POST и PATCH не являются идемпотентными. Каждый вызов создаёт новый ресурс или каким-либо образом изменяет состояние. Используйте ключи идемпотентности, хранящиеся в Redis или в базе данных. Клиент отправляет один и тот же ключ с повторными попытками, сервер распознает его и возвращает исходный ответ, не обрабатывая его повторно.

2. Версионирование
При изменении контракта API меняйте его версию. Указывать версию можно в URL, строке запроса или в заголовках запроса.

3. Имена ресурсов, основанные на существительных
Ресурсы должны быть существительными, а не глаголами. "/api/products", а не "/api/getProducts".

4. Безопасность
Обеспечьте безопасность каждой конечной точки с помощью надлежащей аутентификации. Bearer-токены (например, JWT) включают заголовок, полезную нагрузку и подпись для проверки запросов. Всегда используйте HTTPS и проверяйте токены при каждом вызове.

5. Пагинация
При возврате больших наборов данных используйте параметры пагинации, например "?limit=10&offset=20", чтобы обеспечить эффективность и согласованность ответов.

См. также «REST vs RESTful. В чём Разница?»

Источник: https://blog.bytebytego.com
👍11
День 2489. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.

9. Асинхронное Программирование с async и await
«Можете объяснить, как ключевые слова async и await улучшают приложения .NET? Включите в свой ответ краткое объяснение работы этих ключевых слов и приведите пример сценария, в котором асинхронное программирование особенно полезно».

Хороший ответ
Ключевые слова async и await используются в C# для упрощения процесса написания асинхронного кода, что критически важно для выполнения неблокирующих операций. Ключевое слово async определяет метод как асинхронный, указывая, что метод содержит операции, которые могут потребовать ожидания завершения без блокировки вызывающего потока. Ключевое слово await затем используется внутри асинхронного метода для приостановки выполнения метода до завершения ожидаемой задачи, тем самым не блокируя основной поток выполнения.

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

Практический пример того, где async и await полезны, — это приложения с пользовательским интерфейсом, где критически важно поддерживать отзывчивость. Например, сетевой запрос на получение данных из веб-сервиса. Используя async и await, можно вызвать веб-сервис, не замораживая пользовательский интерфейс в ожидании ответа. Это достигается за счёт ожидания сетевой операции, что позволяет потоку пользовательского интерфейса продолжать обработку других событий, таких как пользовательский ввод, до тех пор, пока данные не будут готовы, как показано в следующем коде:
public async Task LoadDataAsync()
{
try
{
// Асинхронно получаем данные
var data = await GetDataAsync();
DisplayData(data);
}
catch (Exception ex)
{
// Обрабатываем ошибки получения данных
ShowError(ex.Message);
}
}

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

Часто встречающийся неудачный ответ:
«async добавляется просто, чтобы ускорить выполнение метода, и await для каждого вызова метода внутри него. Это приводит к одновременному выполнению всех методов.»

В этом ответе неверно истолковываются назначение и функции async и await.

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

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

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

Подобные ошибки обычно возникают из-за поверхностного понимания концепций асинхронного программирования, что приводит к неверным предположениям о том, как async и await влияют на выполнение программы.

Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👍6👎4