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.
Table of Contents
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
The Problem: Documentation Sprawl
In a typical microservices environment, you might have:
| Service | Swagger Endpoint |
|---|---|
| User Service | http://user-service:8001/swagger-ui.html |
| Order Service | http://order-service:8002/swagger-ui.html |
| Product Service | http://product-service:8003/swagger-ui.html |
| Payment Service | http://payment-service:8004/swagger-ui.html |
| Notification Service | http://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
Architecture Overview
Components
| Component | Purpose |
|---|---|
| Spring Boot | Application framework |
| Springfox | Swagger generation and aggregation |
| Netflix Zuul | API Gateway and routing |
| Spring Cloud Config | Externalized configuration |
How It Works
- Service Registration: Services are configured with their Swagger endpoints
- Definition Fetching: Aggregator periodically fetches Swagger JSON from each service
- Consolidation: All definitions are merged into a selectable dropdown
- 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:
| Feature | Benefit |
|---|---|
| Unified Entry Point | Single URL for all API calls |
| Load Balancing | Distribute traffic across instances |
| Authentication | Centralized security policies |
| Rate Limiting | Protect services from overload |
| Request/Response Modification | Add 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:
- Single entry point for all API documentation
- Consistent developer experience across services
- Dynamic service discovery integration
- API gateway functionality
This solution brings enterprise API management capabilities to your microservices architecture without the complexity or cost of commercial platforms.