API версий

9

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

Иногда вам нужно внести изменения в базу API, которая поддерживает ваш проект. Например, вам нужно добавить функцию, которая требует изменения API, нового метода или требует изменения одного из объектов или формата одного из этих объектов, передаваемых в или из API.

Предполагая, что вы также используете эти объекты в вашем общедоступном API, публичные объекты также будут меняться каждый раз, когда вы делаете это, что нежелательно, поскольку ваши клиенты могут полагаться на объекты API, оставшиеся идентичными, чтобы их код синтаксического анализа работал. (кашляют C ++ WSDL клиенты ...)

Поэтому одним из возможных решений является версия API. Но когда мы говорим «версия» API, это звучит так, как будто это также должно означать версию объектов API, а также предоставление дублирующих вызовов методов для каждой измененной сигнатуры метода. Таким образом, я бы тогда имел простой старый объект clr для каждой версии моего API, что опять-таки кажется нежелательным. И даже если я сделаю это, я, конечно, не буду создавать каждый объект с нуля, так как это приведет к огромному количеству дублированного кода. Скорее API может расширять частные объекты, которые мы используем для нашего базового API, но затем мы сталкиваемся с той же проблемой, потому что добавленные свойства также будут доступны в общедоступном API, когда они не должны быть.

Так какое здравомыслие обычно применяется в этой ситуации? Я знаю, что многие публичные сервисы, такие как Git для Windows, поддерживают версионный API, но мне сложно представить архитектуру, которая поддерживает это без огромного количества дублирующегося кода, охватывающего различные версионные методы и объекты ввода / вывода.

Мне известно, что такие процессы, как семантическое управление версиями, пытаются придать здравый смысл, когда должны произойти разрывы публичного API. Проблема в том, что многие или большинство изменений требуют нарушения открытого API, если объекты не разделены более, но я не вижу хорошего способа сделать это без дублирования кода.

случай
источник
1
I don't see a good way to do that without duplicating code- Ваш новый API всегда может вызывать методы в вашем старом API или наоборот.
Роберт Харви
2
AutoMapper на помощь, к сожалению, да - вам нужны разные версии каждого контракта, не забывайте, что все объекты, на которые ссылается ваш контракт, являются частью этого контракта. В результате ваша фактическая реализация должна иметь собственную единую версию моделей, и вам необходимо преобразовать единую версию в различные версии контракта. AutoMapper может помочь вам в этом, а также сделать внутренние модели умнее, чем контрактные модели. В отсутствие AutoMapper я использовал методы расширения для создания простых переводов между внутренними моделями и контрактными моделями.
Джимми Хоффа
Есть ли конкретная платформа / контекст для этого? (т. е. библиотеки DLL, API REST и т. д.)
GrandmasterB
.NET, с MVC и Webforms ui, dll классы. У нас есть как отдых, так и мыло API.
Дело

Ответы:

6

При поддержке API, который используется третьими сторонами, вам неизбежно потребуется внести изменения. Уровень сложности будет зависеть от типа происходящих изменений. Вот основные сценарии, которые подходят:

  1. Новая функциональность добавлена ​​в существующий API
  2. Старая функциональность устарела от API
  3. Существующая функциональность в API меняется каким-то образом

Новая функциональность Добавление к существующему API

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

Старая функциональность устарела от API

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

Существующая функциональность в API меняется каким-то образом

Этот сценарий будет зависеть от типа изменений. Если входные параметры больше не нужно использовать, вы можете просто обновить службу / библиотеку, чтобы игнорировать дополнительные данные. В библиотеке перегруженный метод должен вызывать новый метод, который требует меньше параметров. В размещенном сервисе конечная точка игнорирует дополнительные данные, и она может обслуживать оба типа клиентов и выполнять ту же логику.

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

другие мысли

Rather, the API is likely to extend the private objects we are using for our base API, but then we run into the same problem because added properties would also be available in the public API when they are not supposed to be.

Не открывайте внутренние частные объекты через вашу библиотеку / службу. Создайте свои собственные типы и отобразите внутреннюю реализацию. Это позволит вам вносить внутренние изменения и минимизировать объем обновлений, которые должны выполнять внешние клиенты.

The problem is more that it seems like many or most changes require breaking the public API if the objects aren't more separated, but I don't see a good way to do that without duplicating code.

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

Не рассматривайте этот шаг как «дублирующий код». Хотя они и похожи, они являются отдельными объектами, которые стоит создать. Хотя изменения внешнего API почти всегда требуют соответствующего изменения внутренней реализации, внутренние изменения реализации не всегда должны изменять внешний API.

пример

Предположим, вы предоставляете решение для обработки платежей. Вы используете PaymentProviderA для транзакций по кредитным картам. Позже вы получите лучший тариф через платежный процессор PaymentProviderB. Если ваш API выставил поля типа «Кредитная карта / адрес счета» вашего типа вместо представления PaymentProviderA, тогда изменение API равно 0, так как интерфейс остался прежним (в любом случае, надеюсь, что для PaymentProviderB требуются данные, которые не требуются для PaymentProviderA, вам нужно выбрать либо: поддержите оба или сохраните худший уровень с PaymentProviderA).

Фил Паттерсон
источник
Спасибо за этот очень подробный ответ. Знаете ли вы какие-либо примеры проектов с открытым исходным кодом, которые я мог бы изучить, чтобы узнать, как существующие проекты сделали это? Я хотел бы увидеть некоторые конкретные примеры того, как они организовали код, который позволяет им делать это, не просто копируя свой различный код построения POCO в различные объекты версий, поскольку, если вы вызываете разделяемые методы, вам придется разделить его общего метода, чтобы иметь возможность редактировать этот объект для версионного объекта.
Дело
1
Я не знаю ни одного хорошего примера. Если бы у меня было время на этих выходных, я мог бы создать тестовое приложение для GitHub, чтобы показать, как некоторые из этих вещей могут быть реализованы.
Фил Паттерсон,
1
Сложность в том, что с высокого уровня существует так много разных способов попытаться минимизировать связь между клиентами и серверами. WCF имеет интерфейс IExtensibleDataObject, который позволяет передавать данные, которых нет в договоре от клиента, и отправлять их по сети на сервер. Google создал Protobuf для связи между системами (есть реализации с открытым исходным кодом для .NET, Java и т. Д.). Кроме того, существует много систем, основанных на сообщениях, которые также могут работать (при условии, что ваш процесс может выполняться асинхронно).
Фил Паттерсон
Может быть неплохо показать конкретный пример дублирования кода, который вы пытаетесь удалить (как новый вопрос переполнения стека), и посмотреть, какие решения предлагает сообщество. Трудно ответить на вопрос в общих чертах; поэтому конкретный сценарий может быть лучше.
Фил Паттерсон