How to combine spring-boot-starter-rsocket and rsocket-rpc-protobuf?

Greetings all,

When generating Java source with rsocket-rpc-core, protobuf-java, and io.rsocket.rpc:rsocket-rpc-protobuf..., the generated Java source contains Interfaces for both a Service and a BlockingService, however, it is unclear to me in which cases each should have concrete implementations.

What is the intended use-case for each generated service? Is it expected that a concrete implementation for each should be written?

For example, using the below protobuf IDL, the generated files include:

  • Interface: BlockingUserService
  • Class: BlockingUserServiceClient
  • Class: BlockingUserServiceServer
  • Interface: UserService
  • Class: BlockingUserServiceClient
  • Class: BlockingUserServiceServer
syntax = "proto3";

package com.solidice.springbootrsocketserver.rpc.proto;

option java_outer_classname = "UserServiceProto";
option java_multiple_files = true;

message User {
    string email = 1;
    string username = 2;
    int32 id = 3;
}

message GetUserByIdRequest {
    int32 id = 1;
}

message GetUserByIdResponse {
    User user = 1;
}

service UserService {
    rpc GetUserById (GetUserByIdRequest) returns (GetUserByIdResponse) {}
}

spring-boot-rsocket-starter startup errors

When attempting to combine the above with spring-boot-rsocket-starter, I receive an exception at startup that indicates that the application is attempting to initialize an instance of BlockingUserServiceServer, and that I have not provided a concrete implementation of BlockingUserService. This issue can easily be resolved by registering a bean that provides a concrete implementation of BlockingUserService, however, it is not clear to me in which scenarios one would use the BlockingUserServiceServer versus the UserServiceServer, and how to configure spring-boot-rsocket-starter to prefer one over the other.

I am hoping to better understand the relationship between BlockingUserServer and UserServer, as I want to avoid implementing concrete implementations for each BlockingUserService and UserService, if it is unnecessary.

Startup exception:

