-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add API Gateway #1572
Add API Gateway #1572
Changes from all commits
d805e1b
a9157fa
028210d
4daba4d
a6e0c86
7858c88
a7e5073
3b7e08e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# API Gateway for VRO | ||
|
||
To support serving up APIs implemented in several languages (e.g., Java by the RRD Team and Python used by the CC Team), | ||
this API Gateway acts as a proxy to forward requests to the specified tenant API, as determined by the URI prefix. | ||
|
||
The URI prefixes are configured in `application.yml` -- under `spring.cloud.gateway.routes`. | ||
|
||
The Swagger UI destinations to tenant APIs are configured in `application.yml` -- under `springdoc.swagger-ui.urls`. | ||
|
||
The implementation is based on https://piotrminkowski.com/2020/02/20/microservices-api-documentation-with-springdoc-openapi/, | ||
which uses Spring Boot 3 and Spring Cloud Gateway. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
plugins { | ||
id 'local.std.java.library-spring-conventions' | ||
id 'local.java.container-spring-conventions' | ||
id "com.google.osdetector" version "1.7.3" | ||
} | ||
|
||
dependencies { | ||
// Spring Boot | ||
implementation 'org.springframework.boot:spring-boot-starter-webflux' | ||
implementation 'org.springframework.boot:spring-boot-starter-actuator' | ||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' | ||
|
||
// Spring Cloud | ||
implementation 'org.springframework.cloud:spring-cloud-starter-gateway:4.0.5' | ||
if (osdetector.classifier == "osx-aarch_64") { | ||
// Fix MacOS error: Unable to load io.netty.resolver.dns.macos.MacOSDnsServerAddressStreamProvider, | ||
// which may result in incorrect DNS resolutions. | ||
// Spring Cloud Gateway uses Netty | ||
runtimeOnly("io.netty:netty-resolver-dns-native-macos:4.1.92.Final:${osdetector.classifier}") | ||
} | ||
|
||
// Swagger UI for WebFlux | ||
implementation 'org.springdoc:springdoc-openapi-starter-webflux-ui:2.1.0' | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
spring_boot_version=3.0.6 | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package gov.va.vro; | ||
|
||
import io.swagger.v3.oas.annotations.OpenAPIDefinition; | ||
import io.swagger.v3.oas.annotations.info.Info; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.boot.SpringApplication; | ||
import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan; | ||
|
||
@Slf4j | ||
@SpringBootApplication | ||
@ConfigurationPropertiesScan(basePackages = {"gov.va.vro"}) | ||
public class GatewayApplication { | ||
public static void main(String[] args) { | ||
new SpringApplication(GatewayApplication.class).run(args); | ||
log.info("\n-------- API Gateway Application Started ---------"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package gov.va.vro; | ||
|
||
|
||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
|
||
@Configuration | ||
public class GatewayConfiguration { | ||
@Bean | ||
HomePageModel homePageModel(){ | ||
HomePageModel homePageModel = new HomePageModel(); | ||
return homePageModel; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package gov.va.vro; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Controller; | ||
import org.springframework.ui.Model; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
|
||
@Controller | ||
@RequiredArgsConstructor | ||
class HomePageController { | ||
final HomePageModel homePageModel; | ||
|
||
@GetMapping("/") | ||
String index(final Model model) { | ||
model.addAttribute("model", homePageModel); | ||
return "index"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package gov.va.vro; | ||
|
||
import lombok.Data; | ||
import org.springframework.beans.factory.annotation.Value; | ||
|
||
@Data | ||
class HomePageModel { | ||
@Value("${vro.openapi.info.version}") | ||
String version; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package gov.va.vro; | ||
|
||
import gov.va.vro.propmodel.Info; | ||
import gov.va.vro.propmodel.OpenApiProperties; | ||
import io.swagger.v3.oas.annotations.OpenAPIDefinition; | ||
import io.swagger.v3.oas.models.OpenAPI; | ||
import io.swagger.v3.oas.models.info.Contact; | ||
import io.swagger.v3.oas.models.info.License; | ||
import io.swagger.v3.oas.models.servers.Server; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
|
||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
|
||
@Slf4j | ||
@Configuration | ||
@RequiredArgsConstructor | ||
public class OpenApiConfiguration { | ||
private final OpenApiProperties openApiProperties; | ||
|
||
@Bean | ||
public OpenAPI customOpenApi() { | ||
Info info = openApiProperties.getInfo(); | ||
gov.va.vro.propmodel.Contact contact = info.getContact(); | ||
gov.va.vro.propmodel.License license = info.getLicense(); | ||
|
||
List<Server> servers = | ||
openApiProperties.getServers().stream() | ||
.map(server -> new Server().description(server.getDescription()).url(server.getUrl())) | ||
.collect(Collectors.toList()); | ||
|
||
OpenAPI config = new OpenAPI() | ||
.info( | ||
new io.swagger.v3.oas.models.info.Info() | ||
.title(info.getTitle()) | ||
.description(info.getDescription()) | ||
.version(info.getVersion()) | ||
.license(new License().name(license.getName()).url(license.getUrl())) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason to not just pass existing contact and license objects? It would make the code simpler. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They're different object types:
|
||
.contact(new Contact().name(contact.getName()).email(contact.getEmail()))) | ||
.servers(servers); | ||
return config; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
package gov.va.vro.config.propmodel; | ||
package gov.va.vro.propmodel; | ||
|
||
import lombok.Getter; | ||
import lombok.Setter; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
package gov.va.vro.config.propmodel; | ||
package gov.va.vro.propmodel; | ||
|
||
import lombok.Getter; | ||
import lombok.Setter; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package gov.va.vro.propmodel; | ||
|
||
import gov.va.vro.propmodel.Info; | ||
import gov.va.vro.propmodel.Server; | ||
import lombok.Getter; | ||
import lombok.Setter; | ||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
|
||
@Getter | ||
@Setter | ||
@ConfigurationProperties(prefix = "vro.openapi") | ||
public class OpenApiProperties { | ||
private final Info info = new Info(); | ||
|
||
private List<Server> servers = new ArrayList<Server>(Arrays.asList(new Server())); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package gov.va.vro.propmodel; | ||
|
||
import lombok.Getter; | ||
import lombok.Setter; | ||
|
||
@Getter | ||
@Setter | ||
public class Server { | ||
private String description = "Local Server"; | ||
private String url = "/"; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
# This file contains shared properties across all environments; it is always loaded by Spring | ||
# See https://github.com/department-of-veterans-affairs/abd-vro/wiki/Configuration-settings#vros-use-of-spring-profiles | ||
|
||
management: | ||
endpoints.web: | ||
exposure.include: "*" | ||
endpoint: | ||
health: | ||
show-details: always | ||
probes: | ||
enabled: true | ||
group: | ||
liveness.include: livenessState | ||
readiness.include: readinessState, db | ||
metrics: | ||
enabled: true | ||
distribution: | ||
percentiles.http.server.requests: 0.5, 0.90, 0.95, 0.99, 0.999 | ||
percentiles-histogram.http.server.requests: true | ||
sla.http.server.requests: 10ms, 50ms | ||
slo.http.server.requests: 10ms, 50ms | ||
tags: | ||
group: starter | ||
service: example | ||
region: "${POD_REGION:local}" | ||
stack: "${CLUSTER:dev}" | ||
ns: "${NAMESPACE:example}" | ||
pod: "${POD_ID:docker}" | ||
web.server.request.autotime.enabled: true | ||
server.port: 8061 | ||
|
||
server: | ||
ssl: | ||
enabled: false | ||
port: 8060 | ||
maxHttpHeaderSize: 48000 | ||
session: | ||
timeout: 60 | ||
connection: | ||
timeout: 60000 | ||
servlet: | ||
session: | ||
timeout: 120000 | ||
|
||
spring: | ||
application: | ||
name: "vro-api" | ||
servlet: | ||
multipart: | ||
maxFileSize: 25MB | ||
maxRequestSize: 25MB | ||
enabled: true | ||
session: | ||
timeout: 120000 | ||
resources: | ||
add-mappings: false | ||
http: | ||
encoding: | ||
force: true | ||
|
||
springdoc: | ||
writer-with-default-pretty-printer: true | ||
show-actuator: true | ||
swagger-ui: | ||
# http://localhost:8078/swaggerui will be forwarded to http://localhost:8078/webjars/swagger-ui/index.html | ||
path: /swaggerui | ||
operations-sorter: method | ||
tagsSorter: alpha | ||
# Populate API dropdown on the upper-right of Swagger UI | ||
urls: | ||
- name: 0. Gateway API | ||
# API defined for this API Gateway | ||
url: /v3/api-docs | ||
- name: App API | ||
# API defined for the VRO Java-base App | ||
url: /app/v3/api-docs | ||
- name: Contention Classification API | ||
# Use the route defined under spring.cloud.gateway below | ||
url: /contention-classification/openapi.json | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @lukey-luke If you the location of the |
||
|
||
# TODO: Add Spring Cloud CircuitBreaker: https://spring.io/guides/gs/gateway/ | ||
spring.cloud.gateway: | ||
actuator.verbose.enabled: true | ||
discovery: | ||
locator: | ||
enabled: false | ||
routes: | ||
# Each domain API should have one entry with a URI prefix | ||
- id: vro-app | ||
uri: http://localhost:8080 | ||
predicates: | ||
- Path=/app/** | ||
filters: | ||
- RewritePath=/app/(?<path>.*), /$\{path} | ||
- id: contention-classification-tenant | ||
# TODO: add Swagger servers such that /contention-classification is used as the prefix for requests | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
uri: http://localhost:18000 | ||
predicates: | ||
- Path=/contention-classification/** | ||
filters: | ||
- RewritePath=/contention-classification/(?<path>.*), /$\{path} | ||
|
||
log4j2: | ||
formatMsgNoLookups: true | ||
|
||
vro: | ||
openapi: | ||
info: | ||
title: "Automated Benefits Delivery (ABD): Virtual Regional Office (VRO) API" | ||
description: "To improve benefit delivery to Veterans" | ||
version: "3.0.0" | ||
contact: | ||
name: Premal Shah | ||
email: "[email protected]" | ||
license: | ||
name: CCO 1.0 | ||
url: "https://github.com/department-of-veterans-affairs/abd-vro/blob/master/LICENSE.md" | ||
servers: | ||
- | ||
description: "Server" | ||
url: ${STARTER_OPENAPI_SERVERURL:/} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<html> | ||
<body style="font-family: Arial"> | ||
<h1>VRO API Gateway</h1> | ||
Automated Benefits Delivery - Virtual Regional Office (ABD-VRO) | ||
<p><a href="https://github.com/department-of-veterans-affairs/abd-vro">ABD-VRO GitHub Repo</a> | ||
</p> | ||
|
||
<h3>Swagger UI</h3> | ||
APIs for each VRO tenant is available via VRO's <a href="webjars/swagger-ui/index.html">Swagger UI</a>. | ||
<ul> | ||
<li>Select the tenant from the "Select a definition" dropdown on the upper-right of the UI.</li> | ||
<li>Each tenant page has a link to the API spec.</li> | ||
<li>To use Swagger's "Try It" feature, make sure to select the correct server on the left dropdown. | ||
For example, for the VRO App, select the "/app - via API Gateway" server. | ||
</li> | ||
</ul> | ||
|
||
<h3>VRO info</h3> | ||
<ul> | ||
<li>VRO version: <span th:text="${model.version}" /></li> | ||
</ul> | ||
|
||
</body> | ||
</html> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use Spring Boot 3. The rest of VRO is using Spring Boot 2.