День 2488. #SystemDesign101
Как Разработать Хороший API
Хорошо спроектированный API кажется интуитивно понятным, он просто работает. Но за этой простотой кроется набор последовательных принципов проектирования, которые делают API предсказуемым, безопасным и масштабируемым.
Вот что отличает хорошие API от плохих.
1. Идемпотентность
GET, HEAD, PUT и DELETE должны быть идемпотентными. Отправка одного и того же запроса несколько раз даёт один и тот же результат и не содержит непреднамеренных побочных эффектов.
POST и PATCH не являются идемпотентными. Каждый вызов создаёт новый ресурс или каким-либо образом изменяет состояние. Используйте ключи идемпотентности, хранящиеся в Redis или в базе данных. Клиент отправляет один и тот же ключ с повторными попытками, сервер распознает его и возвращает исходный ответ, не обрабатывая его повторно.
2. Версионирование
При изменении контракта API меняйте его версию. Указывать версию можно в URL, строке запроса или в заголовках запроса.
3. Имена ресурсов, основанные на существительных
Ресурсы должны быть существительными, а не глаголами. "
4. Безопасность
Обеспечьте безопасность каждой конечной точки с помощью надлежащей аутентификации. Bearer-токены (например, JWT) включают заголовок, полезную нагрузку и подпись для проверки запросов. Всегда используйте HTTPS и проверяйте токены при каждом вызове.
5. Пагинация
При возврате больших наборов данных используйте параметры пагинации, например "
См. также «REST vs RESTful. В чём Разница?»
Источник: https://blog.bytebytego.com
Как Разработать Хороший 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
👍12
День 2489. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.
9. Асинхронное Программирование с async и await
«Можете объяснить, как ключевые слова async и await улучшают приложения .NET? Включите в свой ответ краткое объяснение работы этих ключевых слов и приведите пример сценария, в котором асинхронное программирование особенно полезно».
Хороший ответ
Ключевые слова async и await используются в C# для упрощения процесса написания асинхронного кода, что критически важно для выполнения неблокирующих операций. Ключевое слово async определяет метод как асинхронный, указывая, что метод содержит операции, которые могут потребовать ожидания завершения без блокировки вызывающего потока. Ключевое слово await затем используется внутри асинхронного метода для приостановки выполнения метода до завершения ожидаемой задачи, тем самым не блокируя основной поток выполнения.
Когда в асинхронном методе компилятор встречает ключевое слово await, он разбивает метод на две части. Первая часть - до ключевого слова await, а вторая часть – инструкции после ожидаемой операции - инкапсулируется в продолжение, которое выполняется после её завершения.
Практический пример того, где async и await полезны, — это приложения с пользовательским интерфейсом, где критически важно поддерживать отзывчивость. Например, сетевой запрос на получение данных из веб-сервиса. Используя async и await, можно вызвать веб-сервис, не замораживая пользовательский интерфейс в ожидании ответа. Это достигается за счёт ожидания сетевой операции, что позволяет потоку пользовательского интерфейса продолжать обработку других событий, таких как пользовательский ввод, до тех пор, пока данные не будут готовы, как показано в следующем коде:
Этот метод обеспечивает отзывчивость пользовательского интерфейса, улучшая пользовательский опыт, предотвращая зависание приложения во время длительных операций.
Часто встречающийся неудачный ответ:
«async добавляется просто, чтобы ускорить выполнение метода, и await для каждого вызова метода внутри него. Это приводит к одновременному выполнению всех методов.»
В этом ответе неверно истолковываются назначение и функции async и await.
- Заблуждение об улучшении скорости: добавление async не ускоряет выполнение метода. Вместо этого оно позволяет методу выполнять операции, не блокируя вызывающий поток, что может улучшить отзывчивость приложения, но не обязательно скорость его выполнения.
- Неправильное использование await: использование await для каждого метода без разбора невыгодно и может привести к ненужному усложнению и накладным расходам. await следует использовать обдуманно для управления асинхронными операциями, которые в противном случае блокировали бы критически важные потоки, такие как потоки пользовательского интерфейса или потоки ответа сервера.
- Непонимание параллелизма: утверждение о том, что все методы выполняются одновременно, путает асинхронное выполнение с параллельным. Асинхронное программирование заключается в освобождении текущего потока для выполнения другой работы в ожидании завершения операции, а не в одновременном выполнении нескольких операций.
Подобные ошибки обычно возникают из-за поверхностного понимания концепций асинхронного программирования, что приводит к неверным предположениям о том, как async и await влияют на выполнение программы.
Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
Марк Прайс предложил свой набор из 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
👍7👎4
День 2490. #ЧтоНовенького
Увеличиваем Тестовое Покрытие с GitHub Copilot Testing
Написание хороших тестов важно для надёжности ПО, но зачастую отнимает много времени и требует рутинных действий. Теперь GitHub Copilot Testing доступно в виде превью в Visual Studio. Эта новая возможность позволяет создавать модульные тесты на основе ИИ непосредственно в процессе разработки.
Copilot понимает структуру вашего кода, настройки вашего проекта и то, как должны выглядеть хорошие тесты. Нужны ли вам тесты для отдельного элемента, файла, всего проекта или даже всего решения, Copilot автоматически генерирует, собирает и запускает тесты в выбранной области. Результат? Быстрая обратная связь, меньше ошибок и больше уверенности в вашем коде.
Ключевые особенности
- Модульные тесты, генерируемые ИИ: автоматическое создание, сборка и запуск тестов для отдельных элементов, файлов, проектов или целых решений.
- Детерминированные, типобезопасные результаты: тесты основаны на семантике компилятора и языка, что обеспечивает согласованность и предсказуемость.
- Поддержка MSTest, xUnit и NUnit: работает с вашими существующими фреймворками и рабочими процессами.
- Интеллектуальная интеграция с Visual Studio: GitHub Copilot Testing интегрировано с Visual Studio, использует анализаторы Roslyn, MSBuild, интеграцию с системами проектов и Test Explorer для обеспечения бесперебойной настройки, выполнения и обнаружения тестов прямо из IDE.
- Автоматическое восстановление тестов: иногда не все тесты проходят сразу — и это нормально. В случае сбоя теста Copilot пытается исправить ошибку, перегенерировать тест и повторно запустить его, чтобы убедиться в его корректной работе
Начало работы
Необходимые условия: последняя сборка Visual Studio 2026 Insiders, код C# и лицензия GitHub Copilot.
Убедитесь, что функция GitHub Copilot Testing (Тестирование GitHub Copilot) включена в разделе Tools > Options > GitHub > Copilot > Testing (Инструменты > Параметры > GitHub > Copilot > Тестирование).
Откройте проект или решение и убедитесь, что сборка проходит без ошибок, чтобы упростить процесс. В окне чата Copilot используйте следующий запрос, чтобы запустить тестирование GitHub Copilot и начать генерацию тестов:
Где #target может быть именем члена, класса, файла, проекта, решения или #changes для git diff.
GitHub Copilot Testing запустит итеративный процесс. Copilot проанализирует ваш код и создаст тестовый проект, если его нет. Затем автоматически cгенерирует тесты, соберёт проект и запустит их.
В обозревателе тестов будут отображаться результаты по мере генерации тестов. После завершения генерации тестов GitHub Copilot Testing предоставит сводку в чате Copilot, которая включает в себя:
- Статистику о количестве созданных или изменённых тестов, файлов и проектов.
- Обзор покрытия до и после, чтобы вы могли легко оценить изменения в покрытии.
- Индикаторы качества отобразят состояние «успех/неудача», а также любые пропущенные или нестабильные случаи для отслеживания.
- Если есть пробелы в тестируемости, они будут перечислены вместе с практическими советами.
- Прямые ссылки на тестовые проекты и файлы.
Источник: https://devblogs.microsoft.com/dotnet/github-copilot-testing-for-dotnet/
Увеличиваем Тестовое Покрытие с GitHub Copilot Testing
Написание хороших тестов важно для надёжности ПО, но зачастую отнимает много времени и требует рутинных действий. Теперь GitHub Copilot Testing доступно в виде превью в Visual Studio. Эта новая возможность позволяет создавать модульные тесты на основе ИИ непосредственно в процессе разработки.
Copilot понимает структуру вашего кода, настройки вашего проекта и то, как должны выглядеть хорошие тесты. Нужны ли вам тесты для отдельного элемента, файла, всего проекта или даже всего решения, Copilot автоматически генерирует, собирает и запускает тесты в выбранной области. Результат? Быстрая обратная связь, меньше ошибок и больше уверенности в вашем коде.
Ключевые особенности
- Модульные тесты, генерируемые ИИ: автоматическое создание, сборка и запуск тестов для отдельных элементов, файлов, проектов или целых решений.
- Детерминированные, типобезопасные результаты: тесты основаны на семантике компилятора и языка, что обеспечивает согласованность и предсказуемость.
- Поддержка MSTest, xUnit и NUnit: работает с вашими существующими фреймворками и рабочими процессами.
- Интеллектуальная интеграция с Visual Studio: GitHub Copilot Testing интегрировано с Visual Studio, использует анализаторы Roslyn, MSBuild, интеграцию с системами проектов и Test Explorer для обеспечения бесперебойной настройки, выполнения и обнаружения тестов прямо из IDE.
- Автоматическое восстановление тестов: иногда не все тесты проходят сразу — и это нормально. В случае сбоя теста Copilot пытается исправить ошибку, перегенерировать тест и повторно запустить его, чтобы убедиться в его корректной работе
Начало работы
Необходимые условия: последняя сборка Visual Studio 2026 Insiders, код C# и лицензия GitHub Copilot.
Убедитесь, что функция GitHub Copilot Testing (Тестирование GitHub Copilot) включена в разделе Tools > Options > GitHub > Copilot > Testing (Инструменты > Параметры > GitHub > Copilot > Тестирование).
Откройте проект или решение и убедитесь, что сборка проходит без ошибок, чтобы упростить процесс. В окне чата Copilot используйте следующий запрос, чтобы запустить тестирование GitHub Copilot и начать генерацию тестов:
@Test #target
Где #target может быть именем члена, класса, файла, проекта, решения или #changes для git diff.
GitHub Copilot Testing запустит итеративный процесс. Copilot проанализирует ваш код и создаст тестовый проект, если его нет. Затем автоматически cгенерирует тесты, соберёт проект и запустит их.
В обозревателе тестов будут отображаться результаты по мере генерации тестов. После завершения генерации тестов GitHub Copilot Testing предоставит сводку в чате Copilot, которая включает в себя:
- Статистику о количестве созданных или изменённых тестов, файлов и проектов.
- Обзор покрытия до и после, чтобы вы могли легко оценить изменения в покрытии.
- Индикаторы качества отобразят состояние «успех/неудача», а также любые пропущенные или нестабильные случаи для отслеживания.
- Если есть пробелы в тестируемости, они будут перечислены вместе с практическими советами.
- Прямые ссылки на тестовые проекты и файлы.
Источник: https://devblogs.microsoft.com/dotnet/github-copilot-testing-for-dotnet/
День 2491. #ЗаметкиНаПолях
Паттерн Идемпотентный Потребитель. Начало
Распределенные системы по своей природе ненадёжны. Одна из ключевых проблем — гарантировать, что сообщения обрабатываются ровно один раз. Теоретически это невозможно гарантировать в большинстве систем. Если вы проектируете систему, предполагая, что каждое сообщение будет обработано ровно один раз, вы подвергаете себя риску неявного повреждения данных. Но мы можем спроектировать систему так, чтобы побочные эффекты применялись ровно один раз, используя паттерн «Идемпотентный потребитель».
Что может пойти не так при публикации
Предположим, сервис публикует событие при создании новой заметки:
Конкретная реализация издателя или брокера сообщений не важна.
Теперь представьте:
- Издатель отправляет сообщение брокеру.
- Брокер сохраняет его и отправляет ACK.
- Сбой в сети: ACK не доходит до производителя.
- Производитель по тайм-ауту повторяет попытку публикации.
- Теперь у брокера два события NoteCreated.
С точки зрения производителя, он действовал правильно. Но потребитель получил два события о создании одной и той же заметки.
И это только один из путей возникновения сбоя. Вы также можете получить дубликаты из-за:
- Повторных доставок брокером.
- Сбоев потребителя + повторных попыток.
Т.е. даже если вы всё сделали «правильно» на издателе, потребителю всё равно придётся защищаться.
Идемпотентность на стороне издателя (управляет брокер)
Многие брокеры сообщений поддерживают идемпотентную публикацию посредством дедупликации сообщений, если вы укажете уникальный идентификатор сообщения. Например, Azure Service Bus может обнаруживать дубликаты и игнорировать повторные публикации сообщений с тем же идентификатором в течение заданного периода. Amazon SQS и другие брокеры также предлагают аналогичные гарантии.
Не нужно заново изобретать эту логику в вашем приложении. Ключ к успеху — назначить каждому сообщению стабильный идентификатор, уникально отражающий отправляемое логическое событие. Например, при публикации события NoteCreated:
Если происходит сбой сети после отправки сообщения, приложение может повторить попытку. Но когда брокер обнаруживает тот же MessageId, он понимает, что это дубликат, и безопасно отбрасывает его. Вы получаете дедупликацию без каких-либо специальных таблиц отслеживания или дополнительного состояния в вашем сервисе.
Эта идемпотентность на уровне брокера решает широкий класс проблем на стороне производителя: повторные попытки сети, временные сбои и дублирующиеся публикации.
Она не обрабатывает повторные попытки потребителя, которые происходят при повторной доставке сообщений или сбое вашего сервиса во время обработки.
С этим поможет шаблон «Идемпотентный потребитель».
Продолжение следует…
Источник: https://www.milanjovanovic.tech/blog/the-idempotent-consumer-pattern-in-dotnet-and-why-you-need-it
Паттерн Идемпотентный Потребитель. Начало
Распределенные системы по своей природе ненадёжны. Одна из ключевых проблем — гарантировать, что сообщения обрабатываются ровно один раз. Теоретически это невозможно гарантировать в большинстве систем. Если вы проектируете систему, предполагая, что каждое сообщение будет обработано ровно один раз, вы подвергаете себя риску неявного повреждения данных. Но мы можем спроектировать систему так, чтобы побочные эффекты применялись ровно один раз, используя паттерн «Идемпотентный потребитель».
Что может пойти не так при публикации
Предположим, сервис публикует событие при создании новой заметки:
await publisher.PublishAsync(
new NoteCreated(note.Id, note.Title, note.Content));
Конкретная реализация издателя или брокера сообщений не важна.
Теперь представьте:
- Издатель отправляет сообщение брокеру.
- Брокер сохраняет его и отправляет ACK.
- Сбой в сети: ACK не доходит до производителя.
- Производитель по тайм-ауту повторяет попытку публикации.
- Теперь у брокера два события NoteCreated.
С точки зрения производителя, он действовал правильно. Но потребитель получил два события о создании одной и той же заметки.
И это только один из путей возникновения сбоя. Вы также можете получить дубликаты из-за:
- Повторных доставок брокером.
- Сбоев потребителя + повторных попыток.
Т.е. даже если вы всё сделали «правильно» на издателе, потребителю всё равно придётся защищаться.
Идемпотентность на стороне издателя (управляет брокер)
Многие брокеры сообщений поддерживают идемпотентную публикацию посредством дедупликации сообщений, если вы укажете уникальный идентификатор сообщения. Например, Azure Service Bus может обнаруживать дубликаты и игнорировать повторные публикации сообщений с тем же идентификатором в течение заданного периода. Amazon SQS и другие брокеры также предлагают аналогичные гарантии.
Не нужно заново изобретать эту логику в вашем приложении. Ключ к успеху — назначить каждому сообщению стабильный идентификатор, уникально отражающий отправляемое логическое событие. Например, при публикации события NoteCreated:
var message = new NoteCreated(
note.Id, note.Title, note.Content)
{
MessageId = Guid.NewGuid() // либо note.Id
};
await publisher.PublishAsync(message);
Если происходит сбой сети после отправки сообщения, приложение может повторить попытку. Но когда брокер обнаруживает тот же MessageId, он понимает, что это дубликат, и безопасно отбрасывает его. Вы получаете дедупликацию без каких-либо специальных таблиц отслеживания или дополнительного состояния в вашем сервисе.
Эта идемпотентность на уровне брокера решает широкий класс проблем на стороне производителя: повторные попытки сети, временные сбои и дублирующиеся публикации.
Она не обрабатывает повторные попытки потребителя, которые происходят при повторной доставке сообщений или сбое вашего сервиса во время обработки.
С этим поможет шаблон «Идемпотентный потребитель».
Продолжение следует…
Источник: https://www.milanjovanovic.tech/blog/the-idempotent-consumer-pattern-in-dotnet-and-why-you-need-it
👍6