Перед тем как говорить об API, сначала стоит определиться с тем, что такое интерфейс в целом.

Если набрать в поисковике слово «интерфейс», то практически все результаты будут так или иначе связаны с программным обеспечением.
Но если мы попытаемся понять глубинный смысл этого слова, то окажется, что интерфейс – это:
-
контактная поверхность;
-
граница раздела;
-
стык;
-
место соприкосновения – точка, в которой встречаются два объекта.

Например, пользовательский интерфейс, User Interface (UI) – это место соприкосновения человека с программным обеспечением или техническим устройством через различные элементы управления: кнопки, переключатели, ползунки и другие инструментальные средства.
Причем нам, чтобы пользоваться устройством, не обязательно понимать, как оно устроено и что именно там происходит внутри. Главное – научиться пользоваться его интерфейсом.

То же самое можно сказать и про программный интерфейс (API) – это тоже место соприкосновения между программами или различными компонентами внутри одной программы.

В целом для построения систем выделяют два архитектурных подхода:
-
Монолит – когда все функциональные блоки собраны вместе и тесно связаны.
-
Микросервисы – когда система состоит из большого количества различных сервисов, которые взаимодействуют между собой через программные интерфейсы.

С точки зрения этих архитектурных подходов, 1С – это модульный монолит, потому что:
-
с одной стороны, мы можем подключить 1С к различным другим системам;
-
с другой стороны, сама система 1С может состоять из нескольких изолированных компонентов (библиотек), которые также структурированы по подсистемам и модулям.

Интеграция 1С с внешним миром возможна разными способами:
-
мы можем получать данные с внешних ресурсов через FTP-соединение;
-
интегрироваться с помощью внешних компонент – Native API или COM;
-
встраивать веб-клиент 1С в iframe на сайте и управлять им извне с помощью webclient1ce.js;
-
вызывать из 1С внешние HTTP-сервисы – обращаться к ним через HTTP-соединение;
-
развернуть HTTP-сервер, опубликовать на нем HTTP-сервисы и вызывать через них извне нужные методы;
-
использовать клиент для SOAP-сервиса – через WS-прокси и WS-определения;
-
организовать сервер SOAP и обращаться к методам через Web-сервисы;
-
вызывать 1С с помощью командного интерфейса – Command Line Interface (CLI);
-
использовать WebSocket-клиенты;
-
обращаться к почте по SMTP и IMAP – для этого есть специальный механизм ИнтернетПочтовыйПрофиль;
-
у 1С:Шины есть сервисы интеграции, которые также обладают интерфейсом;
-
использовать ODBC – подключать через него к 1С внешние источники данных;
-
а еще недавно объявили, что в платформе 8.5.4 будет поддержан gRPC.

В работе платформы используется еще и множество неявных интерфейсов:
-
Например, при работе в 1С с файлами и операционной системой мы фактически используем интерфейс операционной системы.
-
Работа с криптографией – это тоже интерфейс.
-
Когда мы помещаем на форму 1С поле HTML-документа, встроенный в него webKit также предоставляет нам интерфейс для взаимодействия с 1С.
-
Когда мы описываем вызов приложений из 1С с помощью «ЗапуститьПриложение», мы тоже следуем интерфейсу командной строки – только уже для запускаемого приложения.
А с архитектурной точки зрения в 1С интерфейсов еще больше:
-
Например, для подключения 1С к веб-серверам также используются интерфейсы: для Apache – это WSAPI, а для IIS – ISAPI.
-
RAS и RAC тоже общаются между собой с помощью согласованного интерфейса.
-
Даже внутри консоли администрирования мы подключаемся к кластеру именно через COM-интерфейс администрирования кластера.
Интерфейсов очень много, и мы встречаемся с ними повсюду. Практически любое современное приложение взаимодействует с внешними сервисами и использует для этого различные интерфейсы.
Поговорим… О внутренних интерфейсах
Но сейчас мы в первую очередь будем говорить о внутренних интерфейсах – тех, которые позволяют компонентам модульного монолита общаться между собой.

