Как стать автором
Обновить

Комментарии 54

Одно непонятно, зачем WSDL? От него же большинство описанных проблем и идет. Логичнее переходить на популярный REST, там и HTTP status и JSON,… из коробки. А описать вручную мобильное API несложно.
С Rest'ом есть нерешенный вопрос о стандартизации и документации предоставляемого сервиса. В случае с SOAP есть WSDL по которому однозначно генерируются клиентские классы.
Насчет однозначности реальность к сожалению дает сбои. В принципе согласен, урезанный аналог WSDL для REST давно ждут, но для мобильных приложений, все же API достаточно простое, чтобы ограничиться примерами.

P.S. а еще REST кэшируется хорошо
Полностью согласен насчёт REST, я сам его большой поклонник. Объекты описать — не самое сложное занятие, но все же я люблю автоматизировать всю ручную монотонную работу там, где это можно. Так голова останется только для решения действительно важных задач.

Ну и API тоже не всегда простое — теперь бывают и очень сложные клиенты. На одном моем проекте было, например, более ста различных веб-сервисов с множеством параметров. В таких случаях автоматическая генерация очень помогает.
Ну сотню сервисов, конечно, только SOAP автоматизирует.
для мобильных приложений, все же API достаточно простое

Именно так. И особенно это работает когда клиент и сервер написаны в одной конторе :) Но в остальном он не производит серьезного впечатления, т.к. без возможности фиксировать контракт api является просто хорошим паттерном реализации сервисного уровня.
Ну а чем WSDL лучше в плане возможности фиксировать контракт? REST API вполне можно обеспечить и версионностью и обратной совместимостью.
Ну а чем WSDL лучше в плане возможности фиксировать контракт?

Вот здесь я не совсем понял ваш вопрос. WSDL — это и есть контракт. Сервис ничего не отдаст, чего нет в WSDL, потому что серверная часть либо создается из WSDL файла либо WSDL создается по серверному API. Поэтому WSDL является результатом фиксированного контракта.

В REST да, все можно сделать. Только контракт описан в вики, что равно «на словах», поэтому никак не привязано к реальному API и держится на честном слове разработчиков.
Контракт, но кто мешает мне его сломать (так и происходит обычно)? Я не вижу фиксированности, ну разве что версионность из коробки, но это слабое подспорье.

Кроме того, вы недооцениваете REST, для гомогенных сред сейчас один и тот же код используется на сервере и клиенте. Для iOS и android тоже есть аналоги JAXB и RESTEasy. Вики-описание уже давно не потолок возможностей.
SOAP хорош для гомогенных сред (Java — Java, .NET — .NET), в других же случаях маршаллинг сложных объектов не работает, и приходится заниматься всякими извращениями, как то перевод HashMap в массив MapEntry и так далее. В сухом остатке остается возможность маршаллить примитивы (строки, целые числа, массивы) и объектики, составленные из этих примитивов. По-моему, возня с WSDL и проблемы с обновлением серверной части не стоит этого. Куда лучше, на мой взгляд, сделать простейший интерфейс на HTTP запросах, который принимает и возвращает JSON / XML. И задокументировать методы вручную в коде и на викистраничке проекта.
Я в данном случае говорю не о SOAPе, а о проблеме стандартизации (Кстати по поводу SOAP для java-java, net-net я не соглашусь, так как его цель именно дать возможность коммуникации между средами внезависимости от их имплементации). Сейчас все пишут REST сервисы как Б-г на душу положит. Хорошо, когда человек прочитал пару книг и имеет какой-то опыт реализации за плечами. Можно надеятся на что-то стабильное в плане API и обратную совместимость в будущем. По факту, процент таких очень мал, если мы исключим всем известные, широко используемые публичные сервисы.

Но опять же, у каждого свой путь реализации версионности сервисов, выкатываения багфиксов и т.п. В частном случае SOAP протокола, я стандартными средствами (автоматизированными даже) иду на урл сервиса, и узнаю какие версии api он поддерживает и что какой формат данных я могу оттуда ожидать. Это дает гарантию моему клиенту, что при правильном запросе он получит знакомый ему ответ.

