diff --git a/src/en/clean-copy/02-Section I. The API Design/03.md b/src/en/clean-copy/02-Section I. The API Design/03.md index d4b0a84..ce53b16 100644 --- a/src/en/clean-copy/02-Section I. The API Design/03.md +++ b/src/en/clean-copy/02-Section I. The API Design/03.md @@ -62,7 +62,6 @@ GET /v1/orders/{id} } ``` - Then a developer just needs to compare two numbers to find out whether the order is ready. This solution intuitively looks bad, and it really is: it violates all the abovementioned principles. diff --git a/src/ru/clean-copy/02-Раздел I. Проектирование API/03.md b/src/ru/clean-copy/02-Раздел I. Проектирование API/03.md index d472e1e..5e8b50a 100644 --- a/src/ru/clean-copy/02-Раздел I. Проектирование API/03.md +++ b/src/ru/clean-copy/02-Раздел I. Проектирование API/03.md @@ -41,6 +41,23 @@ GET /v1/orders/{id} И зададимся вопросом, каким образом разработчик определит, что заказ клиента готов. Допустим, мы сделаем так: добавим в рецепт лунго эталонный объём, а в состояние заказа — количество уже налитого кофе. Тогда разработчику нужно будет проверить совпадение этих двух цифр, чтобы убедиться, что кофе готов. +``` +GET /v1/recipes/lungo +→ +{ + … + "volume": "100ml" +} +``` +``` +GET /v1/orders/{id} +→ +{ + … + "volume": "80ml" +} +``` + Такое решение выглядит интуитивно плохим, и это действительно так: оно нарушает все вышеперечисленные принципы. **Во-первых**, для решения задачи «заказать лунго» разработчику нужно обратиться к сущности «рецепт» и выяснить, что у каждого рецепта есть объём. Далее, нужно принять концепцию, что приготовление кофе заканчивается в тот момент, когда объём сравнялся с эталонным. Нет никакого способа об этой конвенции догадаться: она неочевидна и её нужно найти в документации. При этом никакой пользы для разработчика в этом знании нет. @@ -62,6 +79,22 @@ POST /v1/orders * разработчик, которому придётся поддержать эту функциональность, имеет высокие шансы сделать ошибку: добавив поддержку произвольного объёма кофе в код, работающий с `POST /v1/orders` нужно не забыть переписать код проверки готовности заказа; * мы получим классическую ситуацию, когда одно и то же поле (объём кофе) значит разные вещи в разных интерфейсах. В `GET /v1/recipes` поле «объём» теперь значит «объём, который будет запрошен, если не передать его явно в `POST /v1/orders`»; переименовать его в «объём по умолчанию» уже не получится, с этой проблемой теперь придётся жить. +Мы получим: +``` +GET /v1/orders/{id} +→ +{ + … + // Это текущий объём кофе, + // который наследует имя поля + // из старого интерфейса + "volume": "80ml", + // а это объём кофе, запрошенный + // пользователем + "volume_requested": "800ml" +} +``` + **В-третьих**, вся эта схема полностью неработоспособна, если разные модели кофемашин производят лунго разного объёма. Для решения задачи «объём лунго зависит от вида машины» нам придётся сделать совсем неприятную вещь: сделать рецепт зависимым от id машины. Тем самым мы начнём активно смешивать уровни абстракции: одной частью нашего API (рецептов) станет невозможно пользоваться без другой части (информации о кофемашинах). Что немаловажно, от разработчиков потребуется изменить логику своего приложения: если раньше они могли предлагать сначала выбрать объём, а потом кофемашину, то теперь им придётся полностью изменить этот шаг. Хорошо, допустим, мы поняли, как сделать плохо. Но как же тогда сделать *хорошо*? Разделение уровней абстракции должно происходить вдоль трёх направлений: