banderos120
@banderos120
Играю на балалайке

Как верно организовать сервисную архитектуру Symfony2 для поддержки нескольких версий API одновременно?

Здравствуйте.
Посмотрел вот такой материал по тому, как организовать работу нескольких API одновременно. Наиболее мне подходящий метод - это трансформация Request запросов, для формирования необходимой для сервиса структуры данных. Сервисы бизнес логики всегда одни и меняются только в сторону актуальной версии api. Вопрос остается только в том, как в PHP , а в частности, в Symfony организовать такую структуру ?
Т.е. как я себе это представляю:
- будет использоваться трансформер в контроллере, при получении даты из requesta трансформер валидирует эти данные для той версии апи, которую обрабатывает данный контроллер, далее, если данные верны, трансформер преобразовывает эти данные в те, которые будут необходимы для работы нашего сервиса.
- и будет использоваться валидатор в сервисе, который валидирует данные, уже непосредственно в сервисе.
На примере :
В первой версии API при создании профиля мы использовали поля first_name, last_name, patronymic .
Во второй версии API мы используем только поле name_info в котором содержится информация ФИО . Соответственно сервис будет работать у нас с новой версией и на вход будет принимать массив с ключом $data['name_info'], который и будет валидировать.
class ProfileFactory
{
    public function create(array $data){
        // throwing some ValidateException 
        $this->validator->validate(CreationValidateRules(), $data);

        $profile = new Profile();
        $profile->setNameInfo($data['name_info']); // Вот тут смущает то, что сервис и валидатор знают о ключах массива отдельно друг от друга
       //.... some code
        return $profile;
    }
}

В контроллере новой версии мы будем в сервис передавать просто массив,
class ProfileControllerApi_2{

    public function createProfile(Request $request){
        $data = $request->request->all();

        $profile = $this->get('profile_factory')->create($data );

       // ... etc.
    }

}

а в старой, будем пропускать массив через валидатор и трансформер, чтобы массив удовлетворял новым требованиям.
class ProfileControllerApi_1{

    public function createProfile(Request $request){
        $data = $request->request->all();
        //throwing some ValidateException
        $transformedData = $this->transformerHandler->transform(ProfileCreatingTransformer(), $data);

        $profile = $this->get('profile_factory')->create($transformedData );

       // ... etc.
    }

}

Но есть такие моменты, как ошибки при валидации. Например отправляю я данные на старый API, но трансформер неверно сформировал массив для сервиса и вместо name_info, добавил nameS_info . Должен ли тут сервис кидать валидационное исключение (сервис тоже валидирует), ведь клиент, который прислал данные на сервер, присылал валидные данные для старой версии, а эта ошибка трансформера. Или исключения разделить по типам - для исключений валидации внутри сервиса - кидать ServiceValidationException, а для исключений внутри валидаторов контроллера просто ValidationException ?
Прошу прощения за сумбурность, надеюсь более-менее понятно объяснил, просто в голове каша, хочется как-то более правильно сделать.
Можно ли использовать такую структуру, либо есть другие, наиболее оптимальные подходы ?
  • Вопрос задан
  • 938 просмотров
Решения вопроса 2
Fesor
@Fesor
Full-stack developer (Symfony, Angular)
Вопрос остается только в том, как в PHP , а в частности, в Symfony организовать такую структуру ?


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

А контроллеры же уже разруливают все это. Далее мы просто можем устранять дублирование и делать работу со всем этим проще. Это могут быть просто два маршрута для одного экшена если у нас там нет изменений, либо мы можем еще хэндлеры для десериализации сделать отдельные хэндлеры и т.д.

Если говорить о архитектуре - идеальный вариант это гексагональная архитектура. Тогда мы будем воспринимать разные версии API просто как разные адаптеры к слою приложения.
Ответ написан
prototype_denis
@prototype_denis
Symfony
Познакомьтесь пожалуйста с этим symfony.com/blog/new-in-symfony-2-7-serialization-... , чтобы облегчить cебе работу с форматом "прилетающих" от клиента данных,
вот с этим symfony.com/doc/current/book/validation.html#valid... для того, что бы не было проблем с валидацией и не придумывать некие валидаторы кидающие исключения,
а ещё с этим symfony.com/doc/current/components/options_resolve... дабы не было проблем с DTO объектами в фабрике

А так же с этим (Если возникают вопросы с контролерами и версионностью)
https://github.com/FriendsOfSymfony/FOSRestBundle/...

По поводу
$data = $request->request->all();

Есть замечательные форматы, например json или xml.

По поводу
//throwing some ValidateException

Валидатор не должен кидать исключения, он должен просто возвращать ошибки.

По поводу
Сервисы бизнес логики всегда одни и меняются только в сторону актуальной версии api

Они должны не меняться, а дополняться. Привет обратной совместимости с вашей версионностью.
Или создаваться и использоваться вообще разные сервисы в зависимости от версии api.

Ознакомьтесь с semver.org/lang/ru и примените эту концепцию к всем вашим контроллёрам, сервисам и всей прочей ереси в папке src.

Другими словами, создавайте объекты в фабрике относительно версии api, а не используйте магический трансформер, который загнётся при большей логике.
Используйте стандартные средства, которые так любезно предоставляет фреймворк.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы