Предположим, у вас есть большой проект, поддерживаемый базой API. Проект также предоставляет публичный API, который могут использовать конечные пользователи.
Иногда вам нужно внести изменения в базу API, которая поддерживает ваш проект. Например, вам нужно добавить функцию, которая требует изменения API, нового метода или требует изменения одного из объектов или формата одного из этих объектов, передаваемых в или из API.
Предполагая, что вы также используете эти объекты в вашем общедоступном API, публичные объекты также будут меняться каждый раз, когда вы делаете это, что нежелательно, поскольку ваши клиенты могут полагаться на объекты API, оставшиеся идентичными, чтобы их код синтаксического анализа работал. (кашляют C ++ WSDL клиенты ...)
Поэтому одним из возможных решений является версия API. Но когда мы говорим «версия» API, это звучит так, как будто это также должно означать версию объектов API, а также предоставление дублирующих вызовов методов для каждой измененной сигнатуры метода. Таким образом, я бы тогда имел простой старый объект clr для каждой версии моего API, что опять-таки кажется нежелательным. И даже если я сделаю это, я, конечно, не буду создавать каждый объект с нуля, так как это приведет к огромному количеству дублированного кода. Скорее API может расширять частные объекты, которые мы используем для нашего базового API, но затем мы сталкиваемся с той же проблемой, потому что добавленные свойства также будут доступны в общедоступном API, когда они не должны быть.
Так какое здравомыслие обычно применяется в этой ситуации? Я знаю, что многие публичные сервисы, такие как Git для Windows, поддерживают версионный API, но мне сложно представить архитектуру, которая поддерживает это без огромного количества дублирующегося кода, охватывающего различные версионные методы и объекты ввода / вывода.
Мне известно, что такие процессы, как семантическое управление версиями, пытаются придать здравый смысл, когда должны произойти разрывы публичного API. Проблема в том, что многие или большинство изменений требуют нарушения открытого API, если объекты не разделены более, но я не вижу хорошего способа сделать это без дублирования кода.
источник
I don't see a good way to do that without duplicating code
- Ваш новый API всегда может вызывать методы в вашем старом API или наоборот.Ответы:
При поддержке 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).
источник