Swagger Aggregator: Unified API Documentation for Microservices Architecture

Learn how to build a centralized Swagger aggregation service that consolidates API documentation from multiple microservices. Create a unified API catalog with Spring Boot and Netflix Zuul gateway integration.

GT
Gonnect Team
January 14, 20249 min readView on GitHub
Spring BootSwaggerNetflix ZuulOpenAPIJava

Introduction

In a microservices architecture, each service typically exposes its own API documentation. As the number of services grows, developers and consumers face the challenge of navigating multiple Swagger UI endpoints to understand the complete API landscape.

What if there was a single entry point for all your API documentation? What if developers could browse, test, and discover APIs from all microservices in one unified interface?

The Swagger Aggregator solves this problem by consolidating Swagger definitions from multiple microservices into a single, unified catalog. Combined with Spring Boot and Netflix Zuul, it provides both documentation aggregation and API gateway capabilities.

Key Insight: A centralized Swagger aggregator transforms scattered API documentation into a cohesive developer experience, comparable to enterprise platforms like Swagger Hub but tailored to your specific microservices ecosystem.

Microservices Architecture

Loading diagram...

The Problem: Documentation Sprawl

In a typical microservices environment, you might have:

ServiceSwagger Endpoint
User Servicehttp://user-service:8001/swagger-ui.html
Order Servicehttp://order-service:8002/swagger-ui.html
Product Servicehttp://product-service:8003/swagger-ui.html
Payment Servicehttp://payment-service:8004/swagger-ui.html
Notification Servicehttp://notification-service:8005/swagger-ui.html

Developers must bookmark and navigate to each endpoint, leading to:

  • Friction in API discovery
  • Inconsistent documentation experience
  • Difficulty onboarding new developers
  • No single source of truth for API contracts

The Solution: Unified Aggregation

The Swagger Aggregator provides:

+------------------+     +---------------------+
|   User Service   |---->|                     |
+------------------+     |                     |
                         |                     |
+------------------+     |   Swagger           |     +---------------+
|   Order Service  |---->|   Aggregator        |---->|   Unified     |
+------------------+     |   (Gateway)         |     |   Swagger UI  |
                         |                     |     +---------------+
+------------------+     |                     |
| Product Service  |---->|                     |
+------------------+     +---------------------+

Microservices Architecture

Loading diagram...

Architecture Overview

Components

ComponentPurpose
Spring BootApplication framework
SpringfoxSwagger generation and aggregation
Netflix ZuulAPI Gateway and routing
Spring Cloud ConfigExternalized configuration

How It Works

  1. Service Registration: Services are configured with their Swagger endpoints
  2. Definition Fetching: Aggregator periodically fetches Swagger JSON from each service
  3. Consolidation: All definitions are merged into a selectable dropdown
  4. Gateway Routing: Zuul proxies API calls to the appropriate service

Project Setup

Maven Dependencies

<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Netflix Zuul Gateway -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    </dependency>

    <!-- Springfox Swagger -->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>2.9.2</version>
    </dependency>

    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>2.9.2</version>
    </dependency>

    <!-- Configuration Processor -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
    </dependency>
</dependencies>

Project Structure

swagger-aggregator/
├── src/
│   └── main/
│       ├── java/
│       │   └── com/
│       │       └── gonnect/
│       │           └── swagger/
│       │               ├── SwaggerAggregatorApplication.java
│       │               ├── config/
│       │               │   ├── SwaggerConfig.java
│       │               │   └── ZuulConfig.java
│       │               ├── model/
│       │               │   └── ServiceDefinition.java
│       │               └── resource/
│       │                   └── SwaggerResource.java
│       └── resources/
│           └── application.yml
├── config/
│   └── application.yml
└── pom.xml

Implementation

Service Definition Model

package com.gonnect.swagger.model;

public class ServiceDefinition {

    private String name;
    private String url;
    private String version;

    public ServiceDefinition() {}

    public ServiceDefinition(String name, String url, String version) {
        this.name = name;
        this.url = url;
        this.version = version;
    }

    // Getters and setters
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public String getUrl() { return url; }
    public void setUrl(String url) { this.url = url; }