В текущей реализации RESTа сервисов — этого сделать нельзя: я не могу парсить wiki страничку, я не могу знать стандартный урл для извлечения версий api, и контракта (не каждый сервис это предоставляет, не будет одинаковых способов это сделать). Захочется девелоперу подфиксить какой-то баг у себя в АПИ — хорошо если он об этом напишет у себя в wiki, и то я пока прочитаю, мой клиент будет лежать.

Именно это не делает REST стандартом дефакто в серьезных системах. Потому что если упадет интеграция с twitter'ом, никто ничего не потеряет.
Для REST пытались принять WADL, но W3C не планируют его стандартизировать, и вообще у многих людей появляются серьезные сомнения на его счет, хотя были попытки реализации кодогенераторов для него.

В целом, разработка застыла где-то в 2009
при написании серверной логики мы столкнулись с проблемой синхронизации базы на клиенте и на сервере. как с точки зрения идеологии REST, правильно и оптимально, осушествить синхронизацию даных?
Наверное никак, REST — REpresentational State Transfer, всего лишь.
Как бы REST ни расшифровывался, он идеально подходит для операции синхронизации. Смотрите комментарий ниже.
Да, для простого случая со слабосвязанными таблицами вполне, вот только к «идеологии» REST тут мало что относится, просто HTTP доступ к базе.
Таблица — ресурс. Не назову идеологией, но мне больше нравится чем просто оборачивать методы в XML, как в соапе или RPC
Отличный вопрос, мы как раз реализовывали подобное с использованием REST.
URL/synchservice/ — возвращает текущую версию базы в формате, например, секунд прошедших с 1970 года;
URL/synchservice/tablename — возвращает полную таблицу так, как она есть в базе;
URL/synchservice/tablename/<current_version_on_client> — возвращает только изменения, произошедшие в таблице от момента current_version_on_client до текущей версии базы.

Этих методов должно хватить. В самом XML или JSON, который будете сериализовать, необходимо завести три секции — одну для записей, которые нужно вставить, вторую — удалить, третью — обновить. Это самый неизбыточный способ, по крайней мере это реально работает уже 2 года в довольно большой энтерпрайз-проекте.

Данные на стороне клиента желательно парсить SAX-парсером, т.к. дамп базы при первой синхронизации может быть достаточно большим. На сервер-сайде к каждой таблице нужно хранить таблицу истории со след. параметрами:
1. Операция (D — delete, I — insert, U — update)
2. Время операции.
3. ID записи в основной таблице.

И не забудьте проиндексировать по дате. Удачи!
это хорошо подходит когда данные необходимо только получать. а что если новые данные создаются на клиентах, как быть в этом случае?
Такого мы не реализовывали, но я полагаю — отправлять методом POST в той же структуре (отдельные блоки для добавленных, удаленных и измененных) по адресам таблиц (URL/synchservice/tablename). Т.е. GET — вы берёте таблицу, POST — добавляете данные в таблицу.

Если нужно еще и создавать таблицы, то это PUT, если удалять — DELETE.

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

Только как в SVN — перед тем, как добавить, желательно для начала обновить
с точки зрения rest, post на список может только обновить или добавить, а вот удаление должно идти через delete. что приведет к множеству запросов на сервер. (или понадобится реализация пакетной обработки запросов)
А вот и нет. DELETE — это тоже изменить ресурс, а не удалить ресурс, т.е. его можно рассматривать как EDIT с точки зрения идеологии REST. Т.е. запрос всё равно один.

