Контекст.Компонента, реализованная в виде сервера на python/pyramid, инкапсулирует (в контексте всей системы) логику взаимодействия с сторонним сервисом, и предоставляет:
- пользовательский интерфейс для настройки параметров такого взаимодействия, с одной стороны;
- внутренний API для других частей системы, с другой стороны.
api-часть подвергается интенсивным запросам. Эти запросы асинхронны к действиям пользователя (строго говоря, вообще никак не связаны). Таким образом, в своей api-части сервер фактически играет роль гейта к стороннему сервису. Иначе говоря, он транслирует запросы к своему простому api в запросы к более сложному стороннему api, при этом добавляется много другой логики, например логика, связанная с авторизацией. Т.е. работа с сторонним сервисом не тривиальна, здесь есть свои вопросы развития и поддержки стороннего api, поэтому необходимость наличия такого гейта в данной архитектуре сомнений не вызывает.
Проблема.Получается, что интенсивное использование сервера в качестве api-гейта будет нагружать его, что приведет к тормозам на стороне пользовательского интерфейса, или даже полной его недоступности.
Хотелки.Что хочется? Разумеется, разделить эти две роли: пользовательский интерфейс и api-гейт. Тогда мы можем позволить себе высокие нагрузки на основных рабочих задачах (которые связаны с разнообразным взаимодействием с разными сторонними сервисами), но все эти нагрузки никак не будут отражаться на пользовательском интерфейсе. Это все происходит у нас внутри и пользователи это не видят.
Другими словами, хочется
управлять производительностью отдельно для той и другой задачи.
Другая проблема.Но, разделяя эти задачи на два разных программных сервера (единицы развертывания), мы плодим дублирование кода и прочие непотребности. Т.к. нужно иметь возможность запускать единицы развертывания в разных конфигурациях (разработчиская, юнит-тестовая, кьюэйная, на-поиграться менеджерам, рабочая конфигурация и т.д), тут так уж получается, что даже сделав обе задачи на Python/Pyramid, логику взаимодействия с сторонним сервером не сильно и вынесешь в разделяемую библиотеку. Вернее, вынести-то можно, но тогда появляется третья единица развертывания (пайтоновский пакет с библиотечным классом), и кроме этого появляется дополнительный дурацкий код, организующий наследование классов, перегрузку методов и прочую лишнюю ООП-дрянь, которую так не хотелось бы иметь в наших очень компактных по своему исходному коду компонентах-сервисах.
Решение.Обе задачи (UI & API-gate) оставлены в пределах одной единицы развертывания - все тот же сервер на python/pyramid, простой, компактный, легко обозримый. Но в paste-configuration файле (.ini-файл) вводится дополнительный параметр system.api = true/false. Декоратор, который выглядит буквально так:
def api(func):
def wrapper(req):
if not asbool(system_params.api):
raise HTTPNotFound
return func(req)
return wrapperиспользуется для функций - видов, реализующих api (прим.: system_params - это не пирамидовский обьект, а мой, который я давно и привычно использую для более удобной работы с произвольными параметрами в .ini файлах).
На некритичных конфигурациях (development.ini, testing.ini, staging.ini и проч.) этот параметр задан как true. Поэтому в этих конфигурациях UI и api-гейт - это один и тот же сервер.
Рабочая конфигурация разделена на две: produciton_ui.ini и production_api.ini, и api "включен" только в одном из них. Безопасность доступа к api обеспечивается сетевыми средствами. Поэтому к второму снаружи никак не подступишься, а на первом такие методы отдают Not Found.
По-моему, разумное решение, и не выглядит костылем. Оба зайца одним коммитом.
UPDATE.
Мораль: иногда можно написать одно приложение, совмещая в нем два, и переключать его, скажем, конфигурационным файлом, вместо того, чтобы писать два приложения, и выносить их общую логику в отдельную библиотеку. Почему? Да потому что выносить код в библиотеку только для того, чтобы воспользоваться ей всего лишь два раза - может быть не самой удачной идеей.