Wamba Open JSON API
Разработка и  сопровождение API
в большом проекте
Сегодня в программе:
●

Авто-документация. Генерация HTTP-роутинга.

●

Архитектура основных компонентов API.

●

Гибкое построение форм на клиенте: FormBuilder.

●

Тестирование.

●

Версионность.
Часть 1.
Документация.
Документация. Поиск решения
Документация может вестись отдельно, но тогда она быстро
устаревает.
Для открытого API важнее актуальность документации.
Решение: документация должна быть в коде (phpdoc).
Документация в коде. Поиск решения.
NelmioApiDocBundle
<?php
/**
* @ApiDoc(
*

description="CreateanewObject",

*

input="YourType",

*

output="YourClass"

• Используется
нестандратный
phpdoc (99% не
поддерживается IDE)

*)
*/
public function postAction() {
}

• Названия классов вшиты
в строки (автоматический
рефакториг затруднен)
Документация в коде. Поиск решения.
Restler-API-Explorer
<?php
require_once '../../../vendor/restler.php';
use LuracastRestlerRestler;
$r = new Restler();
$r->addAPIClass('Say');
$r->handle();

• Название классов API
задается динамически (рефакторинг)
Документация в коде. Решение: ООП!
/**
* Возращаем список пользователей
* @http_method get

● Генерируется
автоматически IDE
(PHPStorm)

* @uri /users/:userId/
* @param FiltersBrokerAuthorized $fb
* @param RequestUserDetails $req
* @param ResponseUserDetails $rsp
* @return ResponseUserDetails
*/
public function getUserDetails(
FiltersBrokerAuthorized $fb,
RequestUserDetails $req,
ResponseUserDetails $rsp) { ...

● Имена классов (при
сборке документации)
берутся из типов
атрибутов.
Генерация документации. Apigen
cd $PROJECT_HOME; make doc
apigen ...
PhpDoc + Apigen + Apigee = WebConsole
PhpDoc + Apigen + Apigee = WebConsole
Генерация HTTP-роутинга. Apigen.
class ServiceUser {

...
$this->map['GET']['/users/:anketaId/']

/**
* ...

=function(){

* @http_method get
return (new ServiceUser())

* @uri /users/:anketaId/

->getUserDetails(

* ...

new FiltersBrokerAuthorized(),

*/
public function getUserDetails(

new RequestUserDetails(),

FiltersBrokerAuthorized $fb,

new ResponseUserDetails()

RequestUserDetails $req,
ResponseUserDetails $rsp
) { ...

};
...
Часть 2.
Архитектура основных компонентов.
Архитектура. Обзор
•

HTTP-роутер
Архитектура. Обзор
•
•

HTTP-роутер
Брокер фильтров

public function getUserDetails(
FiltersBrokerAuthorized $fb,
RequestUserDetails $req,
ResponseUserDetails $rsp
}
Архитектура. Обзор
•
•
•

HTTP-роутер
Брокер фильтров
Сервис
Архитектура. Обзор
•
•
•
•

HTTP-роутер
Брокер фильтров
Сервис
Запрос
Архитектура. Обзор
•
•
•
•
•

HTTP-роутер
Брокер фильтров
Сервис
Запрос
Ответ
Архитектура. Брокер Фильтров (FiltersBroker)
Проверяет доступ к сервису.
class FiltersBrokerAuthorized {

● Настоящее предсобытие
без “магических” конфигов.

public function __construct() {
if (!Profile::isAuthrorized()) {
throw new AccessDeniedException();
}
...
}
}

● Передается в сервис как
аттрибут (не потеряется).
Архитектура. Сервис (Service)
Бизнес-слой
class ServiceUser {
public function getUserDetails(

Защищен брокером

FiltersBrokerAuthorized $fb,
RequestUserDetails $req,

Работает с конкретной структурой
входных данных

ResponseUserDetails $rsp
) {

Отдает ответ в соотвествии с конкретной
структурой

// бизнес-логика. Использует только объекты API
// Весь код работы со сторонними библиотеками спрятан в DAO слой
}
}
Архитектура. Запрос (Request)
Описание полей входных данных
/**
* Идентификатор пользователя
* @api
* @var int
* @example 12234
*/
protected $userId;
Архитектура. Запрос (Request)
Контроль типа через методы-аксессоры
/**
* @return int
*/
public function getUserId() { return (int)$userId; }
/**
* Применяется при unit-тестировании
* @return void
*/
public function setUserId($userId) {
$this->userId = (int)$userId;
}
Архитектура. Запрос (Request)
Методы-помощники.
/**
* Метод-помощник.
* Возращает объект анкеты по установленному значению userId
* @return User;
*/
public function getUser() {
}
Архитектура. Ответ (Response)
Описание полей ответа
/**
* Идентификатор пользователя
* @api
* @var User
* @example #object#
*/
protected $user;
Архитектура. Ответ (Response)
Контроль выходных данных через методы-аксессоры
public function getUser() {
if (!($this->user instanceof User::getClass())) {
throw new InvalidTypeException();
}
return $this->user;
}
Архитектура. Ответ (Response)
Сам себя превращает в JSON
class ResponseUserDetails implements JsonSerializable
{
public function jsonSerialize() {
return [
'user' => $this->getUser(),
];
}
}
Часть 3.
Гибкое построение форм на клиенте: FormBuilder.
Формы: FormBuilder. Цели
•

Минимизировать ошибки на клиенте

•

Иметь возможность модифицировать формы без релиза

•

Сократить время разработки экрана-формы
Формы: FormBuilder. Пример.
{
"formBuilder": {
"blocks": [{
"name": "Авторизация",
"fields": [{
"name": "Электронная почта
или логин",
"inputType": "Text",
},
{
"name": "Пароль",
"inputType": "Password",
...
Часть 4.
Тестирование.
Функциональное тестирование. Behat
Scenario: Возвращает форму авторизации.
When I am on "/login/builder/?lang_id=ru"
Then I should see FormBuilder:
| block | field

| type

| enable |

| login | login

| Text

| true

|

| password

| Password

| true

|
|
Функциональное тестирование. Behat
Behat = Cucumber на PHP
Весь сложный код тестирования прячется за простым
выражением
Расширяем до +∞

•
•

•

/**
* @Then /^I should see FormBuilder:$/
*/
public function iShouldSeeFormBuilder(Table $table) {
...
}
Функциональное тестирование. Behat
●

Тестирование на естественном языке

●

Проверка системы в продакшн с данными

●

Отправная точка для unit-тестирования
Unit-тестирование. PHPUnit
•

Проверка мелких узлов системы без данных

•

Удаление зависимостей в коде

•

Помогает делать код более простым

Рецепт: Отключите автозагрузку сторонних библиотек при unitтестировании.
Часть 5.
Версионность.
Версионность. Внешняя
Доступ к API: http://<домен-партнера>/mobile/api/v5.1.0/...

v5.1.0..1..2..
Мажорная версия API

Версия релиза клиента
Идентификатор платформы
(iOS, Android)
Версионность. Внутренняя
•

Нет подверсий API

•

Подверсии у стуктур данных

•

•

Переход на подверсию API связан с изучением changelog,
неодобряется бизнесом
Переход на подверсию структуры быстр и касается только
какого-то кокретного вызова API
Подверсии структур. Пример.
Анкета версия 1

Анкета версия 2

{

{
"name": "Прекрасный принц",

"name": "Прекрасный принц",

"photo": "photo250x250.jpg"

"photo": {

}

"square250": "photo250x250.jpg",
"square600": "photo600x600.jpg",
"large": "photoLarge.jpg"
}
}
Резюме:
•

Генерация документации и HTTP-роутинга из phpdoc - Apigen

•

Генерация web-console разработчика - Apigee

•

Гибкий подход к формам на клиенте - FormBuilder

•

Тестирование - Behat, PHPUnit

•

Версионность: внешняя и внутренняя у структур.
Руководитель группы разработки мобильных сервисов Wamba

Каторгин Виталий (v.katorgin@wamba.com)
Спасибо за внимание!

Виталий Каторгин, Wamba