    public String getVersion() { return version; }
    public void setVersion(String version) { this.version = version; }
}

Configuration Properties

package com.gonnect.swagger.config;

import com.gonnect.swagger.model.ServiceDefinition;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
@ConfigurationProperties(prefix = "documentation.swagger")
public class SwaggerServicesConfig {

    private List<ServiceDefinition> services = new ArrayList<>();

    public List<ServiceDefinition> getServices() {
        return services;
    }

    public void setServices(List<ServiceDefinition> services) {
        this.services = services;
    }
}

Swagger Configuration

package com.gonnect.swagger.config;

import com.gonnect.swagger.model.ServiceDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.List;

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Autowired
    private SwaggerServicesConfig servicesConfig;

    @Primary
    @Bean
    public SwaggerResourcesProvider swaggerResourcesProvider() {
        return () -> {
            List<SwaggerResource> resources = new ArrayList<>();

            for (ServiceDefinition service : servicesConfig.getServices()) {
                SwaggerResource resource = new SwaggerResource();
                resource.setName(service.getName());
                resource.setLocation(service.getUrl());
                resource.setSwaggerVersion(service.getVersion());
                resources.add(resource);
            }

            return resources;
        };
    }
}

Zuul Gateway Configuration

package com.gonnect.swagger.config;

import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableZuulProxy
public class ZuulConfig {
    // Zuul configuration is primarily handled via application.yml
}

Main Application

package com.gonnect.swagger;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
@EnableZuulProxy
@EnableConfigurationProperties
public class SwaggerAggregatorApplication {

    public static void main(String[] args) {
        SpringApplication.run(SwaggerAggregatorApplication.class, args);
    }
}

Configuration

Application Configuration

# application.yml
server:
  port: 8000

spring:
  application:
    name: swagger-aggregator

# Swagger Service Definitions
documentation:
  swagger:
    services:
      - name: Pet Store API
        url: http://petstore.swagger.io/v2/swagger.json
        version: "2.0"

      - name: User Service
        url: http://user-service:8001/v2/api-docs
        version: "2.0"

      - name: Order Service
        url: http://order-service:8002/v2/api-docs
        version: "2.0"

      - name: Product Service
        url: http://product-service:8003/v2/api-docs
        version: "2.0"