Для этой цели я предлагаю в первую очередь рассмотреть стандарт 543 – он важен тем, что вводит определение «Функциональная подсистема».
Мы знаем, что в дереве объектов метаданных есть подсистемы. Их можно использовать для двух целей:
-
Для формирования командного интерфейса – того самого интерфейса, который будет отображен пользователям.
-
Для логической группировки объектов метаданных в функциональную подсистему – чтобы удобнее управлять схожими по назначению объектами в процессе разработки.
Снимая с подсистемы флажок «Включать в командный интерфейс», мы получаем функциональную подсистему.

Стандарт 551 вводит более укрупненное понятие – «Библиотека». Так называется конфигурация, не предназначенная для самостоятельного использования, которая служит для встраивания в конечные прикладные решения. Именно библиотеки и являются компонентами в модульном монолите.
Библиотека может содержать в себе множество различных функциональных подсистем. Например, В БСП сейчас порядка 80 функциональных подсистем.
А всего в современные «Бухгалтерии предприятия» (или ERP) встроено порядка 25 библиотек.

Этот же стандарт 551 вводит для модуля три основных области видимости, в которых мы можем писать код.
-
Программный интерфейс. Сюда относят те самые экспортные процедуры и функции, которые мы будем вызывать, обращаясь к библиотеке извне – именно они и определяют интерфейс модуля.
-
Служебный программный интерфейс – это тоже экспортные процедуры и функции, но вызываемые только в рамках одной библиотеки. Они служат для того, чтобы связывать разные функциональные подсистемы внутри одной библиотеки.
-
Служебные процедуры и функции – тоже могут содержать экспортные процедуры и функции, но определяют реализацию только в рамках функциональной подсистемы, изолируя ее от всего остального мира.

Мы можем представить, что наша система – это некоторый космический корабль, состоящий из укрупненных модулей, наших библиотек.
-
Если мы хотим связать между собой модули различных библиотек, мы должны использовать программный интерфейс.
-
А если у нас одна библиотека состоит из трех логических блоков-подсистем, эти логические блоки будут общаться между собой через служебный программный интерфейс.

В общем понимании это выглядит примерно так:
-
Программный интерфейс – это public, он доступен всем.
-
Служебный программный интерфейс – это internal, то, что доступно только в рамках одной библиотеки.
-
А служебные процедуры и функции – это private, то, что доступно только в рамках функциональной подсистемы.

Таким образом, экспортные методы в модулях могут иметь различное назначение.
-
Программный интерфейс – это именно те методы, которые предназначены для внешних вызовов.
-
Внутри программного интерфейса могут выделяться дополнительные области видимости – например, вы можете встретить в коде группировку «Для вызова из других подсистем». Тут даже из названия понятно, что такие методы, как правило, не предназначены для прямого использования – они предназначены только для специфических интеграционных вызовов. Фактически это реализация чужого интерфейса внутри этого модуля.
-
Также существует переопределяемый интерфейс – механизм, позволяющий задавать и изменять поведение определенных событий внутри библиотеки. К нему мы еще вернемся позже.
-
Служебный программный интерфейс менее стабилен по сравнению с основным, поскольку он не рассчитан на использование вне библиотеки и служит только для внутреннего взаимодействия ее компонентов. Поэтому его стоит использовать с большой осторожностью.
-
Служебные процедуры и функции – вообще не предназначены для вызова извне, их следует использовать только в том контексте, для которого они были разработаны.
Почему так?
Основная причина разбивки методов по областям видимости – обеспечение совместимости.

Различают два вида совместимости.
-
Прямая совместимость – когда для текущей версии компонента (или библиотеки) гарантируется корректная работа с текущими или будущими версиями других компонентов.
-
Обратная совместимость – когда текущая версия должна быть также совместима с более ранними версиями соседних компонентов.