Если следовать вашей логике, то и добавлять нужно методом PUT, что не верно.
Опечатался — DELETE это удалить ресурс, а POST — изменить. Удаление записей в таблице — изменение.
Про куки вопрос. Если надо хранить на клиенте, скажем, его местоположения, город там выбраный или в таком духе? Как вы будете решать это токеном в описанной вами ситуации:
Допустим, ваше мобильное приложение реализует только 70% функционала веб-сайта. Остальные 30% функционала доступны только в вебе, а ваше приложение лишь содержит ссылки на соответствующие веб-страницы.
Не совсем понял вопрос, но местоположение девайса обычно определяется на клиентской стороне по GPS, а затем уже отправляется на сервер. Сервер же по токену будет знать, какой из девайсов к нему обращается.
А чем по сути отличается сессия от токена?

Сессия/кука это ведь точно такой же токен, просто передающийся внутри HTTP заголовка. Session ID можно передавать (если нужно) внутри запроса, смысл происходящего остается тот же самый — у клиента есть какой-то токен (id/хэш/как угодно бозывайте), по которому на сервере есть некий контейнер с данными, хранящий состояние авторизации/координаты/прочих данных, связанных с клиентом.

P.S: только как-то слабо вяжется такая большая любовь к HTTP заголовкам статуса и нелюбовь к HTTP заголовком куков.
как мне кажется, основное отличие сессии от токена, это в первом случае в рамках сессии на сервере сохраняется некоторое состояние (скажем в сессию записывается id залогиневшегося пользователя), а при использовании токена хранение состояние недопустимо.
С точки зрения сервера — да. А какая разница с точки зрения пользователя API? API клиент получил какой-то хэш и с этим хэшем ходит при последующих запросах. Хранится ли при этом авторизация в самом хэше, или в контейнере, или где-то еще — по сути деталь реализации данного API, которая клиента никоим образом волновать не должна.
Нет, тут про GPS речь не идет.

Пользователь выбрал свой домашний город, или язык вебсайта или цвет бэкграунда и т.п.
Если вы будете хранить все это дело в базе на сервере, либо в сессии на сервере (для сессионных компонент), то при наплыве пользователей наступят проблемы с хранилищем на сервере (ограниченное количество соединений с базой, не резиновая память для хранения сессий в RAM и т.п.).

Обычно, такие данные хранят в куках.
Я не пойму зачем использовать, ошибки из пула 5XX, если их смысловая нагрузка в том что на сервере что-то произошло не так, ее можно использовать что бы сказать, что пока сервер не доступен. Достаточно писать кодов 200,404,400,403 и т.п, что бы сказать клиенту правильное состояние.В случае ошибок, добавить в ответ описание ошибки, прочитать ее и локализовать, если это необходимо, и парсить не придется можно просто текстом.
Мне тоже нравится идея возвращать только статус.
Однако возвращаю {'ok': false, 'error': 'Some valuable parameter is not provided}
А иначе как? Status 400 и описание ошибки в теле ответа? в виде HTML?
Спасибо.
Нет, почему, можно возвращать например статус 520, а в проектной документации договориться, что это значит 'Some valuable parameter is not provided'. Клиент будет знать, что за ошибка, и правильно её обработает. Т.е. на все ошибки будут просто разные статусы HTTP. Это, конечно, работает если заведомо известно что их не будет больше 80-ти. Если же их будет больше, можно просто возвращать код ошибки в JSON как вы написали — без самого текста сообщения, который клиент сможет вывести сам, красивый и локализованный.
Интересно. Хотя это ошибки валидации, количество которых предсказать трудно.
И, цитируя вашу цитату — 5xx Server Error The server failed to fulfill an apparently valid request —
ключевое слово apparently valid. А здесь есть сомнения что запрос валидный. То есть с точки зрения формы он валидный, но содержание подкачало.

Наверно ближе всего 400, что для меня значит — не нужно повторять этот запрос.
Но тоже есть сомнения.

Так что пока возвращаю статус 200,
имея в виду «у нас нет проблем с обменом данными, все в порядке с доступом»,
ну а содержание сообщения это уже другой вопрос.

Тем более, может я хочу одни и те же JSON сообщения отправлять через
HTTP, AMQP, ZeroMQ…
Тогда, конечно, другое дело — я писал только об HTTP. Дополнительный уровень абстракции накладывает дополнительные ограничения.

Вообще — да, не привыкли мы использовать HTTP коды, для меня это тоже было дико когда впервые услышал такое предложение. Но потом понял, что это удобно :)
Обработка ошибок на стороне клиента для iOS приложений — вещь неудобная. Для исправления или добавления диагностики приходится компилировать новую версию, выкладывать на проверку для review team, что занимает до двух недель. Проблему локализации можно решить отправкой кода языка на сервер при каждом запросе. Дайте еще варианты решений. Извиняюсь за ошибки — пишу из Ялты.
Передавать язык клиента можно, но тоже не слишком удобно.