2019-09-29 15:01:51,260 WARN  [main] org.springframework.context.support.AbstractApplicationContext: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'BlockingUserServiceServer' defined in file [...spring-boot-rsocket-server\target\classes\com\solidice\springbootrsocketserver\rpc\proto\BlockingUserServiceServer.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.solidice.springbootrsocketserver.rpc.proto.BlockingUserService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
2019-09-29 15:01:51,278 INFO  [main] org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener: 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-09-29 15:01:51,354 ERROR [main] org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter: 

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in com.solidice.springbootrsocketserver.rpc.proto.BlockingUserServiceServer required a bean of type 'com.solidice.springbootrsocketserver.rpc.proto.BlockingUserService' that could not be found.


Action:

Consider defining a bean of type 'com.solidice.springbootrsocketserver.rpc.proto.BlockingUserService' in your configuration.

I believe the above issue is due to the generated Java source classes from the proto IDL being decorated with the @javax.inject.Named annotation, which I believe is why spring is discovering them.

Is this default/expected behavior? Is there a configuration that should be used to prevent the decoration of the generated classes with this annotation to prevent auto-discovery, or am I missing something else potentially?

From reviewing the source of rsocket-rpc-protobuf/src/java_plugin/cpp/java_generator.cpp, it does not appear that this annotation is configurable, which leads me to believe its usage is intended.

@javax.annotation.Generated(
    value = "by RSocket RPC proto compiler (version 0.2.12)",
    comments = "Source: UserService.proto")
@io.rsocket.rpc.annotations.internal.Generated(
    type = io.rsocket.rpc.annotations.internal.ResourceType.SERVICE,
    idlClass = BlockingUserService.class)
@javax.inject.Named(
    value ="BlockingUserServiceServer")
public final class BlockingUserServiceServer extends io.rsocket.rpc.AbstractRSocketService {
    ...
}

pom.xml

Below are the contents of my pom.xml, for reference.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.0.M5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.solidice.springbootrsocketserver</groupId>
    <artifactId>spring-boot-rsocket-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-rsocket-server</name>
    <description>Example spring boot application using rsocket.</description>

    <properties>
        <java.version>11</java.version>
        <protobufVersion>3.6.1</protobufVersion>
        <rsocketRpcVersion>0.2.12</rsocketRpcVersion>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-rsocket</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>io.rsocket.rpc</groupId>
            <artifactId>rsocket-rpc-core</artifactId>
            <version>${rsocketRpcVersion}</version>
            <exclusions>
                <exclusion>
                    <groupId>io.rsocket.rpc</groupId>
                    <artifactId>rsocket-rpc-protobuf</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>${protobufVersion}</version>
        </dependency>

        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.5.0.Final</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>build-helper-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>add-source</goal>
                        </goals>
                        <configuration>
                            <sources>
                                <source>${project.build.directory}/generated-sources/protobuf/java</source>
                                <source>${project.build.directory}/generated-sources/protobuf/rsocketRpc</source>
                            </sources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.5.1</version>
                <configuration>
                    <protocArtifact>
                        com.google.protobuf:protoc:${protobufVersion}:exe:${os.detected.classifier}
                    </protocArtifact>
                    <pluginId>rsocketRpc</pluginId>
                    <pluginArtifact>
                        io.rsocket.rpc:rsocket-rpc-protobuf:${rsocketRpcVersion}:exe:${os.detected.classifier}
                    </pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>jcenter</id>
            <url>https://jcenter.bintray.com/</url>
        </repository>
        <repository>
            <id>netifi-oss</id>
            <url>https://dl.bintray.com/netifi/netifi-oss/</url>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </pluginRepository>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

</project>

Thanks for any insight or direction that you can provide.

After a bit of digging, I was able to land on a configuration that registers my UserServiceServer/BlockingUserServiceServer, however it is still unclear to me when you would want to register the blocking vs non-blocking server implementation.

Config

It appears that whichever server is passed last to RequestHandlingRSocket will be registered in the registeredServices map, so it as far as I can tell, you wouldn’t configure both the blocking and non-blocking servers at the same time, but it would still be great to get some clarification from someone who knows more.

@Configuration
public class RSocketConfig {

    @Bean
    public CloseableChannel rSocket(
        SocketAcceptor socketAcceptor,
        WebsocketServerTransport transport) {
        return RSocketFactory
            .receive()
            .acceptor(socketAcceptor)
            .transport(transport)
            .start()
            .block();
    }

    @Bean
    public SocketAcceptor socketAcceptor(
        UserServiceServer userServiceServer,
        BlockingUserServiceServer blockingUserServiceServer) {
        RequestHandlingRSocket requestHandlingRSocket = new RequestHandlingRSocket(
            userServiceServer,
            blockingUserServiceServer
        );
        return ((setup, sendingSocket) -> Mono.just(requestHandlingRSocket));
    }

    @Bean
    public WebsocketServerTransport websocketServerTransport() {
        return WebsocketServerTransport.create(8081);
    }
}

Thanks

Hi,

As you noticed the generator creates both blocking and non-blocking interfaces. They are present so the developer can chose if they want a blocking or non-blocking API in there code. Generally you’d use the non-blocking ones, but perhaps there is an older application or a team that isn’t interested in reactive they can use the non-blocking interfaces.

The choice between interfaces is purely an API decision- a non-blocking client can call a blocking service implementation etc - everything is just frames under the hood.

Regarding the Spring issue- I have seen the spring issue before - and as a work around I’ve deleted the blocking interfaces. @OlehDokuka - maybe you can add some more color around this issue?

Thanks,
Robert

Thanks for your reply @robertroeser.

…as a work around I’ve deleted the blocking interfaces…

I considered this workaround for my use-case, however, as I currently have the build setup, the generated sources are produced by Maven during the compile/build stage, and then output to /target/generated-sources/. Because the files are output to target rather then say src/main/java they are not included in my version control, and even if I did delete them from target, they are just regenerated on each build.

I’m looking forward to any insight @OlehDokuka might have on how to set this up better to avoid the auto-discovery of the undesirable blocking implementations.

Also, I’m fairly new to Spring, and more specifically RSocket, so I’m receptive to this purely being due to a lack of my understanding :slight_smile:.

Thanks again.

After a bit of searching, I was able to come to a working solution that allowed me to exclude certain classes from Spring’s component scanning based on a regex.

@SpringBootApplication()
@ComponentScan(excludeFilters = {
    @ComponentScan.Filter(
        type = FilterType.REGEX,
        pattern = "com.solidice.springbootrsocketserver.rpc.proto.Blocking.*"
    )
})
public class SpringBootRSocketServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootRSocketServerApplication.class, args);
    }
}

