Когда я впервые начал работать с Symfony2 обновляя части нашей архитектуры в Namshi, я был искренне рад, потому что я был уверен, что Symfony был лучший выбор для нашего бизнеса. Прошло время, я все еще доволен нашим выбором, но я хотел бы дать честный и ясный обзор того, какие элементы работали, а какие нет, для того чтобы другие могли извлечь пользу из нашего опыта.
Прежде, чем мы начнем
Во-первых, позвольте мне представить нашу архитектуру, или, по крайней мере, как это выглядит в настоящее время: в Namshi, мы запустили портал электронной коммерции, который быстро растет на Ближнем Востоке (7 стран, 2 языках). Для того, чтобы достичь желаемого роста, мы боролись со сложностями, опираясь на микросервисы в сервис-ориентированной архитектуре.
Мы также диверсифицировали наши платформы разработки, так как наши фронтэнды могли быть как «нативными» (мобильными приложениями) так и написаными на JavaScript, или на NodeJS (настольный веб-сайт) или на стороне клиента с AngularJS (мобильный веб-сайт).
Как вы, наверное, уже догадались, мы используем Symfony2 для разработки серверных API, используемых нашими фронтендами. В этой статье мы рассмотрим только серверную часть приложений.
FOSRest или не FOSRest?
Если вы разрабатываете API, необходимо использовать FOSRestBundle, верно? Не так быстро!
Я не говорю, что это плохой пример программного обеспечения, но для часто изменяемых, долгосрочных проектов, вы, возможно, захотите взглянуть на что-нибудь попроще, с долее простым конфигурированием и с вашими собственными ограничениями и правилами.
Мы используем FOSRestBundle но медленно мигрируют с него в новых сервисах, которые не требуют слишком много абстракции: мы определили, что самое простое решение (т.е. JMS serializer + JsonResponse), вероятно, все, что нам нужно.
Вы можете сказать, что если необходимо обеспечивать поддержку множества форматов, то используя FOSRestBundle вы можете сэкономить много времени. Я думаю, что очень важно оценить ваши конкретные потребности, а затем решить, использовать его или нет. В нашем случае, мы пришли к выводу, что простой метод в контроллерах, это все, что нам необходимо:
class ApiController
{
public function createApiResponse($data, $whatever, ...)
{
return new JsonResponse($this->getSerializer()->serialize($data, 'json'), ...);
}
}
Еще раз повторю, я думаю, FOSRest является очень хорошим примером программного обеспечения. Тем не менее, я хотел бы подчеркнуть тот факт, что вы могли бы избежать дополнительной сложности или абстракции, если вы не нуждаетесь во всех его функциях.
Аутентификация
Для авторизации запросов API, мы обратили внимание на то, что Google продвигала в некоторых из своих пакетов SDK, JSON веб-токенов.
В результате мы написал небольшую библиотеку для использования JWT в PHP и, думаю, что кто-то написал связку для этого. Нам на самом деле до сих пор очень нравится простота JWT и, по сравнению с другими, более сложными решениями, эта простота очень хорошо работала до настоящего времени.
Идея JWS (Json Web Signatures, которые являются версией JWT) очень проста, клиент сначала регистрируется под своими учетными данными, затем вы выдаете стандартный, подписанный JWT, который содержит информацию о клиенте, затем клиент посылает токен с каждым запросом к API.
Еще одно, нужно отметить, что все наши API, работают на HTTPS, и я определенно рекомендовал бы двигаться в этом направлении. С все более быстрым Интернетом и такими вещами как SPDY и LTE соединения, накладные расходы становятся очень малы.
Несколько бандлов или несколько приложений?
Другой большой вопрос, который мы обсуждали, это развивать ли N Symfony приложения или просто хранить различные наши интерфейсы в отдельных бандлах, то есть в одном приложении.
Мы начали с последнего, поскольку это был самый быстрый способ запустить сервисы в работу, но в настоящее время дело движется в направлении разделения всех служб в их собственных приложениях. Эта стратегия позволяет избежать конфликта зависимостей (одиному бандлу необходима версия Х.Y зависимости, которая не поддерживается в другом бандле, по какой причине, и т.д.), чтобы поддерживать процесс развития этих компонентов отдельно.
Это означает, что мы можем поручить небольшой команде разработчиков работы над одной API вне зависимости от какой-либо другой API. Кроме того, команда может сдать свою часть работы быстрее, без необходимости дожидаться развертывания всей инфраструктуры API сразу. Наконец, этот подход также помогает в случае необходимости переписать один из наших сервисов под другую платформу (NodeJS, например).
Конечно, такие проблемы начинают возникать тогда, когда вы начинаете масштабировать команду и объем программного обеспечения, так что я бы порекомендовал начать с нескольких бандлов, а затем просто разделить их в N-приложения, когда это будет необходимо.
У нас также есть некоторый код/пакеты, которые используются в практически всех API, и хранятся в отдельных хранилищах, которые подключаются через Composer.
Как насчет производительности?
Если быть честным, ничего специфического: мы работаем с большей частью нашего контента через Redis, и используем Varnish над Nginx, так что наши API имеют возможность отдохнуть время от времени.
Хотя мы не достигли умопомрачительной пропускной способности, мы с удовольствием отмечаем что у наши API отрабатывают за около 75 мс, что достаточно приличный показатель, чтобы не беспокоиться о производительности на некоторое время и сосредоточиться на других аспектах архитектуры.
Модульное тестирование. Попался!
Правда: мы были очень небрежны в этом. Мы сумели добавил функциональные тесты, не особо задумываясь на юнит-тестах. Тенденция, которую мы начали в последнее менять.
В любом случае, есть несколько вещей, которые я хотел бы отметить:
- Вместо того чтобы использовать PHPUnit, который полностью интегрирован вне Symfony, мы решили перейти на PHPSpecbecause, мы чувствуем, что это более современное решение для юнит-тестов.
- Travis-CI возможно нет ничего умнее: мы использовали его в течение нескольких месяцев и очень довольны, не располагая нашими собственными экземплярами Дженкинс, чтобы вызвать тесты каждый раз, когда кто-либо публикует на GitHub.
CORS и сотоварищи
Для тех, кому интересно, как мы решили вопросы CORS, мы начали с NelmioCorsBundle но, поскольку нам, в любом случае, нужна поддержка старых версий IE и такое решение, как xDomain не так просто, как вы можете подумать, мы решили реализовать API прокси так что мы избежать запросов cross-origin в целом. Вместо того, чтобы с запрашивать api.example.com/users, интерфейс на example.com запрашивает example.com/api/users, а правило в настройках веб-сервера будет отправить этот запрос API на api.
под домене.
Версионирование
В попытке сделать все как можно проще, мы реализовали версионирование посредствам стандартных функций веб-сервера и самой Symfony.
Подход, который мы приняли это на самом деле предельно просто. Мы позволяем клиентам использовать наши API, через URL, например api.example.com/checkout/v3 / …. трюк в том, что сервер Nginx берет первые два сегмента URL и устанавливает два заголовка HTTP соответственно:
Api-Service: checkout
Api-Version: 3
Затем, у нас есть routing listener, который загружается контроллеры, соответственно версии: он проверяет доступен ли контроллер в пространстве имен \Namshi\CheckoutBundle\Controller\V3
. Если нет, то возвращает результат нулевой версии (\Namshi\CheckoutBundle\Controller
) или выдает ошибку 404.
Помимо этого, до отправки ответа клиенту, у нас есть Преобразователи ответов: мы смотрим на версию API, которая была запрошена, находим Преобразователи, которые соответствуют этой версии и применяем их к ответу.
Преобразователь это простой POPO (Plain Old PHP Object), который принимает ответ и применяет преобразования при необходимости. Например, мы используем Преобразователь для преобразования к кемелКейс значений в ответах API, чтобы добавить некоторые правила кэширования или обрезать содержание, которое нам не нужно в конкретной версии. Вот так выглядит преобразователь:
class CamelCaseTransformer implements ApiResponseTransformer
{
protected $inflector;
public function __construct(SomeInflector $inflector)
{
$this->inflector = $inflector;
}
public function transform(Response $response)
{
$content = $this->inflector->camelize($response->getContent());
$response->setContent($content);
}
}
В целом
Команда в Namshi далеко от совершенства, и я искренне верю, что есть еще многое предстоит сделать и множество улучшить. Тем не менее, некоторые очень простые решения, как те, которые мы описали, помогли нам опередить многих наших конкурентов за счет гибкости, которая исходит от уменьшения сложности наших, разработанных на Symfony, API.
Мы очень рады использованию Symfony2 для наших API, и уверены, что если бы нам пришлось начать с нуля, мы бы не выбрали что-нибудь другое из предлагаемых PHP экосистем.
Пожалуйста, отправьте нам свой отзыв о решениях, которые мы сделали, потому что мы действительно считаем, что мы можем и должны как можно больше учиться у интеллектуального сообщества Symfony.
И последнее, но не менее важное, в процессе написания нашей SOA мы имели возможность развивать некоторые компоненты с открытым исходным кодом (как в JavaScript и PHP), которые доступны на GitHub: не стесняйтесь, и помогайте нам сделать их еще более удивительным!
Источник: http://symfony.com/blog/going-soa-with-symfony2-a-year-and-a-half-down-the-road
машинный перевод.