1) В этом случае локализация не будет инкапсулирована в одном месте, т.к. локальные сообщения и просто стринги всё равно нужно будет хранить на стороне приложения. Это не критично, просто не очень удобно.
2) Придётся в каждом запросе отправлять код локали и анализировать на стороне сервера. Больше кода.

В моём подходе одна проблема — все ошибки нужно учесть ДО загрузки приложения в аппстор. Но как показывает практика, новые кейсы (и, соответственно, новые ошибки) происходят после добавления функционала в приложение, т… е приложение всё равно придётся перезаливать в аппстор.
Причем тут http коды ошибок к кодам ошибок приложения? Ошибки можно локализировать на сервере, это не проблема, а вот бывает так что все варианты ошибок не зашить на клиенте, посему тут остается только отдавать текст ошибки сервером.
С куками (они же индетификаторы сессии в большинстве случаев) тоже никаких проблем нет, так трудно в заголовок запроса записать? Хотя я против сессий в апи. Ну если уж вы переходите в вебвью, то это проблемы проектирования. Зачем вообще нужно приложение, которое урезано и половина функционала в вебвью? Да и тут вообщем проблем нету, нужно лишь договорится с серверным разрпаботчиком в принципе если уж так нужно такое приложение и не будет у вас двух авторизаций. Ну это уж третий вопрос.
WSDL — это конечно круто сгенерить прокси классы. но бывает так что вправло/влево от стандарта — полный расстрел, кажется уж проще json ответ от REST-сервса распарсить. Короче водаводой…
С куками (они же индетификаторы сессии в большинстве случаев) тоже никаких проблем нет

Хотя я против сессий в апи.

Так вы против или нет проблем?
Ну если уж вы переходите в вебвью, то это проблемы проектирования.

Напротив, если бы вы внимательно читали пост — поняли бы, что я против такого подхода.
Зачем вообще нужно приложение, которое урезано и половина функционала в вебвью?

Разные случаи бывают. Бывает, что нужно совершить покупку внутри приложения (не IAP, а как на ебэе например) — делать внутри Эпл запрещает. Бывает, бюджет и сроки. Не судите столь категорично.

Да и вообще, ваш стиль комментария отталкивает от того, чтобы его комментировать.
Ну вы и не особо то прокомментировали… Весь ваш пост можно выразить словами «Думайте как удобно будет юзать api», остольное все вода. А если вы хотите чтобы это не была вода, то надо конкретно описывать проблемы и думать перед этим, а те проблемы, что вы описали — полная ерунда!
Извините, конечно я не прав — вы лучший архитектор, как я мог вообще не подумав написать статью, да ещё и с вами спорить.
Да ладно, почитайте комментарии, люди то же самое пишут…
Ну Вы бы не огрызались на одного конкретного человека, а ответили по делу на вопросы. У меня, к стати, они те же. Зачем использовать коды 5хх для ошибок? Вы в курсе, что далеко не все прокси\сервера\клиенты HTTP корректно эти ошибки примут? А если мне не хватит диапазона? Да и зачем вообще смешивать коды ошибок протокола передачи данных и протокола программы? А если я завтра захочу с HTTP-протокола перейти на какие-нибудь бинарные сокеты — зачем мне эти 5хх коды ошибок? По-хорошему, ответ с ошибкой должен выглядеть вот так:

JSON:
{
success: false,
errorCode: 451254,
description: Wrong password
}

