MyShows, Eaze и мысли о правильном построении веб-приложения
Когда я только начинал делать MyShows, все было достаточно просто – все url описаны в pages.xml, и на каждый url по экшену.
Кусок сайта из pages.xml:
<page uri="/profile/"> <actions>MyShows.Site.ManageEpisode, MyShows.Site.GetMyShows, MyShows.Site.GetFirstUnwatchedEpisodes</actions> <template>tmpl://fe/profile/index.tmpl.php</template> </page> <page uri="/view/([0-9]+)/"> <actions>MyShows.Site.GetShow, MyShows.Site.ManageShow</actions> <template>tmpl://fe/search/view.tmpl.php</template> </page> <page uri="/view/episode/([0-9]+)/"> <actions>MyShows.Site.GetEpisode</actions> <template>tmpl://fe/search/episode.tmpl.php</template> </page>
И в таком духе…
Мобильная версия
Вскоре понадобилось создать мобильную версию сайта, где логика примерно такая же, но со своими исключениями:
<page uri="/unaired/"> <actions>MyShows.Site.GetMyShows, MyShows.Site.GetMyFutureEpisodesIphone</actions> <template>tmpl://iphone/unaired-shows.tmpl.php</template> </page> <page uri="/unaired/([0-9]+)"> <actions>MyShows.Site.GetMyShows, MyShows.Site.GetMyFutureEpisodesIphone</actions> <template>tmpl://iphone/unaired-episodes.tmpl.php</template> </page>
На получение данных, в принципе, экшены все одни и те же, но с дополнительными настройками, которые заданы через MyShows.Site.xml. Так что, более менее проблема решается.
API
А потом понадобилось сделать API. И тут началось, т.к. API должен продублировать почти все функции, которые есть у сайта… С точки зрения системы экшенов – отлично подходила REST-архитектура (ну или её подобие).
Для управления данными требовалось написать экшен-фильтр, который преобразует данные из Page::$RequestData в GET/POST параметры, как будто это действие было совершено через сайт.
<page uri="/profile/shows/([0-9]+)/(sync|watching|cancelled|later|remove|episodes)"> <actions>MyShows.Site.ManageShowAPI, MyShows.Site.ManageShow</actions> </page> <page uri="/profile/shows/([0-9]+)/(rate)/([1-5])"> <actions>MyShows.Site.ManageShowAPI, MyShows.Site.ManageShow</actions> </page>
А для вывода данных пришлось написать свои экшены, в которых был организован вывод в JSON. Благо данные (сериалы, эпизоды, новости и остальное) обычно брались из уже заранее написанных утилит.
Как мы видим, полученный результат не очень удовлетворяет наши программистские потребности — DRY передавал привет.
Назад в будущее
Если бы мы заранее предположили, что у нас будет не только мобильная версия, но ещё и API, а API ещё и с версионностью, то скорее всего подход был бы другой:
- Выбор API пал бы точно на JSON-RPC 2.0, т.к. есть batch-запросы и для JSON-RPC есть нормальные библиотеки для работы с ним.
- Если раньше были утилиты (например ShowUtility, которые работали с базой) и экшены, в которых выполнялась логика, то к ним нужно добавить ещё *Manager’ов, которые использовались бы в API и в экшенах, например: SiteUserManager::Register( $login, $email, $password, $gender ).
- Мобильную версию сайта можно полностью перевести на javascript и API для того, чтобы не дублировать экшены и не писать никаких адаптеров-переходников.
Руководствуясь этими принципами, можно получить вполне хороший результат.
Но можно пойти ещё дальше и сделать API на каком-нибудь языке типа Go, а сайт – таким же клиентом к этому API. Или заложить такой же принцип, вызывая методы API из экшенов напрямую – как подготовку к будущей версии с Go.