# Zuul Gateway Routes
zuul:
  routes:
    user-service:
      path: /users/**
      url: http://user-service:8001
      stripPrefix: true

    order-service:
      path: /orders/**
      url: http://order-service:8002
      stripPrefix: true

    product-service:
      path: /products/**
      url: http://product-service:8003
      stripPrefix: true

  # CORS configuration
  ignored-headers: Access-Control-Allow-Origin

External Configuration

For dynamic configuration without rebuilds, use an external config folder:

# config/application.yml
documentation:
  swagger:
    services:
      - name: Production User Service
        url: http://prod-user-api.internal/v2/api-docs
        version: "2.0"

      - name: Production Order Service
        url: http://prod-order-api.internal/v2/api-docs
        version: "2.0"

Building and Running

Build

# Clone the repository
git clone https://github.com/mgorav/swagger-aggregator.git
cd swagger-aggregator

# Build the application
mvn clean install

# Run with default configuration
java -jar target/swagger-aggregator-0.0.1-SNAPSHOT.jar

# Run with external configuration
java -jar target/swagger-aggregator-0.0.1-SNAPSHOT.jar \
  --spring.config.location=config/

Docker Deployment

# Dockerfile
FROM openjdk:11-jre-slim

COPY target/swagger-aggregator-0.0.1-SNAPSHOT.jar app.jar
COPY config/ config/

EXPOSE 8000

ENTRYPOINT ["java", "-jar", "app.jar", \
  "--spring.config.location=config/"]
# docker-compose.yml
version: '3.8'

services:
  swagger-aggregator:
    build: .
    ports:
      - "8000:8000"
    environment:
      - SPRING_PROFILES_ACTIVE=docker
    volumes:
      - ./config:/config

Accessing the Unified UI

Navigate to http://localhost:8000/swagger-ui.html to access the unified Swagger UI:

+-------------------------------------------+
|  Swagger Aggregator                       |
|                                           |
|  Select a definition: [Pet Store API  v]  |
|                        +----------------+ |
|                        | Pet Store API  | |
|                        | User Service   | |
|                        | Order Service  | |
|                        | Product Service| |
|                        +----------------+ |
|                                           |
|  [API Documentation for selected service] |
|                                           |
+-------------------------------------------+

Advanced Features

Service Discovery Integration

Integrate with Consul or Eureka for dynamic service discovery:

@Component
public class DiscoveryClientSwaggerProvider
        implements SwaggerResourcesProvider {

    @Autowired
    private DiscoveryClient discoveryClient;

    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> resources = new ArrayList<>();

        discoveryClient.getServices().forEach(serviceId -> {
            List<ServiceInstance> instances =
                discoveryClient.getInstances(serviceId);

            if (!instances.isEmpty()) {
                ServiceInstance instance = instances.get(0);
                String swaggerUrl = String.format(
                    "http://%s:%d/v2/api-docs",
                    instance.getHost(),
                    instance.getPort());

                SwaggerResource resource = new SwaggerResource();
                resource.setName(serviceId);
                resource.setLocation(swaggerUrl);
                resource.setSwaggerVersion("2.0");
                resources.add(resource);
            }
        });

        return resources;
    }
}

Database-Driven Configuration

Store service definitions in a database for runtime management:

@Entity
@Table(name = "swagger_services")
public class SwaggerServiceEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String url;
    private String version;
    private boolean enabled;

    // Getters and setters
}

@Repository
public interface SwaggerServiceRepository
        extends JpaRepository<SwaggerServiceEntity, Long> {

    List<SwaggerServiceEntity> findByEnabledTrue();
}

@Service
public class DatabaseSwaggerProvider implements SwaggerResourcesProvider {

    @Autowired
    private SwaggerServiceRepository repository;

    @Override
    public List<SwaggerResource> get() {
        return repository.findByEnabledTrue().stream()
            .map(entity -> {
                SwaggerResource resource = new SwaggerResource();
                resource.setName(entity.getName());
                resource.setLocation(entity.getUrl());
                resource.setSwaggerVersion(entity.getVersion());
                return resource;
            })
            .collect(Collectors.toList());
    }
}

Health Check Integration

Verify service availability before including in aggregation:

@Component
public class HealthAwareSwaggerProvider implements SwaggerResourcesProvider {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private SwaggerServicesConfig servicesConfig;

    @Override
    public List<SwaggerResource> get() {
        return servicesConfig.getServices().stream()
            .filter(this::isServiceHealthy)
            .map(service -> {
                SwaggerResource resource = new SwaggerResource();
                resource.setName(service.getName());
                resource.setLocation(service.getUrl());
                resource.setSwaggerVersion(service.getVersion());
                return resource;
            })
            .collect(Collectors.toList());
    }

    private boolean isServiceHealthy(ServiceDefinition service) {
        try {
            String healthUrl = service.getUrl()
                .replace("/v2/api-docs", "/actuator/health");
            ResponseEntity<String> response =
                restTemplate.getForEntity(healthUrl, String.class);
            return response.getStatusCode().is2xxSuccessful();
        } catch (Exception e) {
            return false;
        }
    }
}

API Gateway Benefits

The integrated Zuul gateway provides:

FeatureBenefit
Unified Entry PointSingle URL for all API calls
Load BalancingDistribute traffic across instances
AuthenticationCentralized security policies
Rate LimitingProtect services from overload
Request/Response ModificationAdd headers, transform payloads

Gateway Filters

@Component
public class ApiKeyFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        ctx.addZuulRequestHeader("X-API-Gateway", "swagger-aggregator");
        return null;
    }
}

Conclusion

The Swagger Aggregator transforms the challenge of distributed API documentation into a streamlined developer experience. By combining:

  • Springfox for Swagger aggregation
  • Netflix Zuul for API gateway capabilities
  • Spring Boot for rapid development
  • Externalized configuration for flexibility

Organizations can provide:

  1. Single entry point for all API documentation
  2. Consistent developer experience across services
  3. Dynamic service discovery integration
  4. API gateway functionality

This solution brings enterprise API management capabilities to your microservices architecture without the complexity or cost of commercial platforms.


References