Правила хорошего тона для API +8


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

О том, какие принципы и инструменты мы используем для добавления REST API к проектам, читайте под катом.

Существует множество фреймворков, ориентированных на разработку API. Особенно их много на NodeJS, но и на других языках – достаточно. Тем не менее, когда задача состоит в использовании существующего функционала и данных проекта, то менять его архитектуру в корне, переписывать всё на другом языке или фреймворке – нерационально. Мы пишем на своём фреймворке ZeroEngine, который ориентирован на высоконагруженные проекты и работает по принципу plug-in’ов. Кратко принцип работы ZeroEngine можно описать так: новый «модуль» можно встроить в любой уже существующий, а также перехватить управление выдачей в нужный момент.

Резюмируем вводные данные


Требуется написать REST API для сайта. Архитектура позволяет внедрить роутер и использовать существующий функционал полностью или частично.

При разработке в целом и, в частности, API, мы стараемся следовать законам логики и придерживаться семантических названий, методов и параметров. Вот примерный перечень внутренних требований, принятых в команде ZeroTech:

ДЕЙСТВИЕ /объект/идентификатор/метод


Метод и URL должны чётко описывать выполняемую методом API функцию. Строго говоря, при именовании метода API, метод запроса (GET, POST, PUT, DELETE) является в предложении «глаголом», а адрес представляет собой «путь» от общего к частному.

Например:

  • GET /images («получить изображения») – вернёт список изображений;
  • POST /image – опубликует изображение;
  • PUT /image/123 – «положит» переданное значение в изображение номер 123.

Мы сознательно разделяем image и images, чтобы было сразу понятно, что именно придёт в ответ на запрос – массив или единичный объект.

Семантические ошибки


Для сообщения об ошибке мы не назначаем свои коды, а пользуемся стандартным набором HTTP-кодов. Чтобы упростить обработку на стороне приложения, код дублируется в теле ответа и дополняется описанием. Мы убеждены, что снабжать разработчика приложения длинным описанием всех ошибок не стоит выгоды в трафике во время отладки.

Меньше методов — меньше запросов


Когда вопрос касается читаемости кода и его повторного использования, лучше использовать большее количество независимых методов. Но при разработке API стоит минимизировать количество обращений к серверу, насколько это возможно. Проще говоря: данные, которые в приложении должны показываться вместе, отдавать нужно тоже вместе.

Не тестировать дважды


Разумеется, речь идёт о дублировании функциональных тестов модульными. Как и в остальных случаях, мы стараемся использовать инструменты по их назначению: юнит-тестами мы покрываем модули сайта и роутера, а сам API тестируем с помощью dredd и API Blueprint.

Разработка через документацию


Именно для этого мы используем API Blueprint и сервис apiary. Сначала мы описываем то, что хотим получить в итоге. Далее – продумываем структуру методов, их возвращаемые значения, варианты ошибок и прочее. Только после этого пишем API. Такой подход имеет множество преимуществ, и позволяет разработчикам API оперативно получать комментарии от разработчиков приложения, исключая двойную работу.

Версионность


Когда дело касается мобильных приложений, следует помнить, что не все пользователи устанавливают новую версию сразу же по выходу. Следовательно, интерфейс должен быть совместим с приложениями, работающими на старых версиях API. В этом нет ничего сложного: приложение сообщает в заголовке нужную ему версию, а мы, меняя мажорную версию, переносим старую версию контроллера метода в подпапку с номером его (старой) версии.

Постоянная интеграция


Мы используем TeamCity, но любой CI-сервис, в том числе облачный, поддерживает unit и dredd-тесты, а также интеграцию с Apiary. При успешном тестировании мы актуализируем внешние тестовые площадки и анализируем несколько метрик. Эти действия позволяют быстро отследить возникшие проблемы и обеспечивают постоянное наличие свежей документации.

Внедрение Unit-тестирования в существующий проект


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

Не торопитесь выбрасывать код и переписывать его с нуля. Если ваш проект позволяет внедрить модуль в архитектуру, то вы можете написать такой модуль, который позволит проводить выборочное тестирование. Вы сможете писать новые модули через TDD, а постепенно покроете тестами и старые.

Мы делаем это примерно так: в папке нашего модуля мы устанавливаем PHPunit и его зависимости, а в теле самого модуля вызываем модифицированный testRunner:

$out = '';
$module = "console";
		
$testRunner = new PHPUnit_TextUI_TestRunner();
$testPrinter = new ZeroTech_printer($out);
	
$testRunner->setPrinter($testPrinter);
$testSuite = new PHPUnit_Framework_TestSuite();
		
foreach (glob(U_PATH . "/tests/*test.php") as $filename)
{
    $testSuite->addTestFile($filename);
}
$testRunner->doRun($testSuite, array("verbose" => true));	

Результат выполнения будет в переменной $out. Останется только вывести результат на экран или в шаблон.

Модуль доступен через админку и выглядит так:



Функциональные тесты с помощью dredd


Как уже было отмечено выше, для прототипирования, документирования и тестирования нашего API мы используем сервис apiary и его утилиту dredd:

  • Описываем функционал в формате API Blueprint (своеобразный markdown на стероидах): Разделяем методы на группы, описываем, зачем он нужен; какие заголовки / формат данных / параметры / атрибуты метод принимает, какие из них обязательные; какие есть ограничения; что метод возвращает; на что отвечает ошибками; в каком именно формате.
  • Сохраняем в файл и запускаем dredd file.apib.
  • Чиним проваленные тесты, рефакторим.
  • Выгружаем на apiary.

Выглядит APIB синтаксис примерно так:

 FORMAT: 1A 
# Group User 
 
## /user 
###  GET - Получение данных профиля пользователя [GET]
   + Response 200 (application/json)
   + Attributes
   + first_name: Иван (required, string) - Имя пользователя (только русские символы)        
   + last_name: Иванов (required, string) - Фамилия пользователя (только русские символы)        
   + dob: 1988-10-01 (required, string) - Дата рождения 
   + sex: 1 (required, number) - Пол (0 - женский, 1 - мужской) 
   + city: Москва (required, string) - Город         

### POST - Создание нового пользователя [POST] 
   + Request (application/json)    
   + Attributes        
   + first_name: Иван (required, string) - Имя пользователя (только русские символы)        
   + last_name: Иванов (required, string) - Фамилия пользователя (только русские символы)        
   + dob: 1988-10-01 (required, string) - Дата рождения        
   + sex: 1 (required, number) - Пол (0 - женский, 1 - мужской)        
   + city: Москва (required, string) - Город 
 
   + Response 201  
      {      
        message: “Successfully created”,      
        id: 123 
       } 

Apiary преобразует всё это в удобный интерфейс с mock-сервером. Разработчики приложения могут использовать его даже в том случае, если «живой» API ещё не написан или работает некорректно. Можно также использовать mock-сервер в качестве песочницы.



Кроме того, можно смотреть историю тестов утилитой dredd, если у вас, например, нет интерфейса непрерывной интеграции.

Заключение


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


К сожалению, не доступен сервер mySQL