gRPC Spring Boot Starter

repository·master·Indexed 25 days ago

https://github.com/grpc-ecosystem/grpc-spring

A library providing automatic configuration for integrating gRPC servers and clients into Spring Boot applications. It includes support for @GrpcService and @GrpcClient annotations, Spring Boot Actuator for gRPC metrics (including gRPC A66 spec), and automatic service registration with discovery implementations such as Consul, Eureka, Nacos, and Zookeeper.

Tokens
26.6K
Snippets
61
Records
105
Agent score
84%

What's inside grpc-spring

  1. Overview of gRPC-Spring-Boot-Starter

    master
    gRPC-Spring-Boot-Starter integrates Google's gRPC framework with Spring Boot. It simplifies the setup of gRPC servers and clients by allowing you to add a single dependency and use annotations on your service classes or client stub fields. The library is designed to complement the standard gRPC experience while providing Spring Boot's ease of configuration and customization.
  2. Understand the gRPC Spring versioning policy

    master

    The major version of grpc-spring-boot-starter indicates compatibility with specific Spring Boot versions:

    • 1.x.x: End of Life (EOL); no longer receiving updates.
    • 2.x.x: Current version; updated alongside Spring Boot or gRPC releases.
    • 3.x.x: Current version; updated alongside Spring Boot or gRPC releases.

    Minor versions represent feature updates. A minor version bump typically occurs when Spring Boot or gRPC versions are incremented, or when major features are added/changed. While upgrades are usually compatible, gRPC API evolutions may occasionally introduce incompatibilities.

  3. Configure Mutual Certificate Authentication (mTLS)

    master

    To ensure only trustworthy clients can connect, you can enable mutual certificate authentication. This requires providing a collection of trusted client certificates and setting the clientAuth mode.

    Steps:

    1. Create a certificate collection file by concatenating your client certificates: cat client*.crt > trusted-clients.crt.collection
    2. Configure the server properties with the collection path and the desired clientAuth mode.

    clientAuth modes:

    • REQUIRE: Client certificate authentication is mandatory.
    • OPTIONAL: The server requests a certificate but does not force the client to provide one. This is useful for securing specific services while allowing unauthenticated access to others.
    grpc.server.security.trustCertCollection=file:certificates/trusted-clients.crt.collection
    grpc.server.security.clientAuth=REQUIRE
  4. Test gRPC components using a Mocked Stub

    master

    You can test components by mocking the gRPC stub and injecting it via a setter. This approach is fast and works well with standard mocking frameworks, but it requires extra configuration to handle final classes/methods and does not work for beans using stubs in @PostConstruct or via indirect injection.

    Implementation Steps:

    1. Add mockito to your dependencies.
    2. Create src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker with the content mock-maker-inline to allow mocking final classes/methods.
    3. Mock the stub and use a setter to inject it into your component.
    public class MyComponentTest {
    
        private MyComponent myComponent = new MyComponent();
        private ChatServiceBlockingStub chatService = Mockito.mock(ChatServiceBlockingStub.class);
    
        @BeforeEach
        void setup() {
            myComponent.setChatService(chatService);
        }
    
        @Test
        void testSayHello() {
            Mockito.when(chatService.sayHello(...)).thenAnswer(...);
            assertThat(myComponent.sayHello("ThisIsMyName")).contains("ThisIsMyName");
        }
    
    }
  5. Set up Cloud Discovery for gRPC services

    master

    The gRPC Spring Boot Starter supports automatic service registration with specific discovery implementations. Currently, the supported implementations that provide automatic registration are consul, eureka, nacos, and zookeeper. Note that while these allow automatic registration, clients do not require additional configuration to use them.

    1. Start a Discovery Server

    Choose one of the following methods to start your discovery service:

    Consul:

    docker run --name=consul -p 8500:8500 consul

    Eureka:

    ./gradlew :example:cloud-eureka-server:bootRun

    Nacos:

    docker run --env MODE=standalone --name nacos -d --rm -p 8848:8848 nacos/nacos-server

    2. Run Server and Client with Discovery

    Use the -Pdiscovery Gradle property to specify the implementation (consul, eureka, or nacos) when running the examples.

  6. Configure Interface Project Dependencies (Maven & Gradle)

    master

    The Interface Project requires protobuf and gRPC dependencies to generate Java classes from .proto files.

    Important: For Java 9+ compatibility, use jakarta.annotation-api version 1.3.5 and do not update it to 2.0.0.

    ### Maven (Interface)
    ```xml
    <properties>
        <protobuf.version>3.23.4</protobuf.version>
        <protobuf-plugin.version>0.6.1</protobuf-plugin.version>
        <grpc.version>1.58.0</grpc.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>${grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>${grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>jakarta.annotation</groupId>
            <artifactId>jakarta.annotation-api</artifactId>
            <version>1.3.5</version>
            <optional>true</optional>
        </dependency>
    </dependencies>
    
    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.7.0</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>${protobuf-plugin.version}</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    Gradle (Interface)

    buildscript {
        ext {
            protobufVersion = '3.23.4'
            protobufPluginVersion = '0.8.18'
            grpcVersion = '1.58.0'
        }
    }
    
    plugins {
        id 'java-library'
        id 'com.google.protobuf' version "${protobufPluginVersion}"
    }
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        implementation "io.grpc:grpc-protobuf:${grpcVersion}"
        implementation "io.grpc:grpc-stub:${grpcVersion}"
        compileOnly 'jakarta.annotation:jakarta.annotation-api:1.3.5'
    }
    
    protobuf {
        protoc {
            artifact = "com.google.protobuf:protoc:${protobufVersion}"
        }
        generatedFilesBaseDir = "$projectDir/src/generated"
        clean {
            delete generatedFilesBaseDir
        }
        plugins {
            grpc {
                artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}"
            }
        }
        generateProtoTasks {
            all()*.plugins {
                grpc {}
            }
        }
    }
  7. Configure non-shaded grpc-netty

    master

    By default, if grpc-netty-shaded is on the classpath, this library favors it. To force the use of the non-shaded grpc-netty, you must explicitly include it and exclude grpc-netty-shaded from the grpc-spring-boot-starter (or the specific server/client starter) dependencies.

    Maven Example:

    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-netty</artifactId>
        <version>${grpcVersion}</version>
    </dependency>
    
    <dependency>
        <groupId>net.devh</groupId>
        <artifactId>grpc-spring-boot-starter</artifactId>
        <version>...</version>
        <exclusions>
            <exclusion>
                <groupId>io.grpc</groupId>
                <artifactId>grpc-netty-shaded</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    Gradle Example:

    implementation "io.grpc:grpc-netty:${grpcVersion}"
    
    implementation 'net.devh:grpc-spring-boot-starter:...' exclude group: 'io.grpc', module: 'grpc-netty-shaded'
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-netty</artifactId>
        <version>${grpcVersion}</version>
    </dependency>
    
    <!-- For both -->
    <dependency>
        <groupId>net.devh</groupId>
        <artifactId>grpc-spring-boot-starter</artifactId>
        <version>...</version>
        <exclusions>
            <exclusion>
                <groupId>io.grpc</groupId>
                <artifactId>grpc-netty-shaded</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
  8. Add a ServerInterceptor

    master

    There are three ways to add a ServerInterceptor to your gRPC server:

    1. Global Interceptor: Annotate your interceptor with @GrpcGlobalServerInterceptor or implement a GlobalServerInterceptorConfigurer.
    2. Service-Specific: Explicitly list interceptors in the @GrpcService#interceptors or @GrpcService#interceptorNames fields.
    3. Programmatic: Use a GrpcServerConfigurer to call serverBuilder.intercept(ServerInterceptor interceptor).
  9. Configure Authorization using Spring Annotations

    master

    You can use standard Spring Security annotations (like @Secured) on your gRPC method implementations.

    Requirements:

    1. Add @EnableMethodSecurity(proxyTargetClass = true) to one of your @Configuration classes. proxyTargetClass = true is mandatory; otherwise, you will receive UNIMPLEMENTED responses.
    2. Ensure your service implementation extends the generated ImplBase class.
    3. Annotate the implementation methods directly.
  10. Enable Transport Layer Security (TLS) for gRPC clients

    master

    gRPC uses TLS by default. To ensure TLS is enabled for a specific client, verify that the negotiationType property is set to TLS or is using its default value.

    Prerequisites: You must have a compatible SSL/TLS implementation on your classpath:

    • grpc-netty-shaded (includes implementation)
    • grpc-netty: Requires adding a dependency to netty-tcnative-boringssl-static. Ensure you use the exact compatible versions specified in the grpc-java netty security section.
    grpc.client.<SomeName>.negotiationType=TLS
  11. Create gRPC Service Definitions

    master

    Place your .proto files in src/main/proto. The configured Maven or Gradle plugins will use protoc and protoc-gen-grpc-java to generate:

    • Data classes: For request and response messages.
    • ImplBase classes: Base logic for implementing service methods.
    • Stub classes: Complete client implementations.
    syntax = "proto3";
    
    package net.devh.boot.grpc.example;
    
    option java_multiple_files = true;
    option java_package = "net.devh.boot.grpc.examples.lib";
    option java_outer_classname = "HelloWorldProto";
    
    // The greeting service definition.
    service MyService {
        // Sends a greeting
        rpc SayHello (HelloRequest) returns (HelloReply) {
        }
    }
    
    // The request message containing the user's name.
    message HelloRequest {
        string name = 1;
    }
    
    // The response message containing the greetings
    message HelloReply {
        string message = 1;
    }