sfcodes / grpc-client-spring-boot

Spring Boot module for auto-configuring gRPC stubs
Apache License 2.0
19 stars 0 forks source link

image:https://circleci.com/gh/sfcodes/grpc-client-spring-boot/tree/develop.svg?style=shield["CircleCI", link="https://circleci.com/gh/sfcodes/grpc-client-spring-boot/tree/develop"] image:https://codecov.io/gh/sfcodes/grpc-client-spring-boot/branch/develop/graph/badge.svg["Codecov", link="https://codecov.io/gh/sfcodes/grpc-client-spring-boot"] image:https://img.shields.io/maven-central/v/codes.sf/grpc-client-spring-boot.svg?label=Maven%20Central["Maven Central", link="https://search.maven.org/search?q=g:%22codes.sf%22%20AND%20a:%22grpc-client-spring-boot%22"]

= gRPC Client for Spring Boot

Spring Boot library for auto-configuring https://github.com/grpc/grpc-java[gRPC Java stubs].

This library will automatically scan the classpath, find all gRPC stub classes, instantiate them, and register them as beans with the ApplicationContext; allowing for easy @Autowire and injecting them just like you would any other Spring bean. For example:

@RestController
public class GreeterController {

    @Autowired  // <===== gRPC stub is autowired!
    private GreeterGrpc.GreeterBlockingStub greeterStub;

    @RequestMapping(value = "/sayhello")
    public String sayHello(@RequestParam String name) {
        HelloRequest request = HelloRequest.newBuilder().setName(name).build();
        HelloReply reply = greeterStub.sayHello(request);
        return reply.getMessage();
    }
}

If you're completely new to gRPC, start by going through the https://grpc.io/docs/tutorials/basic/java.html[gRPC Basics for Java] tutorial first. Then come back here to learn how to integrate gRPC stubs with Spring.

==== This library requires Spring Framework 5.x and Spring Boot 2.x. If you would like to see this backported to Spring Framework 4.x and Spring Boot 1.x, please upvote https://github.com/sfcodes/grpc-client-spring-boot/issues/1[this issue].

==== Maven

For Maven users, in your pom.xml file, add between <dependencies> ... </dependencies>

<dependency>
    <groupId>codes.sf</groupId>
    <artifactId>grpc-client-spring-boot</artifactId>
    <version>0.0.4</version>
</dependency>

==== Gradle

For Gradle users, add the following in your build.gradle file:

repositories {
    mavenCentral()
}

dependencies {
    compile group: 'codes.sf', name: 'grpc-client-spring-boot', version: '0.0.4'
}

== Usage

By default, without any user configuration, this library will scan for gRPC stubs in the same packages already specified in your @SpringBootApplication and @ComponentScan annotations. Instantiated stubs will use a default plaintext channel with target localhost:6565.

==== Scan Packages

If your stubs are not in the same package as your Spring Boot app, you can configure alternative packages to scan with property:

grpc:
    client:
        scanPackages: io.grpc.examples

Or you can use the more sophisticated annotation form:

@GrpcStubScan(basePackages = "io.grpc.examples")
public class MyGrpcConfiguration {
}

If both are present, the annotation will override the property.

==== Channel

By default, a plaintext localhost:6565 channel is used for all stub instances. To configure a different target, use property:

grpc:
    client:
        target: example.com:8080

For a more sophisticated channel configuration, you can declare your own channel bean:

@Configuration
public class MyGrpcConfiguration {
    @Bean
    public Channel channel() {
        return ManagedChannelBuilder
                .forAddress("grpc.example.com", 443)
                .useTransportSecurity()
                .enableRetry()
                .build();
    }
}

If you require different channels for different stubs, you can declare a GrpcChannelSource bean:

@Configuration
public class MyGrpcConfiguration {
    @Bean
    public GrpcChannelSource channelSource() {
        return stubClass -> {
            String serviceName = stubClass.getCanonicalName().toLowerCase();
            return ManagedChannelBuilder
                    .forAddress(serviceName + ".local", 8080)
                    .usePlaintext()
                    .build();
        };
    }
}

GrpcChannelSource is where your service discovery logic should go, routing stubs to their implementations.

==== Executor

By default gRPC uses it's own Executor instance for asynchronous operations. You can however switch to using the Spring executor with this property:

grpc:
    client:
        springexecutor: true

==== Compression

You may set the compression to use for calls:

grpc:
    client:
        compression: gzip

Note however that the compression set here is used by the stub to compress messages to the server. To get compressed responses from the server, you will need to set the appropriate decompressor registry on the channel.

==== Max Message Sizes

You can set the maximum allowed inbound (from the server) and outbound (to the server) message sizes in bytes, with properties:

grpc:
    client:
        maxInboundMessageSize: 2048
        maxOutboundMessageSize: 1024

===== Client Interceptors

You may register https://grpc.io/grpc-java/javadoc/io/grpc/ClientInterceptor.html[client interceptors] as Spring beans and they will automatically be applied to all stub calls. You may order these interceptors using Spring's order https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/annotation/Order.html[annotation] or https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/Ordered.html[interface].

@Configuration
public class MyGrpcConfiguration {

    @Order(Ordered.HIGHEST_PRECEDENCE)
    @Bean
    public ClientInterceptor clientInterceptorB() {
        return new ClientInterceptor() {
            @Override
            public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
                    MethodDescriptor<ReqT, RespT> method,
                    CallOptions callOptions,
                    Channel next) {

                // For example add credentials to the call
                callOptions = callOptions.withCallCredentials(myCallCredentials);

                return next.newCall(method, callOptions));
            }
        };
    }
}

==== Stub Post Processors

gRPC stub post processors are factory hooks for custom modification of the new gRPC stub instances before they are ready for use. Use post processors if you require further fine tuning not already exposed by the properties documented above.

Stub post processors are registered as Spring beans, can be ordered, and will automatically be detected and applied to the newly created stub instances. Example registration:

@Configuration
public class MyGrpcConfiguration {
    @Bean
    public GrpcStubPostProcessor postProcessor() {
        return stub -> {
            return stub.withOption(myCustomOptionKey, myCustomOption);
        };
    }
}

GrpcStubPostProcessor can generically declare the stub type that it is interested in, in which case the processor will only be invoked on the matching stub instances; for example the following post processor will only apply to GreeterBlockingStub, it will not apply to GreeterStub, GreeterFutureStub, or any other stub:

@Configuration
public class MyGrpcConfiguration {

    @Bean
    public GrpcStubPostProcessor<GreeterBlockingStub> postProcessor() {
        return new GrpcStubPostProcessor<GreeterBlockingStub>() {
            @Override
            public GreeterBlockingStub postProcess(GreeterBlockingStub stub) {
                return stub.withDeadlineAfter(1, TimeUnit.DAYS);
            }
        };
    }
}

Warning, due to Spring issue https://jira.spring.io/browse/SPR-13698[SPR-13698], you can not use lambda's to define generically-declared post processors, they will not work.

If you require more control over deciding which stubs to process, consider using interface GenericGrpcStubPostProcessor instead.

== What about LogNet/grpc-spring-boot-starter ?

If you Googled "spring grpc", you probably found this library and https://github.com/LogNet/grpc-spring-boot-starter[LogNet/grpc-spring-boot-starter], and now wondering why are there two different libraries for integrating gRPC with Spring?

Answer is, the two library actually do two different things:

It might be the case that your app both consumes gRPC stubs, and implements gRPC services, in which case you should use both of these libraries in your app; they're totally compatible with each other.

image::docs/img/server-vs-client.png[Server vs. Client]