From d967ec7c1e01e9a7b7beffefedb8fab77318f050 Mon Sep 17 00:00:00 2001 From: PySTaMbI4 Date: Thu, 22 Oct 2026 14:42:29 +0500 Subject: [PATCH 1/2] feat: advertise the OpenAPI spec path (docs) in the registration Gateway builds /docs from the registry, so every service registered with a spec shows up there without a hardcoded list. The path defaults to nelmio/api-doc-bundle's app.swagger route; microservice_register.docs overrides it. Bump to 1.2.0. --- README.md | 21 ++++++++++++++++++- composer.json | 2 +- config/services.yaml | 1 + src/DependencyInjection/Configuration.php | 4 ++++ .../MicroserviceRegisterExtension.php | 1 + src/Message/MicroserviceRegisterMessage.php | 3 ++- src/Service/RegistrationPublisher.php | 10 +++++++++ 7 files changed, 39 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 84f0419..8e17546 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,24 @@ microservice_register: `port` (по умолчанию `80`), `scheme` (`http`) и `path_prefix` (`''`) тоже настраиваются здесь же, если сервис вдруг не вписывается в общий шаблон. +### 3a. Документация (Swagger) + +Вместе с маршрутами сервис сообщает Gateway, где лежит его OpenAPI-спека — +поле `docs` в сообщении регистрации, путь внутри сервиса. Gateway собирает +`/docs` из всех зарегистрированных сервисов, у которых `docs` есть, — новый +сервис появляется там сам, без правок Gateway. + +По умолчанию путь берётся из маршрута `app.swagger` nelmio/api-doc-bundle +(так его называет Flex-рецепт; у наших сервисов это `/api/doc.json`). Нет +такого маршрута — `docs` не отправляется и сервиса в `/docs` нет. Задать +явно: + +```yaml +# config/packages/microservice_register.yaml +microservice_register: + docs: /api/doc.json +``` + ### 4. Healthcheck и роутинг бандла Один импорт в `config/routes.yaml` приложения: @@ -141,7 +159,8 @@ php bin/console microservice:register `Service\RegistrationPublisher`, используя `source`/`host` из env, `port`/`scheme`/`path_prefix` из `microservice_register.yaml`, а список маршрутов — `Service\ScopedRouteCollector` (роуты с `#[Scope]`) плюс, - опционально, `microservice_register.routes` из того же конфига. + опционально, `microservice_register.routes` из того же конфига, и путь к + OpenAPI-спеке (`docs`, см. 3a). - Сообщение уходит в топик `microservice-register`; Gateway читает его на своей стороне и обновляет реестр (`Gateway\Microservice\Registry`). - **`Controller\HealthCheckController`** — тот самый `{"status":"Ok"}`, diff --git a/composer.json b/composer.json index 97b3316..734ffae 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "type": "symfony-bundle", "description": "Register microservice in gateway", "license": "MIT", - "version": "1.1.1", + "version": "1.2.0", "authors": [ { "name": "PySTaMbI4", diff --git a/config/services.yaml b/config/services.yaml index d5e232d..730f15a 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -18,6 +18,7 @@ services: $scheme: '%microservice_register.scheme%' $pathPrefix: '%microservice_register.path_prefix%' $routes: '%microservice_register.routes%' + $docs: '%microservice_register.docs%' RSofts\Symfony\Bundle\Microservice\Registry\Command\MicroserviceRegisterCommand: ~ diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 4c05d88..34b4dba 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -16,6 +16,10 @@ final class Configuration implements ConfigurationInterface ->integerNode('port')->defaultValue(80)->end() ->scalarNode('scheme')->defaultValue('http')->end() ->scalarNode('path_prefix')->defaultValue('')->end() + ->scalarNode('docs') + ->info('Path of this service\'s OpenAPI spec (e.g. "/api/doc.json"), sent with the registration so Gateway can list it in /docs. Defaults to the path of nelmio/api-doc-bundle\'s "app.swagger" route, if the app has one; no spec is advertised otherwise.') + ->defaultNull() + ->end() ->arrayNode('routes') ->info('Extra routes to register alongside whatever Service\ScopedRouteCollector already finds via #[Attribute\Scope] on a controller action — prefer the attribute for anything with a real #[Route], this is for edge cases that can\'t carry one. "route" is "METHOD /path"; "scopes" is what an access token must carry at least one of to be let through — empty means no auth required.') ->example([['route' => 'POST /api/users', 'scopes' => ['users.create']]]) diff --git a/src/DependencyInjection/MicroserviceRegisterExtension.php b/src/DependencyInjection/MicroserviceRegisterExtension.php index 1cbc18d..070bdcb 100644 --- a/src/DependencyInjection/MicroserviceRegisterExtension.php +++ b/src/DependencyInjection/MicroserviceRegisterExtension.php @@ -17,6 +17,7 @@ class MicroserviceRegisterExtension extends Extension $container->setParameter('microservice_register.scheme', $config['scheme']); $container->setParameter('microservice_register.path_prefix', $config['path_prefix']); $container->setParameter('microservice_register.routes', $config['routes']); + $container->setParameter('microservice_register.docs', $config['docs']); $loader = new YamlFileLoader($container, new FileLocator(dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'config')); $loader->load('services.yaml'); diff --git a/src/Message/MicroserviceRegisterMessage.php b/src/Message/MicroserviceRegisterMessage.php index dfc9028..8e7b68b 100755 --- a/src/Message/MicroserviceRegisterMessage.php +++ b/src/Message/MicroserviceRegisterMessage.php @@ -11,7 +11,8 @@ final readonly class MicroserviceRegisterMessage implements JsonSerializable public function __construct( public string $source, public array $connection, - public array $routes + public array $routes, + public ?string $docs = null, ) { } diff --git a/src/Service/RegistrationPublisher.php b/src/Service/RegistrationPublisher.php index bc4e23b..d03b55f 100644 --- a/src/Service/RegistrationPublisher.php +++ b/src/Service/RegistrationPublisher.php @@ -4,6 +4,7 @@ namespace RSofts\Symfony\Bundle\Microservice\Registry\Service; use RSofts\Symfony\Bundle\Microservice\Registry\Message\MicroserviceRegisterMessage; use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Component\Routing\RouterInterface; /** * The one place that actually builds and sends a registration message — @@ -11,9 +12,15 @@ use Symfony\Component\Messenger\MessageBusInterface; * registration — e.g. from docker-entrypoint.sh at container boot) and * Task\SelfRegisterTick (automatic, every minute, so the entry in Gateway's * registry — TTL-based — doesn't expire between deploys). + * + * Also advertises where the service's OpenAPI spec lives ("docs"), so + * Gateway's /docs lists every registered service without a hardcoded list. */ final readonly class RegistrationPublisher { + /** Route name nelmio/api-doc-bundle's Flex recipe gives the JSON spec. */ + private const string NELMIO_SPEC_ROUTE = 'app.swagger'; + public function __construct( private MessageBusInterface $messageBus, private ScopedRouteCollector $routeCollector, @@ -23,6 +30,8 @@ final readonly class RegistrationPublisher private string $scheme, private string $pathPrefix, private array $routes, + private RouterInterface $router, + private ?string $docs, ) { } @@ -41,6 +50,7 @@ final readonly class RegistrationPublisher // #[Scope] is the preferred way going forward, but nothing here // forces every consuming service to migrate at once. routes: [...$this->routeCollector->collect(), ...$this->routes], + docs: $this->docs ?? $this->router->getRouteCollection()->get(self::NELMIO_SPEC_ROUTE)?->getPath(), )); } } -- GitLab From 087515314fd1ba892645a474b80f988b41f6654b Mon Sep 17 00:00:00 2001 From: PySTaMbI4 Date: Thu, 22 Oct 2026 15:01:52 +0500 Subject: [PATCH 2/2] feat: the spec always lives at /api/docs.json, served by the bundle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bundle declares GET /api/docs.json itself (nelmio's swagger controller, only when nelmio is installed) and always advertises that path as docs — one fixed place instead of a per-service route and a config option. Like the routes, docs is the service's own path; how Gateway exposes it is Gateway's business. --- README.md | 25 ++++++++----------- composer.json | 3 +++ config/docs_routes.php | 18 +++++++++++++ config/routes.yaml | 3 +++ config/services.yaml | 1 - src/DependencyInjection/Configuration.php | 4 --- .../MicroserviceRegisterExtension.php | 1 - src/Service/RegistrationPublisher.php | 13 +++++----- 8 files changed, 41 insertions(+), 27 deletions(-) create mode 100644 config/docs_routes.php diff --git a/README.md b/README.md index 8e17546..5f5fe10 100644 --- a/README.md +++ b/README.md @@ -99,21 +99,16 @@ microservice_register: ### 3a. Документация (Swagger) -Вместе с маршрутами сервис сообщает Gateway, где лежит его OpenAPI-спека — -поле `docs` в сообщении регистрации, путь внутри сервиса. Gateway собирает -`/docs` из всех зарегистрированных сервисов, у которых `docs` есть, — новый -сервис появляется там сам, без правок Gateway. - -По умолчанию путь берётся из маршрута `app.swagger` nelmio/api-doc-bundle -(так его называет Flex-рецепт; у наших сервисов это `/api/doc.json`). Нет -такого маршрута — `docs` не отправляется и сервиса в `/docs` нет. Задать -явно: - -```yaml -# config/packages/microservice_register.yaml -microservice_register: - docs: /api/doc.json -``` +OpenAPI-спека сервиса **всегда** лежит по `/api/docs.json` — маршрут +объявляет сам бандл (`config/docs_routes.php`, подключается тем же импортом +`@MicroserviceRegisterBundle/config/routes.yaml`, см. п. 4), если в +приложении есть nelmio/api-doc-bundle. Свой маршрут на JSON-спеку сервису +заводить не нужно. + +При регистрации бандл отправляет `docs: "/api/docs.json"` — путь внутри +сервиса, как и маршруты. Gateway собирает из этого `/docs`: новый сервис +появляется там сам. Как спека и её пути выглядят снаружи — забота Gateway, +сервис о Gateway ничего не знает. Нет nelmio — нет ни маршрута, ни `docs`. ### 4. Healthcheck и роутинг бандла diff --git a/composer.json b/composer.json index 734ffae..455418f 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,9 @@ "symfony/routing": "^7.2 || ^8.0", "symfony/scheduler": "^7.2 || ^8.0" }, + "suggest": { + "nelmio/api-doc-bundle": "The service's OpenAPI spec is served at /api/docs.json and advertised to Gateway" + }, "autoload": { "psr-4": { "RSofts\\Symfony\\Bundle\\Microservice\\Registry\\": "src" diff --git a/config/docs_routes.php b/config/docs_routes.php new file mode 100644 index 0000000..5355c1e --- /dev/null +++ b/config/docs_routes.php @@ -0,0 +1,18 @@ +add(RegistrationPublisher::DOCS_ROUTE, RegistrationPublisher::DOCS_PATH) + ->controller('nelmio_api_doc.controller.swagger') + ->methods(['GET']); +}; diff --git a/config/routes.yaml b/config/routes.yaml index 6df6090..c95281b 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -1,3 +1,6 @@ microservice_register_health: resource: '../src/Controller/' type: attribute + +microservice_register_docs: + resource: 'docs_routes.php' diff --git a/config/services.yaml b/config/services.yaml index 730f15a..d5e232d 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -18,7 +18,6 @@ services: $scheme: '%microservice_register.scheme%' $pathPrefix: '%microservice_register.path_prefix%' $routes: '%microservice_register.routes%' - $docs: '%microservice_register.docs%' RSofts\Symfony\Bundle\Microservice\Registry\Command\MicroserviceRegisterCommand: ~ diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 34b4dba..4c05d88 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -16,10 +16,6 @@ final class Configuration implements ConfigurationInterface ->integerNode('port')->defaultValue(80)->end() ->scalarNode('scheme')->defaultValue('http')->end() ->scalarNode('path_prefix')->defaultValue('')->end() - ->scalarNode('docs') - ->info('Path of this service\'s OpenAPI spec (e.g. "/api/doc.json"), sent with the registration so Gateway can list it in /docs. Defaults to the path of nelmio/api-doc-bundle\'s "app.swagger" route, if the app has one; no spec is advertised otherwise.') - ->defaultNull() - ->end() ->arrayNode('routes') ->info('Extra routes to register alongside whatever Service\ScopedRouteCollector already finds via #[Attribute\Scope] on a controller action — prefer the attribute for anything with a real #[Route], this is for edge cases that can\'t carry one. "route" is "METHOD /path"; "scopes" is what an access token must carry at least one of to be let through — empty means no auth required.') ->example([['route' => 'POST /api/users', 'scopes' => ['users.create']]]) diff --git a/src/DependencyInjection/MicroserviceRegisterExtension.php b/src/DependencyInjection/MicroserviceRegisterExtension.php index 070bdcb..1cbc18d 100644 --- a/src/DependencyInjection/MicroserviceRegisterExtension.php +++ b/src/DependencyInjection/MicroserviceRegisterExtension.php @@ -17,7 +17,6 @@ class MicroserviceRegisterExtension extends Extension $container->setParameter('microservice_register.scheme', $config['scheme']); $container->setParameter('microservice_register.path_prefix', $config['path_prefix']); $container->setParameter('microservice_register.routes', $config['routes']); - $container->setParameter('microservice_register.docs', $config['docs']); $loader = new YamlFileLoader($container, new FileLocator(dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'config')); $loader->load('services.yaml'); diff --git a/src/Service/RegistrationPublisher.php b/src/Service/RegistrationPublisher.php index d03b55f..1497ad9 100644 --- a/src/Service/RegistrationPublisher.php +++ b/src/Service/RegistrationPublisher.php @@ -13,13 +13,15 @@ use Symfony\Component\Routing\RouterInterface; * Task\SelfRegisterTick (automatic, every minute, so the entry in Gateway's * registry — TTL-based — doesn't expire between deploys). * - * Also advertises where the service's OpenAPI spec lives ("docs"), so - * Gateway's /docs lists every registered service without a hardcoded list. + * Also advertises the service's OpenAPI spec ("docs"): always DOCS_PATH, + * served by this bundle's own route (config/docs_routes.php) whenever the + * app has nelmio/api-doc-bundle. The path is the service's own — like its + * routes, it says nothing about how Gateway exposes it. */ final readonly class RegistrationPublisher { - /** Route name nelmio/api-doc-bundle's Flex recipe gives the JSON spec. */ - private const string NELMIO_SPEC_ROUTE = 'app.swagger'; + public const string DOCS_PATH = '/api/docs.json'; + public const string DOCS_ROUTE = 'microservice_register.docs'; public function __construct( private MessageBusInterface $messageBus, @@ -31,7 +33,6 @@ final readonly class RegistrationPublisher private string $pathPrefix, private array $routes, private RouterInterface $router, - private ?string $docs, ) { } @@ -50,7 +51,7 @@ final readonly class RegistrationPublisher // #[Scope] is the preferred way going forward, but nothing here // forces every consuming service to migrate at once. routes: [...$this->routeCollector->collect(), ...$this->routes], - docs: $this->docs ?? $this->router->getRouteCollection()->get(self::NELMIO_SPEC_ROUTE)?->getPath(), + docs: $this->router->getRouteCollection()->get(self::DOCS_ROUTE) !== null ? self::DOCS_PATH : null, )); } } -- GitLab