Pull to refresh

Comments 24

Ребята, вы как всегда вовремя! Как раз собирался писать API.
Поделитесь, пожалуйста, ссылочкой на доклад Марата про проектирование систем, о котором в начале видео говорится. Ссылка из доклада (http://study.yandex-team.ru/events/kmb-2/techno/talks/901) наружу не доступна.
Кажется, этот семинар ещё не выкладывали. Можно взамен Макконела почитать )
Уже ) Но разве семинар просто рассказывает о книге? )
Нет, конечно. Просто семинара нет в публичном доступе, а книга есть )
Очень жаль, что нет возможности послушать Марата. Рекомендую все-таки выложить
Очень хочется посмотреть данный доклад, выложите, пожалуйста.
очень странно выглдит совет использовать события — это требует глобального/встраевоемого везде медежера событий с интерфейсовм set/get event data, формат которых не задан через интерфейсы
У нас в API карт так и есть — почти все объекты реализуют IEventEmitter
Интерфейсы же для передаваемых событием данных, в общем случае, не нужны — все данные должны быть доступны через интерфейс объекта, бросающего события.
Последняя фраза не очень понятна.
К примеру, есть чат и объект Юзер. У юзера событие — «пришло новое сообщение».
Как подписчику узнать содержимое сообщения, у юзера должен быть метод типа «покажи последние N сообщений» и подписавшийся на юзера клиент должен в них покопаться и найти новое?
Да.
Только не совсем ясно, зачем «копаться». Новое = последнее.
Только в полностью синхронных системах. Т.е., пока подписчик не вышел из обработчика события, никто не может добавить сообщение, иначе эта логика сломается.
В асинхронных системах доставать ид сообщения из события вообще неправильно и довольно бессмысленно.

Тогда поток обработки событий должен хранить ид последнего обработанного сообщения, а по событию обновления запрашивать все сообщения, новее чем этот id.
Мне кажется удобным в самом событии хранить всю необходимую информацию.
Спасибо за труд!

Хотелось бы больше информации по п. 7: как именно вы поддерживаете версионность тестов относительно версий API, какие лучшие практики совместного рефакторинга самой функциональности API и соответствующих тестов, как лучше анализировать необычное поведение устаревших версий API, когда приходится идти на компромиссы в данных — добавлять фейковую информацию, т.к. новейшее API становится в некоторых процессах уже несовместимым со старым.
Например, в старой версии были ± лайки, в новой решили сделать только лайк, а дислайк в обязательной форме указания причины дислайка. Что в этом случае было бы по вашим процессам создания/поддержки API и тестов со старым функционалом дислайков? Это просто пример, когда со временем развитие API приводит к несовместимостям в логике. Часто в таких случаях применяют разделение на большие ветки версий 1.0/2.0, интересно было бы услышать сталкивались ли вы с таким в процессе развития и какие решения принимались и на основе чего для разрешения подобных задач.
> как именно вы поддерживаете версионность тестов относительно версий API

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

> какие лучшие практики совместного рефакторинга самой функциональности API и соответствующих тестов

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

> как лучше анализировать необычное поведение устаревших версий API

Не понял вопроса.

> Например, в старой версии были ± лайки, в новой решили сделать только лайк, а дислайк в обязательной форме указания причины дислайка.

Это эффективный слом обратной совместимости. Прятать его за какие-то фейковые фасады = гримировать труп.

> Часто в таких случаях применяют разделение на большие ветки версий 1.0/2.0, интересно было бы услышать сталкивались ли вы с таким в процессе развития и какие решения принимались и на основе чего для разрешения подобных задач.

В данный момент мы поддерживаем одновременно четыре мажорных версии АПИ, и пока не планируем сворачивать поддержку старых версий.

Наше соглашение примерно следующее: должны поддерживаться (= исправляться любые критические ошибки) не менее двух последних мажорных версий; развивается при этом (добавляется новый функционал) только последняя мажорная; должен быть прямой доступ как минимум к двум минорным версиям в рамках актуальной мажорной.

Мы выпускаем мажорные версии со следующим расчетом: если проект, использующий наше АПИ, актуален и развивается, то он раз в несколько лет неизбежно будет переписан и сможет встроить обновление до новой мажорной версии в свой продуктовый цикл. В реальности получается темп «одна мажорная версия раз в два-три года», который, кажется, всех устраивает.
Ага, примерно так и предполагал, спасибо что поделились. Похоже что у вас изначально все было сделано «по уму» и не случалось резких поворотов в функциональности внутри одной версии. Это когда сделали, к примеру, фатально кривой RPC (уже не важны причины) для мобильного API, приложение в релизе несколько недель, заметили только сейчас. Да, заниматься поддержкой такой ошибки в итоге — это прятать запах освежителем, чем вынести причину его появления, но вопрос как всегда в цене. Варианта вижу два:

1) очень плохой — забить на тех, кто остался с необновленным приложением, передающим уже неправильные запросы. Если их в соотношении с остальными меньше определенного порогового уровня. К примеру, за эти две недели скачали 100 человек и не обновились, а всего активных пользователей приложения под миллион.

2) сделать костыль, для конкретной версии API конкретных приложений с занесением информации что после, к примеру, полугода этот костыль принудительно убрать. Пусть как-то хромает, но мы не нарушим серьезно работоспособность всего приложения и оно будет «хромать».

