Skip to content

Commit

Permalink
Add API Gateway (#1572)
Browse files Browse the repository at this point in the history
* add api-gateway

* move API contact and license to gateway

* remove unnecessary dependencies

* removed @Autowired` and added @requiredargsconstructor

* add README.md
  • Loading branch information
yoomlam authored May 22, 2023
1 parent 6ddd1d5 commit 23d9857
Show file tree
Hide file tree
Showing 24 changed files with 341 additions and 79 deletions.
11 changes: 11 additions & 0 deletions api-gateway/README.md
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.
24 changes: 24 additions & 0 deletions api-gateway/build.gradle
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'
}
1 change: 1 addition & 0 deletions api-gateway/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
spring_boot_version=3.0.6
18 changes: 18 additions & 0 deletions api-gateway/src/main/java/gov/va/vro/GatewayApplication.java
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 ---------");
}
}
14 changes: 14 additions & 0 deletions api-gateway/src/main/java/gov/va/vro/GatewayConfiguration.java
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;
}
}
18 changes: 18 additions & 0 deletions api-gateway/src/main/java/gov/va/vro/HomePageController.java
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";
}
}
10 changes: 10 additions & 0 deletions api-gateway/src/main/java/gov/va/vro/HomePageModel.java
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;
}
47 changes: 47 additions & 0 deletions api-gateway/src/main/java/gov/va/vro/OpenApiConfiguration.java
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()))
.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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package gov.va.vro.config.propmodel;
package gov.va.vro.propmodel;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Info {
private String title = "VRO API";
private String description = "VRO Description";
private String version = "v1.0.25";
private String title = "API";
private String description = "Description";
private String version = "v0.0.0";

private final Contact contact = new Contact();

Expand Down
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;
Expand Down
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()));
}
11 changes: 11 additions & 0 deletions api-gateway/src/main/java/gov/va/vro/propmodel/Server.java
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 = "/";
}
121 changes: 121 additions & 0 deletions api-gateway/src/main/resources/application.yml
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

# 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
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:/}
24 changes: 24 additions & 0 deletions api-gateway/src/main/resources/templates/index.html
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>
10 changes: 0 additions & 10 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,9 @@ dependencies {
implementation project(':svc-bip-api')

implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-amqp'
implementation "com.vladmihalcea:hibernate-types-55:${hibernate_types_version}"
implementation "org.springframework.security:spring-security-core:${spring_security_version}"
implementation "org.springframework.security:spring-security-config:${spring_security_version}"
implementation "org.springframework.security:spring-security-web:${spring_security_version}"
Expand All @@ -39,14 +36,7 @@ dependencies {
implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.14'
implementation 'io.jsonwebtoken:jjwt:0.2'

// Needed?
// implementation "org.postgresql:postgresql:${postgresql_version}"
runtimeOnly "org.postgresql:postgresql:${postgresql_version}"

testRuntimeOnly "com.h2database:h2:${h2_version}"

testImplementation "org.apache.camel:camel-test-spring-junit5:${camel_version}"
testImplementation "io.jsonwebtoken:jjwt:0.2"
}

openApi {
Expand Down
Loading

0 comments on commit 23d9857

Please sign in to comment.