If you are designing an application as microservices architecture, then you must have heard the term API Gateway, aka Backend for Frontend (BFF). It is an important design pattern for building applications based on microservices architecture. A very common architecture pattern is to use gRPC for internal microservices communication and expose public RESTful APIs by API Gateway.
In this article, I will delve deeper into the whys of API Gateway (BFF) design pattern. I will show examples of exposing RESTful APIs through API Gateway with gRPC based APIs. I will also explain how to develop gRPC endpoints as a Spring Boot application.
Code Example
The working code example of this article is listed on GitHub . To run the example, clone repository, and import grpc-spring-boot as a project in your favorite IDE as Gradle project.
To build the project and generate client and server stubs, run the command gradlew clean build
. You can start the gRPC server in IDE by running the main method of ProductApplication. The gRPC server runs on localhost:8001.
You can run API Gateway microservice, product-gateway, as Spring Boot application by running ProductGatewayApplication in IDE.
Why API Gateway?
One important aspect of API design is determining the right size of an API in terms of its features and functionality. A microservice, typically, exposes fine-grained APIs instead of coarse-grained API. The fine-grained APIs introduces some problem for UI workflow.
The problem of fine-grained APIs
Imagine, you are building an e-Commerce application using microservices. You may have microservices for Products, Orders, Offers, Inventory, etc. As the granularity of the APIs provided by the microservices differs from what the frontend application needs, the frontend application has to make many calls to the different services.
For example – the product details UI application may have to call different microservices to show product detail page as:
As the frontend application is running in the browser, these calls can introduce significant latency.
Cross-cutting concerns
Another common problem in a microservices architecture is to solve cross-cutting concerns such as authentication, authorization, rate limiting, etc. Should we offload such concern to a gateway proxy or deploy a shared or specialized service with each microservice (or as a library, which can be consumed by all services)? There is no doubt that this approach increases the tight coupling between the microservices and also increases deployment and maintenance complexity
In the article gRPC for microservices communication, I talked about the concept of public and private APIs. We know that the de facto standard of implementing public API is the REST over HTTP. What if you want to expose public APIs as REST over HTTP but implement internal APIs as gRPC?
All the above concerns can be solved by introducing API Gateway/BFF.
What is API Gateway?
An API Gateway or BFF is a specialized service(s) that acts as an interface between APIs provided by the backed and the consumers of the APIs. The consumer of the API can be Frontend, third-party application consuming public APIs. The API Gateway service should be deployed along with the backend service. This reduces latency due to network round trip from the browser. It also acts as an aggregator of many requests into a single request.
There is a subtle difference between the BFF pattern and the API Gateway pattern. BFF pattern talks about, as the name suggests, a specialized backend for the frontend. For example, you can build separate BFF services for APIs consumed by mobile applications and browser applications.
As BFF is a specialized interface service meant for UI consumption, typically, it should be written and maintained by the Frontend team. If your microservice architecture is polyglot, then think of using the same language for your BFF Service for which your UI team is comfortable. For example, if your application frontend is in Angular then use Node.js as a BFF service.
Want to understand more about BFF? Read this excellent article by SoundCloud engineering.
API management vs API Gateway
Sometimes API Gateway pattern is also confused with commercial API management offerings such as Amazon API Gateway, Apigee, etc. API Gateway is a design pattern that you can implement in your application and offload some of the common concerns such as API management, rate limiting, etc. to these commercial offerings.
Implementing API Gateway
In this article, you will see examples of implementing API Gateway where public API is exposed as REST over HTTP and private API is exposed as gRPC. You will also see how to implement the gRPC application as Spring Boot.
Product Gateway Service
The product-gateway
is an API Gateway microservice that exposes to public RESTful APIs.
- Create a new product:
POST /products
- Get product:
GET /products/{productId}
To expose a POST REST endpoint define @PostMapping("/products")
annotation as :
@PostMapping("/products")
public ResponseEntity<ProductResponse> createProduct(
@RequestHeader(value = "userId") String userId, @RequestBody Product product) {
product.setUserId(userId);
var productId = productService.createNewProduct(product);
var productResponse = new ProductResponse(productId);
return new ResponseEntity<>(productResponse, HttpStatus.CREATED);
}
Ideally, you should pass the authorization information as the authorization header parameter but for simplicity, we are using userID
as a header parameter.
The ProductGatewayController
delegates call to create a new product to the ProductService
. Implementation of ProductService
does the gRPC blocking call to the gRPC Product Service by creating newBlockingStub
as:
public String createNewProduct(Product product) {
var createProductRequest =
CreateProductRequest.newBuilder()
.setPrice(product.getPrice())
.setName(product.getName())
.setDescription(product.getDescription())
.setUserId(product.getUserId())
.build();
var productApiServiceBlockingStub = ProductServiceGrpc.newBlockingStub(managedChannel);
var response = productApiServiceBlockingStub.createProduct(createProductRequest);
var productId = response.getProductId();
return productId;
}
The gRPC connection is created by ManagedChannel
as singleton Spring bean as:
@Bean
public ManagedChannel managedChannel() {
return ManagedChannelBuilder.forAddress(
applicationProperties().getHost(), applicationProperties().getPort())
.usePlaintext()
.build();
}
Channels are expensive to create, and the general recommendation is to use one per application, shared among the service stubs.
gRPC API
The product-service exposes gRPC APIs as:
syntax = "proto3";
package dev.techdozo.product;
import "resources.proto";
option java_package = "dev.techdozo.product.resource";
option java_multiple_files = true;
service ProductService {
rpc CreateProduct(CreateProductRequest) returns (CreateProductResponse);
}
You can import resources.proto
as import "resources.proto";
that includes message definition as :
syntax = "proto3";
package dev.techdozo.product;
message CreateProductRequest {
string name = 1;
string description = 2;
double price = 3;
string userId = 4;
}
message CreateProductResponse {
string productId = 1;
}
The gRPC API for creating a new product is implemented in the Product Service as:
public void createProduct(
CreateProductRequest request, StreamObserver<CreateProductResponse> responseObserver) {
var product = ProductMapper.MAPPER.map(request);
var productId = productRepository.save(product);
var createProductResponse = CreateProductResponse.newBuilder().setProductId(productId).build();
responseObserver.onNext(createProductResponse);
responseObserver.onCompleted();
}
The above code first maps API request CreateProductRequest
object to the domain object Product
using MapStruct and then calls ProductRepository
to persist the Product
. The save method returns productId
which is converted into CreateProductResponse
object and returned by the gRPC API.
gRPC microservice as a Spring Boot application
There are many benefits of making gRPC microservice as Spring Boot application if you are using Spring Boot as a technology stack. For instance, you can use the dependency injection provided by a Spring Boot application.
There are a couple of approaches to make a gRPC microservices as a Spring Boot application. Some of them are:
Both are almost similar in feature, and you can compare and decide which one to use. In this article, we will use yidongnan library.
The very first thing to do is to add grpc-spring-boot-starter dependencies as:
implementation 'net.devh:grpc-server-spring-boot-starter:2.12.0.RELEASE'
and next thing to do is to annotate your gRPC service class with @GRpcService
as:
@GrpcService
public class ProductApiService extends ProductApiServiceGrpc.ProductApiServiceImplBase {
You can start your gRPC server as a Spring Boot application as:
@SpringBootApplication
public class ProductApplication {
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class, args);
}
}
Testing your code
To test you can run both services locally from IDE by running the main method of the class annotated with @SpringBootApplication
. To test use curl (or Postman) as:
curl --location --request POST 'http://localhost:8080/products/' \
--header 'Content-Type: application/json' \
--header 'userId: abc@xyz.com' \
--data-raw '{
"name": "Apple iPhone 12",
"description": "Apple iPhone 12 Pro Max 128 GB, Pacific Blue",
"price": 1700
}'
Putting it all together
The API Gateway, aka BFF, reduces chattiness between clients and services by aggregating multiple requests into a single request. You can build specialized BFF services(s) to handle different interfaces for browser and mobile applications. The API Gateway can also be used to offload cross-cutting concerns such as authentication, authorization, rate limiting to a proxy. API Gateway allows us the flexibility of exposing public APIs as RESTful APIs whereas keeping private APIs as gRPC.
Discussion about this post