Проще всего разобраться в отличиях видов совместимости на примере оборудования:
-
Например, штекер USB 3.0 можно подключить к разъему USB 2.0: скорость снизится, но устройство продолжит работать – это обратная совместимость.
-
Разъем USB 3.0 для него будет родным, потому что там обеспечивается прямая совместимость.
-
При этом, когда появится USB 3.1, штекер USB 3.0 тоже продолжит с ним работать, потому что он изначально проектируется так, чтобы работать и с более новыми версиями – это тоже прямая совместимость.
-
А вот с более новым стандартом, условно USB 8.1 – наш штекер может оказаться полностью несовместим.

То же самое относится и к нашим расширениям или доработкам в коде.
-
Мы можем сделать доработку для УТ 11.5 так, чтобы она могла использоваться и для более ранних версий – например, была обратно совместима с УТ 11.2.
-
При этом она сможет работать и в новых версиях, например, для УТ 11.5.22 – по прямой совместимости.
-
А вот в следующей редакции – условно, в релизе УТ 12.1 – наша доработка может стать несовместима.

Для объяснения того, как формируется номер версии, и как от него зависит итоговая совместимость, вводится стандарт 483. Согласно нему, полная версия продукта – например, 1.0.4.3 – состоит из четырех цифр.
-
Первая цифра обозначает редакцию.
-
Вторая – подредакцию.
-
Третья – версию.
-
Четвертая – сборку.

Самое первое число версии – это редакция. Его увеличение показывает, что изменения в системе настолько существенные, что по сути речь идет о новом продукте. Например, переход с УТ 10.3 на УТ 11 сопровождался кардинальной сменой архитектуры, хотя название при этом осталось прежним. В других продуктах обычно меняют название в таких случаях, но в 1С изменение редакции – это, фактически смена поколения продукта.
Второе, третье и четвертое число образуют версию продукта, отражая менее радикальные изменения – исправления ошибок или добавление новой функциональности.

Тему влияния номера версии на совместимость продолжает стандарт 644. Согласно нему:
-
В пределах одной подредакции (второго числа) должна сохраняться обратная совместимость. Например, версии 2.0.1, 2.0.2, 2.0.5 должны быть обратно совместимы.
-
Однако при переходе с 2.0 на 2.1 совместимость уже не гарантируется – она может как сохраниться, так и быть нарушена. И это нужно заранее учитывать.
Полный номер версии указывает на совместимость:
-
Редакция – существенное нарушение совместимости.
-
Подредакция показывает, что совместимость может быть нарушена.
-
Номер версии поднимается тогда, когда добавлена новая функциональность – в этом случае обратная совместимость должна сохраняться.
-
А последняя, четвертая цифра – это номер сборки. Она формируется только для исправления ошибок.

Обратная совместимость обеспечивается за счет того, что:
-
Программный интерфейс остается неизменным, скрывая от потребителей детали реализации, чтобы люди вызывали такие функции, которые останутся стабильными при повышении версии.
-
Требования обеспечения обратной совместимости преобладают над другими стандартами – такими как «Имя, синоним, комментарий» и «Названия процедур и функций». Если вам не нравится название процедуры, его нельзя менять, потому что в первую очередь нужно соблюсти правила обратной совместимости.

Для экспортных методов из различных областей должна обеспечиваться следующая совместимость:
-
В области «Программный интерфейс» – обратная совместимость гарантируется.
-
«Для вызова из других подсистем» – тоже гарантируется.
-
«Переопределяемый интерфейс» – тоже гарантируется.
-
«Служебный программный интерфейс» – обратная совместимость не гарантируется, поддерживается только прямая совместимость. Задача кода в этой области – обеспечивать работу компонентов только внутри одной версии библиотеки.
-
«Служебные процедуры и функции» – здесь методы могут свободно изменяться, удаляться или переименовываться. Их совместимость не гарантируется и может быть нарушена в любой момент.

Еще несколько следствий из стандарта 644:
-
Добавление новых функций или необязательных параметров допустимо при изменении номера версии (3 число), но недопустимо при изменении номера сборки (4 число).
-
При этом кардинальные изменения поведения на уровне версии также недопустимы: если функция, например, вычисляла сумму, она не должна внезапно начать считать разность – ее логика должна оставаться такой же, как и ранее.
Примеры изменений программного интерфейса
Давайте разберем реальные примеры – как изменения в коде программного интерфейса должны влиять на версию продукта. В качестве индикатора изменений будем использовать «светофор» с разрядами номера версии.