Плюсы:
-коротко и ясно
-JSON хорошо парсится, а для браузера вообще родной
-на этапе разработки можно тупо показывать «description», а потом, когда придёт пора наводить красоту — по коду ошибки можно писать красивые сообщения, включая локализацию
-не пересекается с HTTP, может быть передан как угодно
-коды ошибок могут расширятся до бесконечности
-коды ошибок могут легко гуглится (ну это если API станет популярным и будет много клиентов)
Я с вами согласен, что не стоит на каждую ошибку заводить свой код, тем более из 5XX. Но, если вы используете Http протокол, то должны следовать правилам в его использовании, В целом, когда мы отдаем верный код ошибки, мы понимаем, что мы сделали не так. К примеру, мы неверно составил запрос, даже не читая кода сообщения, увидев, что код ошибки 400, я пойму, что я не верно задал параметры запроса. И код ошибки помогает разработчику, понять, в чем проблема. А далее мы уже можем прочитать тело ответа, в каком формате по соглашению. А по поводу перехода на новый протокол, мне интересно, как часто вам это приходиться делать? И очень трудно, без доработок вам просто удастся перейти на новый протокол, при том, что вы упоминаете прокси сервера. Прокси не будут поддерживать пул ошибок http протокола, но будут поддерживать не тривиальные протоколы?
А как вам как мобильному front-end разработчику удобней реализация токенов?
К примеру, при каждом чистом запуске приложения вы обращаетесь на отдельный URL и получаете новый токен и при следующих запросах он продлевается, пока приложение не закроется(при открытии опять новый токен).

Или есть какие то другие варианты, более удобные и правильные?
В ряде приложений (например, FOREXTrader), над которыми я работал, токен не продлевается — он просто имеет свой Time To Live (TTL), обычно равный от 2-3 минут до часа. А когда приходит ошибка «token expired», нужно повторить реквест авторизации с именем и паролем и повторить тот запрос, на котором пришёл expiration.

Так что не всегда клиенту нужно тупо вывести сообщение об ошибке, и тут как раз приходят коды.

По крайней мере, я не раз видел такую архитектуру в довольно серьёзных приложениях.
Да, нужно придумать текст ошибки. Причем текст должен:

  1. показывать что конкретно произошло, т.е. "Вы не можете отредактировать отправленное сообщение более чем через 5 минут после отправки", а не «403 в доступе отказано».
  2. показывать что пользователю делать, т.е. "Попробуйте создать новое сообщение, или попросить помощи модератора" с соответствующими ссылками. А не опять же красный крест на котором написано «в доступе отказано».


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

При этом текст ошибки все равно придумывать надо. А вот внутренний код по большому счету:
а) Не нужен пользователю т.к. ему интересно что произошло и что с этим делать, а не каким кодом вы все это обозвали.
б) Не нужен программисту, т.к. ошибки на клиенте обрабатываются как правило одинаково — выдается сообщение и набор соответствующих действий.
Согласен по поводу того, что сообщения об ошибка должны быть user-friendly и содержать посыл к действию. У меня такие вопросы:
— почему ошибок заведомо больше чем кодов?
— код нужен программисту, т.к. в абсолютном большинстве приложений локализация происходит на стороне клиента. Возможно, и лучше было бы перенести её на сервер, но знаю 90% реальных приложений, которые так устроены, и только 10% (действительно энтерпрайз), в которых это реализовано. А прикладному сервисному программисту особо не предлагают выбирать — очень часто заказчик приходит с уже готовыми веб-сервисами.

Что код не нужен пользователю — конечно, это самая бесполезная на свете информация. Видел пару раз (особенно в программах, разработанных в НИИ) как в сообщение об ошибке вставляют и код ошибки, и даже непонятное для пользователя имя класса, в котором произошла ошибка.
— почему ошибок заведомо больше чем кодов?