I’m not sure if there are any downsides to this, but it seems to get the job done for now.

Reference: https://stackoverflow.com/questions/18992880/exclude-component-from-componentscan

Thanks again for the assistance.

That makes sense - to exclude them the component scanning.

I was thinking that if you’re using maven you could exclude them maven as well:
https://maven.apache.org/plugins/maven-resources-plugin/examples/include-exclude.html

Might be another to do this so the that the blocking files excluded and you don’t have to change code?

So. Right, the issue is that all classes which are generated by rsocket-rpc plugin are autowirable so it means if we do not implement anything Blocking*** then we have to exclude everything related to that

If the classes don’t have an implementation is there a way to skip them so that things to blow up?

I dont think so. My proposal is to make it less aggressive on the codegeneration level. For all cases I use RSocket-RPC I’m trying to generate all classes in different package, preferable out of spring classpath scan and then do manual configurations of what I really need

Make generating blocking interfaces optional?

1 Like

Right. At least more flexible

I’m still kind of confused though - seems like if there isn’t a concrete impl that we should be able to just skip that when wiring things up. I thought there are like bean controllers or something along those lines that we are used to wire things together?

In regards to making the generation of the blocking interfaces conditional, I think that would be a nice addition to the proto-gen plugin.

However, In regards to making the bean instantiation conditional, my understanding is that this could be accomplished by using the @Conditional annotation on an additional generated class that is annotated with the @Configuration annotation.

For example, the plugin could generate a UserServiceServerConfiguration class, which would then conditionally register any beans (see example below). I haven’t tested the below code, but the general concept is what I think might need to be done.

If a goal is to avoid using Spring specific annotations though, the below would definitely couple the generated source to Spring pretty heavily, so that may not be ideal.

import com.solidice.rsocket.generated.proto.BlockingUserService;
import com.solidice.rsocket.generated.proto.BlockingUserServiceServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import reactor.core.scheduler.Scheduler;

import java.util.Optional;

@Component
class BlockingUserServiceServerFactory {

    private final Optional<BlockingUserService> service;
    private final Optional<Scheduler> scheduler;
    private final java.util.Optional<io.micrometer.core.instrument.MeterRegistry> registry;

    @Autowired
    BlockingUserServiceServerFactory(
        Optional<BlockingUserService> service,
        Optional<reactor.core.scheduler.Scheduler> scheduler,
        Optional<io.micrometer.core.instrument.MeterRegistry> registry) {
        this.service = service;
        this.scheduler = scheduler;
        this.registry = registry;
    }

    public BlockingUserServiceServer create() {
        return new BlockingUserServiceServer(
            service.get(),
            scheduler,
            registry
        );
    }
}


@Configuration
class BlockingUserServiceServerBeanConfiguration {

    @Autowired
    BlockingUserServiceServerFactory factory;

    @Bean
    @ConditionalOnBean(BlockingUserService.class)
    BlockingUserServiceServer conditionalBlockingBean() {
        return factory.create();
    }
}

Yeah - probably don’t want to do that in the generator. I created an issue to make generating the blocking interfaces optional:

Pull request for this is available at https://github.com/rsocket/rsocket-rpc-java/pull/48