Если мы хотим добавить в программный интерфейс новый метод (процедуру или функцию) – в сборке это делать нельзя, нужно как минимум поднять номер версии (третью цифру).

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

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

А переименование параметра, наоборот, ни на что не влияет, потому что в 1С названия параметров используются только внутри реализации функций. У нас нет именованных параметров – таких, как в Python. В Python переименование параметров может нарушить совместимость, а в 1С – никак на вызывающий код не влияет.

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

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

Чтобы проверить, нарушит ли изменение программного интерфейса обратную совместимость, достаточно посмотреть на сигнатуру метода: его имя, параметры и их типы. Если потребителям, которые вызывают этот метод, придется переписывать свой код – обратная совместимость нарушена. Если не придется – сохранена.

Тот же стандарт 644 дополнительно выделяет модули с суффиксом «Служебный», где должны содержаться только служебные процедуры и функции. Такие модули не предназначены для вызова потребителя и программный интерфейс там отсутствует.
В целом, служебные процедуры и функции могут встречаться и внутри обычных модулей. Но правильнее их выносить отдельно в модули с суффиксом Служебный – тогда разработчикам при автокомплите не будут показываться методы, которые заведомо использовать в коде нельзя.

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

Например, если мы хотим удалить метод, сначала его нужно перенести в раздел устаревших. Тогда человек, у которого этот метод используется в коде, получит предупреждение от инструментов статанализа (АПК или BSL LS), что вызывается устаревшая процедура или функция. И сможет заранее адаптировать свой код под последние изменения.

Если же требуется переименовать метод программного интерфейса, его старый вариант с названием и сигнатурой также нужно сохранить.
А новый метод мы просто добавляем рядом, рекомендуя запланировать на него переход заранее.

Что касается переопределяемых общих модулей – это отдельный тип модулей, который описан в стандарте 553.
Переопределяемые модули предназначены для настройки библиотеки под конкретную конфигурацию-потребителя, позволяя обрабатывать определенные события, которые возникают во время работы библиотеки. Библиотека вызывает переопределяемый модуль, а внешние разработчики могут встраивать в него свою логику.

Для переопределяемых модулей действуют особые требования по совместимости.
-
При их обновлении нельзя добавлять новые обязательные процедуры или параметры – иначе библиотека попытается их вызвать, а реализации в модуле не окажется.
-
Нельзя изменять тип параметров.
-
И удалять параметры нельзя.

Можно только:
-
Добавлять новые необязательные процедуры, потому что если они необязательные, то и реализацию для них не придется переписывать.
-
Добавлять новые необязательные параметры.
-
И самое главное – если мы хотим какой-то параметр удалить, в переопределяемом модуле его нужно пометить неиспользуемым.

Например, мы можем назвать неиспользуемый параметр «Устарела».
Особенности нумерации версий в 1С

В части нумерации версий у 1С есть единственное исключение – это конфигурация «Бухгалтерия предприятия». Они единственные из всей линейки не следуют единым правилам версионирования. Поэтому для «Бухгалтерии» приходится следить за нумерацией конкретных библиотек, из которых она состоит, а для всех остальных прикладных решений 1С можно ориентироваться на общее изменение номера версии.

При этом сама система версионирования в 1С не является чем-то уникальным. Она во многом соответствует общепринятому подходу – семантическому версионированию (semver).
Разница в том, что в 1С используется четыре числа, а в классическом semver – три. Если отбросить первую цифру (которая обозначает смену поколения продукта), оставшиеся три вполне укладываются в стандартную модель.
Фиксация API и подходы к проектированию

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