Возьмем, скажем набор ошибок:
  • Доступ к функции, которая запрещена на текущем тарифном плане. Нужно предложить оплатить платную подписку.
  • Доступ к административной функции. Нужно сказать что для этого действия нужно быть модератором и предложить обратиться к кому-то.
  • Доступ к редактированию поста, время которого истекло. Опять же объясняем почему и предлагаем подойти к модератору.


И этот список того что обычно относят к «Access Denied» может достичь до 50-100 разных кейсов в одном приложении. Что вы сделаете? Отведете на все один код ошибки? В таком случае он по вашей логике будет локализован на клиенте одним и тем же сообщением (ведь вам для этого нужен код!). А попытка привести к общему знаменателю пункты 1 и 3 из примера выше выльется только в неадекватное сообщение общими словами, из которого совершенно не ясно будет что же делать.

Дадите каждой ошибке свой код? У вас всего сотня кодов, их заведомо не хватит даже для средней величины приложения.

Возможно, и лучше было бы перенести её на сервер, но знаю 90% реальных приложений, которые так устроены, и только 10% (действительно энтерпрайз), в которых это реализовано.


Ее не просто нужно, ее обязательно нужно перенести на сервер. А про то что не идеальны — так ведь вы же взялись в статье описать идеальный сервер. Так вот и описывайте его!
А мне кажется реализация выдачи ошибок HTTP кодами — отличная идея, даже если не хватит кодов для всех ошибок, можно оставить 1 код в котором будет уже в теле передаваться JSON или XML с дополнительным кодом ошибки.
В своем будущем клиент-серверном приложении как раз наверно такой подход и использую.
Автору спасибо!
Конечно же в iOS Safari не делится своими cookie с приложениями. Но внутри приложения cookie разделяются между UIWebView и NSURLConnection. Если авторизация на веб-сервисе использует cookie и нужно «бросить» пользователя на сайт — проблем не будет.
Это если бросить через WebView. У меня есть такое наблюдение — скорее всего, в WebView используется какая-то урезанная версия WebKit. Было много случаев, когда сайт открывался с Safari, но не открывался в WebView. К тому же пользователь иногда может хотеть уйти из WebView в браузер, например кликнет по ссылке (нехорошо его насильно удерживать в WebView).
В сторонних webview запрещен акселератор js. Возможно поэтому.
А меня наоборот бесит, когда по нажатии на ссылку переключается на браузер молча.
Про ошибки не согласен, как и все :) Удобнее, когда протокол по возможности само-документированный, т.е. по ошибке программисту понятно, что случилось. Я бы делал что-то вроде { success:'false', errorCode:12345,errorText:'Нешмогла' }, где errorText это текст именно для программиста. А на клиенте по коду уже показываем нормальную диагностику. Если обработка такого кода не предусмотрена, на клиенте можно хотя бы errorText показать.

По wsdl возможно и правда для больших проектов. На малом проекте, где не больше 50 типов сущностей имхо все же проще json руками описать. Тем более достаточно описать один раз классы, а при парсинге через setValue:forKey: все значения заполнять. А за свой кастомный xml вообще надо руки отрывать, его парсить просто задолбаешься, т.к. он не накладывается отднозначно на структуры языка.

Почему как и все? Например, человек в комментарии на один выше вашего согласен. В протоколе HTTP как раз и придумали 5XX ошибки для того, чтобы сказать: реквест выполнился, но произошла ошибка. Другое дело, что годами сложилась практика, где это не используют, а код ошибки передают в самом сообщении.

Ну ведь и про HTTP-шные PUT и DELETE на много лет забыли, а потом пришёл REST и всё расставил по местам. Т.е. он стал использовать протокол HTTP не просто как транспортный уровень для XML/JSON, но стал использовать все его возможности, которые изначально закладывались умными людьми.

По поводу errorText — если клиент его не будет использовать, в продакшне это будет просто лишний текст, который гоняется туда-сюда. В 2012 году мы не привыкли экономить байты, но у меня после программирования на слабых контроллерах рука не поворачивается делать такую избыточность :)

Со всем остальным согласен.
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории