Что значит обратная совместимость
Обратная совместимость
Обра́тная совмести́мость — наличие в новой версии компьютерной программы или компьютерного оборудования интерфейса, присутствующего в старой версии, в результате чего другие программы (или человек) могут продолжать работать с новой версией без значительной переделки (или переучивания). Полная обратная совместимость означает, что при замене старой версии компонента на новую, функционирование всей системы в целом не нарушится.
Обратная совместимость является одним из важнейших приоритетов в компьютерной индустрии. Обеспечение обратной совместимости позволяет пользователям, при переходе к новой версии, частично или полностью сохранить ценность тех наработок, которые они приобрели при адаптации к работе со старой версией программного или аппаратного обеспечения.
В то же время, обеспечение обратной совместимости обладает и недостатками, сдерживая развитие технологий. Так, в современных компьютерах очень много «унаследовано» от их предыдущих поколений, от чего нельзя отказаться из-за совместимости. Это позволяет сохранить предыдущие инвестиции производителей и потребителей, но одновременно не позволяет реализовать более прогрессивные функции и повышает вероятность возникновения ошибок.
Обратная совместимость в программном обеспечении
Обратная совместимость применительно к программному обеспечению означает способность более поздних версий программы работать с файлами, созданными более ранней версией этой же программы или программы, реализующей те же алгоритмы, что и более ранняя версия. Так, например, в Microsoft Office присутствует поддержка целого ряда форматов, которые на данный момент почти не используются.
Обратная совместимость в аппаратном обеспечении
Обратная совместимость применительно к аппаратному обеспечению означает способность более новых типов оборудования эмулировать работу предшествующих. Так, например, микропроцессоры Intel до сих пор поддерживают весь набор инструкций, использовавшихся ещё в самых первых представителях этого ряда.
Преимущества и недостатки обратной совместимости
Главным недостатком обратной совместимости является усложнение аппаратного или программного обеспечения. В случае с ПО это чаще всего приводит к увеличению размеров программного продукта, а в случае с аппаратным обеспечением это приводит усложнению архитектуры, то есть строения, соответствующего элемента аппаратного обеспечения. В конечном итоге всё это приводит к увеличению стоимости производства и поддержки (часто после смены базовой технологии невозможно найти специалистов поддержки, владеющих обеими технологиями в достаточной степени).
Между тем отсутствие обратной совместимости вызывает ряд неудобств. Так, например, в операционных системах Windows 2000/XP эмулятор MS-DOS не обладает полной обратной совместимостью с реальной ОС MS-DOS, в отличие от Windows 9x (в состав которой входит не эмулятор, а «настоящая» MS-DOS, запускаемая до Windows и используемая для работы DOS-приложений). Вследствие этого во многих случаях предприятия вынуждены пользоваться более ранними версиями этой операционной системы, либо устанавливать полноценную MS-DOS на виртуальные компьютеры, так как используемое программное обеспечение требует полноценной ОС MS-DOS, а не урезанной в возможностях.
Что такое обратная совместимость
Это когда старая игра запускается на новой приставке
Совместимость — это когда что-то одно может работать с чем-то другим без дополнительных настроек, переделок или костылей.
Например, есть дизельное топливо. Оно совместимо с дизельными автомобилями — независимо от их марки, года выпуска, пробега или других параметров. А ещё можно приехать на своей машине на любую заправку с дизелем, а не искать специальную заправку только для определённой марки машин. Вот это всё — совместимость.
В ИТ чаще всего говорят про прямую и обратную совместимость, когда в одну сторону совместимость работает, а в другую — нет.
Когда совместимости нет
Допустим, на предприятии есть программа, которая обслуживает базу данных в нестандартном формате. Проходит время, разработчики увольняются и перестают поддерживать программу. Но программа нужна предприятию, поэтому ей продолжают пользоваться.
Чтобы код всегда оставался актуальным и чистым, используют рефакторинг. Про это у нас есть отдельная статья.
Спустя пять лет программа конкретно устарела: в ней нет поддержки современных удобных протоколов и она не работает на новых операционных системах. Но зато она работает со старой базой.
Предприятие ищет какое-то решение своей проблемы, но выясняется, что ни одна программа не совместима с её базой данных. Это ситуация, когда совместимости нет.
Обычная (или прямая) совместимость
Предприятие обращается за помощью к новым разработчикам: нужно сделать программу, которая умеет работать с нужным форматом баз данных. И, кроме этого, в программе должны быть все современные протоколы и поддержка современных операционок. Разработчики создают этот софт. Это прямая совместимость.
Прямая совместимость — это когда что-то одно может работать с другим. Это относительно простая задача для разработчика, потому что для неё достаточно добавить поддержку нужного формата данных или протоколов.
Обратная совместимость
А теперь представим другую ситуацию: разработчики не ушли с предприятия, а продолжили работать над проектом. В итоге за пять лет они придумали новую версию базы: тоже нестандартную, но более быструю. Но данные за пять лет всё ещё живут в старой базе данных, а переносить их нельзя.
Теперь перед программистами стоит противоречие:
Это обратная совместимость: новая версия программы поддерживает и новое, и старое.
Пример обратной совместимости — операционная система Windows. В каждой новой версии есть поддержка части программ, которые написаны для старых версий. Даже если вы попробуете запустить аудиоплеер, разработанный для Windows 95, в свежей Windows 10, то, вероятно, у вас это получится.
Также обратная совместимость позволяет нам взять игру для PlayStation 4 и запустить её на новой PlayStation 5 (скорее всего). А вот прямой совместимости у них нет: игра, написанная специально для PS5, вряд ли запустится на PS4.
В чём подвох
Обратная совместимость — головная боль для разработчиков. Ради полноценной поддержки старого формата иногда приходится жертвовать скоростью работы или надёжностью.
Например, в новой версии используется более современный протокол передачи данных. Но чтобы старые версии тоже смогли работать, разработчикам приходится поддерживать одновременно два протокола — новый и старый, каждый проверять на ошибки. Чем больше отличий в версиях — тем сложнее это поддерживать. Иногда для реализации старых технологий тратится намного больше ресурсов, чем для такой же работы — в новых.
Как правильно разрабатывать API с поддержкой обратной совместимости. Семинар в Яндексе
Привет! Меня зовут Сергей Константинов, в Яндексе я руковожу разработкой API Карт. Недавно я поделился опытом поддержки обратной совместимости со своими коллегами. Мой доклад состоял из двух неравных частей. Первая, большая, посвящена тому, как правильно разрабатывать API, чтобы потом не было мучительно больно. Вторая же про то, что делать, если вам нужно что-то рефакторить и не сломать по дороге обратную совместимость.
Если заглянуть в Википедию, то про обратную совместимость там будет написано, что это сохранение интерфейса системы при выпуске новых версий. На самом деле, для конечных пользователей обратная совместимость означает, что код, написанный для предыдущей версии системы, работает функционально так же и в следующей версии.
Для разработчика обратная совместимость в первую очередь подразумевает, что единожды принятое обязательство предоставлять какую-либо функциональность невозможно отменить, исправить или перестать поддерживать.
Зачем нужно брать на себя обязательства? Во-первых, вы экономите время и деньги своим пользователям. Наивно думать, будто бы не поддерживать обратную совместимость дешевле. На самом деле, вы просто размазываете стоимость поддержки по клиентам. Один факап в продакшене может стоить гораздо больше, чем вся разработка всего продукта.
Во-вторых вы поддерживаете свою карму. Сознательный слом обратной совместимости расстраивает пользователей куда больше, чем баги. Людям очень не нравится, когда им явно демонстрируют равнодушие к их проблемам.
В-третьих, обратная совместимость – это конкурентное преимущество. Она подразумевает возможность переходить на более свежие версии без затрат на разработку, гарантию того, что сервис не сломается продакшене.
Обратная совместимость: правильная архитектура
Что можно сделать на этапе проектирования, чтобы потом не было мучительно больно? Тут нужно сделать три предварительных замечания. Во-первых, обратная совместимость не бывает бесплатной. Выстраивание правильной архитектуры влечёт за собой накладные расходы. Вам придётся больше думать, вводить больше сущностей, писать избыточный код.
Во-вторых, прежде чем приступать к разработке, нужно обозначить сферу ответственности, четко прояснить, что именно будет поддерживаться. Сводите к минимуму ситуации, когда какое-то публично доступное API не описано в документации. Никогда не давайте на чтение (и, тем более, на запись) сущности, формат которых не описан.
В-третьих, предполагается, что ваш API спроектирован правильно и структурирован по уровням абстракции.
Предположим, что все это мы поняли и осознали. Пора переходить к правилам, которые которые мы вынесли из нашего почти пятилетноего опыта.
Правило №1: больше интерфейсов
В пределе в вашей публичной документации не должно быть ни одной сигнатуры, принимающей конкретные типы, а не интерфейсы. Исключение может быть сделано для базовых глобальных классов и явно подчинённых компонент.
Почему это помогает избежать потери обратной совместимости? Если в сигнатуре заявлен интерфейс, у вас не будет проблем, когда у вас появится вторая (третья, четвёртая) реализация интерфейса. Атомизируется ответственность объектов. Интерфейс не накладывает условий, чем должен быть передаваемый объект: он может быть как наследником стандартного объекта, так и самостоятельной реализацией.
Почему это полезно при проектировании API? Выделение интерфейсов в первую очередь необходимо разработчику для наведения порядка в голове. Если ваш метод принимает в качестве параметра объект с 20 полями и 30 методами, очень рекомендуется задуматься, что конкретно необходимо из этих полей и методов.
В результате применения этого правила вы должны получить на выходе много дробных интерфейсов. Ваши сигнатуры не должны требовать от входного параметра больше 5±2 свойств или методов. Вы получите представление о том, какие свойства ваших объектов важны в контексте общей архитектуры системы, а какие – нет. Как следствие, снизится избыточность интерфейсов
Правило №2: иерархия
Ваши объекты должны быть выстроены в иерархию: кто с кем взаимодействует. Когда на эту иерархию наложатся интерфейсы, которые вы предъявили к своим объектам, вы получите некую иерархию интерфейсов. Теперь самое важное: объект имеет право знать только об объектах соседних уровней.
Почему это помогает избежать потери обратной совместимости? Снижается общая связанность архитектуры, меньше связей – меньше side-эффектов. А при изменении какого-либо объекта вы можете задеть только его соседей по дереву.
Добиться этого очевидными способами можно не всегда. Нужные методы и свойства нужно пробрасывать по цепочке через промежуточные звенья (с учетом уровня абстракции, разумеется!). Тем самым вы автоматически получите набор точек расширения, которые потом могут пригодиться.
Правило №3: контексты
Рассматривайте любую промежуточную ступень иерархии как информационный контекст для нижележащей ступени.
Пример:
Map = картографический контекст (наблюдаемая область карты + масштаб).
IPane = контекст позиционирования в клиентских координатах.
ITileContainer = контекст позиционирования в тайловых координатах.
Ваше дерево объектов можно будет рассматривать как иерархию контекстов. Каждый уровень иерархии должен соответствовать какому-то уровню абстракции.
Почему это помогает избежать потери обратной совместимости? Правильно построенное дерево контекстов практически никогда не изменится при рефакторинге: потоки информации могут появляться, но очень вряд ли пропадут. Правило контекстов позволяет эффективно изолировать уровни иерархии друг от друга.
Это полезно при проектировании API, так как держать в голове информационную схему проекта существенно проще, чем полное дерево. А описание объектов в терминах предоставляемых ими контекстов позволяет правильно выделять уровни абстракции.
Правило №4: consistency
В данном случае я использует термин consistency в парадигме ACID для баз данных. Это означает, что между транзакциями состояние объектов всегда должно быть валидным. Любой объект должен предоставлять полное описание своего состояния в любой момент и полный набор событий, позволяющий отслеживать все изменения своего состояния.
Подобные паттерны нарушают consistency:
В частности, отсюда следует правило: избегайте методов update, build, apply.
Это помогает избежать потери обратной совместимости, т.к. внешний наблюдатель всегда может полностью восстановить состояние и историю объекта по его публичному интерфейсу. Кроме того, такой объект всегда можно подменить или склонировать, не обладая знанием о его внутреннем устройстве.
Когда у вас организовано такое взаимодействие, что есть state объекта и событие о его изменении, номенклатура методов и событий ваших объектов становится менее разнообразной и более консистентной. Вам станет проще выделять интерфейсы и держать все это в голове.
Правило №5: события
Организуйте взаимодействие между объектами с помощью событий, причём в обе стороны.
Рассмотрим два примера, как можно организовать взаимодействие между кнопкой и макетом:
Вторая схема взаимодействия получается нативно при соблюдении требования consistency:
В первом случае кнопка и макет знают подробности о внутреннем устройстве друг друга, во втором – нет.
Это помогает избежать потери обратной совместимости, т.к. события необязательны к исполнению для обоих объектов: вы легко сможете поддерживать такие реализации обоих объектов, которые реагируют только на часть событий и отображают только часть состояния второго объекта. Если у вас появится третий объект, которому необходимо реагировать на то же действие – у вас не будет проблем.
Если вы верно выполнили и предыдущие четыре шага, у вас получается стандартный паттерн: у вас есть, state, события об его изменении, нижележащий объект, который слушает это событие и реагирует на него каким-то образом. Ваша организация взаимодействия между объектами значительно унифицируется. Взаимодействие между объектами таким образом базируется на общих методах и событиях, а не частных, т.е. будет содержать гораздо меньше специфики конкретных объектов
Правило №6: делегирование
Шестое правило логично вытекает из первых пяти. Вы построили всю систему, у вас есть интерфейсы и события, уровни абстракции. Теперь нужно насколько это возможно перенести всю логику на нижний уровень абстракции. Поскольку чаще всего изменяется реализация и функциональность именно нижнего уровня абстракции (верстка, протоколы взаимодействия, etc), интерфейс к нижнему уровню абстракции должен быть максимально общим.
При таком подходе связи между объектами становятся насколько это возможно абстрактными. Вы сможете безболезненно переписать объекты нижнего уровня абстракции целиком при необходимости
Правило №7: тесты
Пишите тесты на интерфейс.
Правило №8: внешние источники
В абсолютном большинстве случаев самые большие проблемы с сохранением обратной совместимости возникают вследствие несохранения обратной совместимости другими сервисами. Если вы не контролируете смежный сервис (источник данных) – заведите к нему версионируемую обёртку на своей стороне.
Обратная совместимость: рефакторинг
Прежде, чем приступать
Полтора приёма рефакторинга:
От релиза к релизу
Заведите себе блокнотик душевного покоя:
Постановка проблемы обратной совместимости
Как обычно, дадим смысловой определение «обратной совместимости», прежде чем начинать изложение.
Обратная совместимость — это свойство всей системы API быть стабильной во времени. Это значит следующее: код, написанный разработчиками с использованием вашего API, продолжает работать функционально корректно в течение длительного времени. К этому определению есть два больших вопроса, и два уточнения к ним.
Что значит «функционально корректно»?
Это значит, что код продолжает выполнять свою функцию — решать какую-то задачу пользователя. Это не означает, что он продолжает работать одинаково: например, если вы предоставляете UI-библиотеку, то изменение функционально несущественных деталей дизайна, типа глубины теней или формы штриха границы, обратную совместимость не нарушит. А вот, например, изменение размеров визуальных компонентов, скорее всего, приведёт к тому, что какие-то пользовательские макеты развалятся.
Что значит «длительное время»?
С нашей точки зрения длительность поддержания обратной совместимости следует увязывать с длительностью жизненных циклов приложений в соответствующей предметной области. Хороший ориентир в большинстве случаев — это LTS-периоды платформ. Так как приложение все равно будет переписано в связи с окончанием поддержки платформы, нормально предложить также и переход на новую версию API. В основных предметных областях (десктопные и мобильные операционные системы) этот срок исчисляется несколькими годами.
Почему обратную совместимость необходимо поддерживать (в том числе предпринимать необходимые меры ещё на этапе проектирования API) — понятно из определения. Прекращение работы приложения (полное или частичное) по вине поставщика API — крайне неприятное событие, а то и катастрофа, для любого разработчика, особенно если он платит за это API деньги.
Но развернём теперь проблему в другую сторону: а почему вообще возникает проблема с поддержанием обратной совместимости? Почему мы можем хотеть её нарушить? Ответ на этот вопрос, при кажущейся простоте, намного сложнее, чем на предыдущий.
Мы могли бы сказать, что обратную совместимость приходится нарушать для расширения функциональности API. Но это лукавство: новая функциональность на то и новая, что она не может затронуть код приложений, который её не использует. Да, конечно, есть ряд сопутствующих проблем, приводящих к стремлению переписать наш код, код самого API, с выпуском новой мажорной версии:
код банально морально устарел, внесение в него изменений, пусть даже в виде расширения функциональности, нецелесообразно;
новая функциональность не была предусмотрена в старом интерфейсе: мы хотели бы наделить уже существующие сущности новыми свойствами, но не можем;
наконец, за прошедшее после изначального релиза время мы узнали о предметной области и практике применения нашего API гораздо больше, и сделали бы многие вещи иначе.
Эти аргументы можно обобщить как «разработчики API не хотят работать со старым кодом», не сильно покривив душой. Но и это объяснение неполно: даже если вы не собираетесь переписывать код API при добавлении новой функциональности, или вы вовсе её и не собирались добавлять, выпускать новые версии API — мажорные и минорные — всё равно придётся.
NB: в рамках этой главы мы не разделяем минорные версии и патчи: под словами «минорная версия» имеется в виду любой обратно совместимый релиз API.
Напомним, что API — это мост, средство соединения разных программируемых контекстов. И как бы нам ни хотелось зафиксировать конструкцию моста, наши возможности ограничены: мост-то мы можем зафиксировать — да вот края ущелья, как и само ущелье, не можем. В этом корень проблемы: мы не можем оставить свой код без изменений, поэтому нам придётся рано или поздно потребовать, чтобы клиенты изменили свой.
Помимо наших собственных поползновений в сторону изменения архитектуры API, три других тектонических процесса происходят одновременно: размывание клиентов, предметной области и нижележащей платформы.
Фрагментация клиентских приложений
В тот момент, когда вы выпустили первую версию API, и первые клиенты начали использовать её — ситуация идеальна. Есть только одна версия, и все клиенты работают с ней. А вот дальше возможны два варианта развития событий:
Если платформа поддерживает on-demand получение кода, как старый-добрый Веб, и вы не поленились это получение кода реализовать (в виде платформенного SDK, например, JS API), то развитие API более или менее находится под вашим контролем. Поддержание обратной совместимости сводится к поддержанию обратной совместимости клиентской библиотеки, а вот в части сервера и клиент-серверного взаимодействия вы свободны.
Это не означает, что вы не можете нарушить обратную совместимость — всё ещё можно напортачить с заголовками кэширования SDK или банально допустить баг в коде. Кроме того, даже on-demand системы все равно не обновляются мгновенно — автор сталкивался с ситуацией, когда пользователи намеренно держали вкладку браузера открытой неделями, чтобы не обновляться на новые версии. Тем не менее, вам почти не придётся поддерживать более двух (последней и предпоследней) минорных версий клиентского SDK. Более того, вы можете попытаться в какой-то момент переписать предыдущую мажорную версию библиотеки, имплементировав её на основе API новой версии.
Если поддержка on-demand кода платформой не поддерживается или запрещена условиями, как это произошло с современными мобильными платформами, то ситуация становится гораздо сложнее. По сути, каждый клиент — это «слепок» кода, который работает с вашим API, зафиксированный в том состоянии, в котором он был на момент компиляции. Обновление клиентских приложений по времени растянуто гораздо дольше, нежели Web-приложений; самое неприятное здесь состоит в том, что некоторые клиенты не обновятся вообще никогда — по одной из трёх причин:
пользователь не может обновиться вообще, потому что его устройство больше не поддерживается.
В современных реалиях все три категории в сумме легко могут составлять десятки процентов аудитории. Это означает, что прекращение поддержки любой версии API является весьма заметным событием — особенно если приложения разработчика поддерживают более широкий спектр версий платформы, нежели ваше API.
Вы можете не выпускать вообще никаких SDK, предоставляя только серверное API в виде, например, HTTP эндпойнтов. Вам может показаться, что таким образом, пусть ваше API и стало менее конкурентоспособным на рынке из-за отсутствия SDK, вы облегчили себе задачу поддержания обратной совместимости. На самом деле это совершенно не так: раз вы не предоставляете свой SDK — или разработчики возьмут неофициальный SDK (если кто-то его сделает), или просто каждый из них напишет по фреймворку. Стратегия «ваш фреймворк — ваша ответственность», к счастью или к сожалению, работает плохо: если на вашем API пишут некачественные приложения — значит, ваше API само некачественное. Уж точно по мнению разработчиков, а может и по мнению пользователей, если работа API внутри приложения пользователю видна.
Конечно, если ваше API достаточно stateless и не требует клиентских SDK (или же можно обойтись просто автогенерацией SDK из спецификации), эти проблемы будут гораздо менее заметны, но избежать их полностью можно только одним способом — никогда не выпуская новых версий API. Во всех остальных случаях вы будете иметь дело с какой-то гребёнкой распределения количества пользователей по версиям API и версиям SDK.
Эволюция предметной области
Другая сторона ущелья — та самая нижележащая функциональность, к которой вы предоставляете API. Она, разумеется, тоже не статична и развивается в какую-то сторону:
Как правило, API абстрагирует некоторую более гранулярную предметную область. В случае нашего примера с API кофе-машин разумно ожидать, что будут появляться новые модели с новым API, которые нам придётся включать в свою платформу, и гарантировать возможность сохранения того же интерфейса абстракции — весьма непросто. Даже если просто добавлять поддержку новых видов нижележащих устройств, не добавляя ничего во внешний интерфейс — это всё равно изменения в коде, которые могут в итоге привести к несовместимости, пусть и ненамеренно.
Стоит также отметить, что далеко не все поставщики API относятся к поддержанию обратной совместимости, да и вообще к качеству своего ПО, так же серьёзно, как и (надеемся) вы. Стоит быть готовым к тому, что заниматься поддержанием вашего API в рабочем состоянии, то есть написанием и поддержкой фасадов к меняющемуся ландшафту предметной области, придётся именно вам, и зачастую довольно внезапно.
Дрифт платформ
Наконец, есть и третья сторона вопроса — «ущелье», через которое вы перекинул свой мост в виде API. Код, который напишут разработчики, исполняется в некоторой среде, которую вы не можете контролировать, и она тоже эволюционирует. Появляются новые версии операционной системы, браузеров, протоколов, языка SDK. Разрабатываются новые стандарты и принимаются новые соглашения, некоторые из которых сами по себе обратно несовместимы, и поделать с этим ничего нельзя.
Как и в случае со старыми версиями приложений, старые версии платформ также приводят к фрагментации, поскольку разработчикам (в том числе и разработчикам API) объективно тяжело поддерживать старые платформы, а пользователям столь же объективно тяжело обновляться, так как обновление операционной системы зачастую невозможно без замены самого устройства на более новое.
Самое неприятное во всём этом то, что к изменениям в API подталкивает не только поступательный прогресс в виде новых платформ и протоколов, но и банальная мода и вкусовщина. Буквально несколько лет назад были в моде объёмные реалистичные иконки, от которых все отказались в пользу плоских и абстрактных — и большинству разработчиков визуальных компонентов пришлось, вслед за модой, переделывать свои библиотеки, выпуская новые наборы иконок или заменяя старые. Аналогично прямо сейчас повсеместно внедряется поддержка «ночных» тем интерфейсов, что требует изменений в большом количестве API.
Политика обратной совместимости
Итого, если суммировать:
Опишем кратко эти решения и ключевые принципы их принятия.
Как часто выпускать мажорные версии API.
Это в основном продуктовый вопрос. Новая мажорная версия API выпускается, когда накоплена критическая масса функциональности, которую невозможно или слишком дорого поддерживать в рамках предыдущей мажорной версии. В стабильной ситуации такая необходимость возникает, как правило, раз в несколько лет. На динамично развивающихся рынках новые мажорные версии можно выпускать чаще, здесь ограничителем являются только ваши возможности выделить достаточно ресурсов для поддержания зоопарка версий. Однако следует заметить, что выпуск новой мажорной версии раньше, чем была стабилизирована предыдущая (а на это как правило требуется от нескольких месяцев до года), выглядит для разработчиков очень плохим сигналом, означающим риск постоянно сидеть на сырой платформе.
Какое количество мажорных версий поддерживать одновременно.
Что касается мажорных версий, то теоретический ответ мы дали выше: в идеальной ситуации жизненный цикл мажорной версии должен быть чуть длиннее жизненного цикла платформы. Для стабильных ниш типа десктопных операционных систем это порядка 5-10 лет, для новых и динамически развивающихся — меньше, но всё равно несколько лет. Практически следует смотреть на долю потребителей, реально продолжающих пользоваться версией.
Какое количество минорных версий (в рамках одной мажорной) поддерживать одновременно.
Для минорных версий возможны два варианта:
Более подробно мы рассмотрим эти вопросы в следующих главах. Дополнительно в разделе III мы также обсудим, каким образом предупреждать потребителей о выходе новых версий и прекращении поддержки старых, и как стимулировать их переходить на новые версии API.
Это черновик будущей главы книги о разработке API. Работа ведётся на Github.
Англоязычный вариант этой же главы опубликован на medium. Я буду признателен, если вы пошарите его на реддит — я сам не могу согласно политике платформы.