Что касается проектирования API, то здесь есть два принципиальных подхода: Code-first и API-first.
API-first преобладает – если мы сначала проектируем API, описать для него реализацию намного проще.
Режим совместимости платформы
С режимом совместимости платформы в целом работают те же правила. Режим совместимости – это признак, который говорит, что:
-
Старые методы, которые были в платформе, продолжат работать по-старому – их поведение не изменится.
-
Новые методы можно использовать независимо от режима совместимости – даже если режим совместимости старый, а платформа новая, они будут доступны.
-
А вот если вам нужно внести изменения в код, чтобы соответствовать новому поведению старых методов, тогда уже режим совместимости поднимается.
API – это продукт
В итоге API – это продукт. Выпуская новую версию, мы должны провести определенные процессы согласования: написать документацию, передать информацию об изменениях потребителям, обеспечить версионирование. Это – базовые характеристики продукта.

Если хотите глубже разобраться в проектировании API, обратите внимание на книгу The API от Сергея Константинова – специалиста, который проектировал API Яндекс.Карт.
Книга доступна на GitHub, ее можно прочитать бесплатно.

А чтобы на практике изучить, как устроено чужое API, я советую вам писать юнит-тесты, которые моделируют сценарии взаимодействия с системой для вашего варианта использования.
Это помогает не только понять, как работает API, но и получить готовые проверки, которые пригодятся при обновлениях – чтобы быстро понять, нужно ли что-то менять в своем коде.
Вопросы и ответы
Если мы захотим проанализировать свою кодовую базу, чтобы в ней вызывалось то, что вызывать нельзя – как это можно сделать?
Можно использовать инструменты статического анализа – они такие ошибки выявляют.
Например, АПК умеет проверять, в какие функциональные подсистемы и библиотеки входит тот или иной модуль:
-
Если метод «Служебного интерфейса» вызывается в рамках одной библиотеки, это не вызовет предупреждения. Если из другой библиотеки – будет зафиксирована проблема.
-
То же самое относится к методам из области «Служебные процедуры и функции» – если кто-то дергает их извне той функциональной подсистемы, где они находятся, АПК тоже зафиксирует это как проблему.
-
Или если кто-то дергает устаревший метод – АПК тоже об этом предупредит.
А еще предупреждение об использовании устаревших методов есть в BSL LS.
Вы говорили, что большие конфигурации 1С представляют собой модульные монолиты. Но они же все-таки не совсем модульные, потому что многие перечисленные принципы в них не соблюдаются. Например, в ERP для оплаты и взаиморасчетов используются отдельные подсистемы. В модульном монолите они должны взаимодействовать друг с другом только на уровне API – через обращения к методам «Программного интерфейса». Если же подсистема оплаты начинает напрямую обращаться к данным подсистемы взаиморасчетов через запросы – это уже нарушение модульности. Между ними не должно быть ни join’ов, ни select’ов. Допускаются только ссылки на данные, но не прямой доступ к таблицам или объектам другой подсистемы. Ситуация усложняется тем, что на практике разработчики часто используют сложные пакетные запросы, объединяя данные из разных подсистем в одном месте. Из-за этого изменения в одном блоке тянут за собой доработки в других, и вместо локальных изменений возникает каскад разнородных правок.
В стандартах есть оговорка: в отдельных случаях названия таблиц и их реквизиты также могут считаться частью программного интерфейса, если это отдельно оговорено в документации. В вашем примере как раз используется такое допущение – оно позволяет выполнять запросы напрямую. Это своего рода компромиссное решение, которое не всем кажется удачным.
Нам как разработчикам регулярно приходится выбирать: куда отнести экспортную процедуру или функцию – в программный интерфейс или в служебный программный интерфейс. Сейчас вы сказали, что в служебный программный интерфейс нужно относить методы, которые должны вызываться только в рамках библиотеки. Но ранее при изучении этого вопроса у меня сложилось впечатление, что служебный программный интерфейс предназначен для вызовов только внутри функциональной подсистемы. Дайте, пожалуйста, более развернутый ответ – когда использовать служебный программный интерфейс, а когда просто служебные процедуры и функции.
Служебные процедуры и функции следует использовать строго внутри одной функциональной подсистемы. Даже если они экспортные, они должны вызываться только из объектов этой же подсистемы.
А служебный программный интерфейс служит для того, чтобы связывать несколько функциональных подсистем внутри одной библиотеки. При этом укрупненно конфигурацию тоже можно считать библиотекой. Например, «Бухгалтерия предприятия» может быть библиотекой для более вышестоящей конфигурации – «Бухгалтерии молокозавода» или «Управления холдингом». Поэтому служебный программный интерфейс может частично вызываться из соседних подсистем, это допустимо.
Однако если речь идет о механизмах гарантированного взаимодействия между разными библиотеками – для этого должен применяться программный интерфейс.
На практике границы можно ужесточать – например, использовать служебный интерфейс только внутри одной подсистемы. Это усложняет архитектуру, но не является ошибкой.
Важно понимать различия по стабильности:
-
Программный интерфейс предполагает обязательную обратную совместимость
-
Служебный программный интерфейс такой гарантии не дает, хотя обычно он стабильнее, чем просто служебные процедуры и функции
-
Служебные процедуры и функции могут меняться свободно
В итоге служебный программный интерфейс использовать извне можно, но нужно быть готовым к тому, что его контракт может измениться в любой момент. Ответственность за это лежит на разработчике, который его использует.
Понятно, что большинство из нас уже сталкивались с API при работе с внешними сервисами. Но как начать разработку собственного API, чтобы другие сервисы могли интегрироваться с нами? Например, если появляется задача интегрироваться с ГИС ЖКХ – чтобы он что-нибудь у нас узнавал – с чего вообще начать и как все это последовательно разработать?
Прежде чем разрабатывать API с нуля, нужно убедиться, что он действительно необходим. Потому что обычно задачу можно решить проще – вручную или с помощью уже готового решения. API имеет смысл разрабатывать только для интеграции между конкретными системами, или когда других альтернатив не существует.
Далее нужно определить, какие задачи должно решать это API. Для каждой задачи формируются сценарии использования (use cases), где описывается, какие данные должны передаваться и как должен выглядеть поток данных.
После этого нужно выбрать технологию взаимодействия: это может быть HTTPS, REST, gRPC, веб-сервисы и т.д. – и в зависимости от этого уже строить спецификацию API.
Также нужно решить, каким образом будет организовано взаимодействие:
-
stateless-подход, когда в запросе передается вся необходимая клиенту информация;
-
stateful-подход, когда часть состояния хранится на сервере, и клиент может использовать цепочку вызовов.
В каждом конкретном случае архитектурные решения принимаются индивидуально – исходя из конкретных задач, потоков данных и требований к взаимодействию между системами.
Существует мнение, что поскольку служебный интерфейс доступен для вызова извне, в 1С от него вообще лучше отказаться, и сразу все выносить в программный интерфейс. Согласны ли вы с этим?
Я, наоборот, по умолчанию все методы оставляю в служебном интерфейсе, а в программный выношу только то, за что потом готов отвечать.
Потому что при добавлении метода в программный интерфейс, мне нужно будет соблюдать для него обратную совместимость. А соблюдение обратной совместимости – это дополнительные накладные расходы: нужно описывать спецификацию, контролировать изменения, проверять совместимость на каждом релизе. Чем больше таких методов, тем больше объем поддержки.
Как только вы объявляете метод частью программного интерфейса, пользователи начинают ожидать от него стабильного поведения. Чтобы этого избежать, многие разработчики откладывают момент взятия этих обязательств и не повышают версию продукта, оставляя ее в формате 0.х – пока версия 1.0 не выпущена, можно нарушать обратную совместимость для каждой версии.
Пока система активно развивается, никто не хочет «бетонировать» интерфейсы. Но как только появляются реальные потребители, которые просят зафиксировать поведение – эти методы переводятся в программный интерфейс. Обычно это происходит так.
*************
Статья написана по итогам доклада (видео), прочитанного на конференции INFOSTART TECH EVENT.
Вступайте в нашу телеграмм-группу Инфостарт