Со временем пришел к такому решению: с самой первой релизной версии в приложении при его старте делать RPC, отправляющий версию API (или билд), с которой приложение гарантировано было протестировано. При описанной выше проблемной ситуации, в приложение приходил бы ответ с обязательностью уведомления в виде alert-а (не суть как) о необходимости обновить приложение по причине критических багов. Можно это сделать на уровне всех вызовов API, не суть.

Я понимаю, что такие ситуации нужно избегать и делать все возможное, чтобы такого не было, но интерес у меня возник именно на тему разрешения/поведения в нестандартных ситуациях, когда что-то сломали и шага назад сделать нельзя.

В принципе, я ответ уже получил, просто хотел пояснить ранее озвученный мой вопрос. Еще раз спасибо!
Вообще, конечно, информации явно недостаточно, чтобы давать какие-то советы :) Но я бы, вероятно, предпочёл честно сообщить о проблемах и попытаться достучаться до всех пользователей старых версий.
Отличный троллинг с вашей стороны писать такие статьи. Сужу это в первую очередь со стороны человека, который занимался переводом проектов с использование API карт версии 1 на версию 2. Причина перехода была связана с фичами, которые были в 2.0 (кажется поддержка HTTPS). Испытывал кучу нелестных чувств к вам как сторонний разработчик. Имхо, бытует не совсем верное мнение, что в мажорной версии можно полностью сломать обратную совместимость. Это не совсем верно. Если пофиг на пользователей, то да. А если не пофиг, то делать даже в мажорной версии такие изменения нужно точечно и аккуратно. Иначе цена прикручивания новой фичи (поддержки HTTPS) выливается в стоимость полностью переписывания клиентского слоя работавшего с картой. Поверьте, да не устраивает такой вариант — нет. Стороннему разработчику может быть глубоко фиолетово на то, что причина полного изменения всех внешних интерфейсов — лишь обостренное чувство прекрасного у разработчика API. Да, это очень мешает двигаться вперед достаточно быстро, но это неизбежные копромиссы на которые крайне желательно идти, если есть желание быть лицом к пользователям. В коммерческом проекте, такой фокус легко просто не прокатит — крупные заказчики скажут «нет» и вы уйдете думать над менее радикальными изменениями и более плавному их внедрению.
Причиной создания версии 2.0 было вовсе не «обострённое чувство прекрасного» — из обострённых чувств мы обратную совместимость никогда не меняем — а вполне конкретные доводы:

— нам требовалось поддерживать соврменные технологии (transform & transition) и новые устройства (тач-девайсы, в первую очередь), чего архитектура первой версии не позволяла;
— нам требовалась гораздо более гибкая и менее связная модульность, нежели это было возможно в рамках версии 1.х
— нам требовалась глубокая локализация, с различными настройками (например, единиц измерения) для разных стран
— наконец, нас не устраивало качество клиентских решений — иными словами, вебмастера делали на нашем АПИ неудобные и неюзабельные сайты, и нам очень хотелось, насколько это возможно, с этим побороться со своей стороны.

Обо всём этом я довольно подробно рассказал в 2012 году на РИТ++ и ещё на нескольких конференциях. Кроме того, версия 2.0 была изначально запущена с подробным гайдом по переходу.

Ваша боль мне ясна; более того, АПИ 2.1 построено именно так, как вы предлагаете — итеративно с весьма незначительными изменениями. Однако, мы стараемся руководствоваться в первую очередь интересами конечных потребителей АПИ, а они на момент 2011 года явно диктовали потребность в радикальном обновлении архитектуры проеекта.
Как-то работал с очередями сообщении. Если быть точнее — IBM WebSphere MQ.
Вот где поддержка старых версии идет как нигде и никогда.
Например, в оттранслированном заголовочном файле из С в Паскаль:

(****************************************************************)
(*  MQCNO Structure -- Connect Options                          *)
(****************************************************************)

type
  MQCNO = record
    StrucId            : MQCHAR4  ;(* Structure identifier *)
    Version            : MQLONG   ;(* Structure version number *)
    Options            : MQLONG   ;(* Options that control the action of MQCONNX *)
    (* Ver:1 *)
    ClientConnOffset   : MQLONG   ;(* Offset of MQCD structure for client connection *)
    ClientConnPtr      : MQPTR    ;(* Address of MQCD structure for client connection *)
    (* Ver:2 *)
    ConnTag            : MQBYTE128;(* Queue-manager connection tag *)
    (* Ver:3 *)
    SSLConfigPtr       : PMQSCO   ;(* Address of MQSCO structure for client connection *)
    SSLConfigOffset    : MQLONG   ;(* Offset of MQSCO structure for client connection *)
    (* Ver:4 *)
    ConnectionId       : MQBYTE24 ;(* Unique Connection Identifier *)
    SecurityParmsOffset: MQLONG   ;(* Offset of MQCSP structure *)
    SecurityParmsPtr   : PMQCSP   ;(* Address of MQCSP structure *)
    (* Ver:5 *)
  end;
  PMQCNO  = ^MQCNO;
  PPMQCNO = ^PMQCNO;

Sign up to leave a comment.