Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provides auto registration of gRPC client beans #91

Open
DanielLiu1123 opened this issue Jan 6, 2025 · 7 comments
Open

Provides auto registration of gRPC client beans #91

DanielLiu1123 opened this issue Jan 6, 2025 · 7 comments
Milestone

Comments

@DanielLiu1123
Copy link

As mentioned in this comment, users still need to manually register client beans:

@Bean
SimpleGrpc.SimpleBlockingStub stub(GrpcChannelFactory channels) {
    return SimpleGrpc.newBlockingStub(channels.createChannel("local").build());
}

In real-world applications, a service might use dozens or even hundreds of gRPC clients (I’m working on such a project). Manually registering gRPC client beans is unsatisfactory, especially when considering migrating starters from other community implementations to spring-grpc.

Similar issues occur with HTTP interfaces. We can look at this issue and how they plan to solve it. The same pattern should work for gRPC clients.

Here is a feasible solution I propose, grpc-starter provides a fully configuration-driven approach:

grpc:
  client:
    base-packages: [ com.example ]
    channels:
      - authority: user-service:9090
        stubs: # All clients under this configuration share this channel
          - com.example.user.v1.** # Ant-style patterns
          - com.example.user.v2.**
      - authority: order-service:9090
        stubs:
          - com.example.order.**

Then you can inject gRPC client beans into any Spring beans. I hope spring-grpc can provide a similar solution.

@dsyer
Copy link
Collaborator

dsyer commented Jan 6, 2025

I like the idea of a base package. It would be better represented in an annotation IMO (like @ComponentScan). Like @ComponentScan and the @EntityScan one for JPA we can probably make the default just follow the @SpringBootApplication so most users wouldn't have to explicitly configure it at all.

We are still waiting on some actual users to ask for this feature (other than existing library authors that is).

@bystronerik
Copy link

@dsyer We are currently using grpc-ecosystem/grpc-spring and are interested in switching to official integration. The functionality @DanielLiu1123 is referring to would be helpful for us, as we have many different clients and services.

@dsyer
Copy link
Collaborator

dsyer commented Jan 20, 2025

Thanks for that. Anyone else who has an opinion, please do add it.

Can we dig into your scenario a bit? How many clients are we talking about? How many stubs and how many channels? I would be surprised (and a little horrified) if you had really a lot of different channels. So is it the creation of multiple stubs for the same channel that we need to focus on?

The existing ecosystem projects each solve this in different ways and none of them is a great solution IMO, so we need to collect more data on what the actual requirement is.

@bystronerik
Copy link

Yes, primarily the creation of multiple stubs for the same channel. We have api gateway that communicates with all our services via gRPC, currently it's 6 channels and about 60 stubs.

@dsyer
Copy link
Collaborator

dsyer commented Jan 20, 2025

Creation of a stub is trivial, right? So are you saying you'd rather have 200 or 300 beans automatically created for you and only use 60 of them, than declare which ones you need explicitly, even though that is very straightforward (and has its own native API in grpc-java already)? If you had to enumerate them but didn't have to create @Bean definitions explicitly for each one, is that the right direction? I'm trying to find an approach that will automate some things but not result in programming in YAML, and also will not involve creating a bean definition for every stub on the classpath, including blocking, non-blocking, reactive variants etc. I don't know what that looks like yet, but we only find out by looking at scenarios in more detail, so please explain a bit more what your thinking is.

@DanielLiu1123
Copy link
Author

DanielLiu1123 commented Jan 21, 2025

I think Spring Cloud OpenFeign’s @EnableFeignClients is a solid practice. OpenFeign also offers a simple API for creating clients, but no one uses it. In large-scale applications, people generally prefer to auto-register beans using base-packages or clients instead of manual creation. Maybe we can provide a similar @EnableGrpcClients.

For most users, using base-packages is more appealing because you only need to set up the base-packages and the channel (authority) for the stub, and then you can use it. Although this approach creates many extra bean definitions, the beans aren’t instantiated since they are set to lazy initialization. This trade-off between simplicity and performance is acceptable—in my experience, registering a few hundred or even thousands of bean definitions (without instantiation) doesn’t harm performance (p1). Maybe after startup, we could use hooks to remove the extra bean definitions.

@EnableGrpcClients(basePackages = {
        "io.grpc",
        "com.example"
})

For experienced developers or performance-critical (startup time) applications, we can enumerate the specific classes that require bean creation. This approach avoids classpath scanning and prevents extra beans from being registered.

@EnableGrpcClients(clients = {
        SimpleServiceGrpc.SimpleServiceBlockingStub.class,
        SimpleServiceGrpc.SimpleServiceStub.class
})
Image

@dsyer dsyer added this to the 0.4.0 milestone Jan 28, 2025
@dsyer
Copy link
Collaborator

dsyer commented Jan 28, 2025

That might get us closer. I'm not sure I like that the base packages version creates all the blocking and non-blocking (and reactive?) stubs still. Also we need a way to map a client id/name to a group of stubs. We see this pattern already in the ecosystem, and also in Spring Cloud OpenFeign. The HTTP Interface support in Spring Framework is also evolving in that direction, with a named "client group" being the key to configuring different concerns like interceptors and base URLs (like a named Feign client in Spring Cloud, and a lot like the NamedChannel we already have in this project).

Maybe something like this would work (allow multiple enumerations keyed to the channel name):

@EnableGrpcClients(clients = {
        ...
        SimpleServiceGrpc.SimpleServiceStub.class
})
@EnableGrpcClients(name = "basic", clients = {
        ...
        OtherServiceGrpc.OtherServiceBlockingStub.class
})

(The default name is the default channel.)

The base package problem is harder. I'd also like to be able to specify a class not a string (like we already support for component scans and Spring Dtaa repositories). IDK, maybe

@EnableGrpcClients(basePackageClasses = SimpleServiceGrpc.class, factory = GrpcStubClients.class)

The factory is located from the application context (there has to be a bean of that type). The default could be GrpcBlockingStubClients (or whatever).

With the factory idea I guess the other example could be written as

@EnableGrpcClients(factory = GrpcStubClients.class, clients = {
        ...
        SimpleServiceGrpc.class
})
@EnableGrpcClients(name = "basic", factory = GrpcBlockingStubClients.class, clients = {
        ...
        OtherServiceGrpc.class
})

I'm not sure it really all fits in one annotation at this point. We can watch what happens in Spring Boot for HTTP interface clients.

Maybe it would help to implement the factories and see where that takes us. I guess there's no way to avoid reflection there, but it's the least evil way to do things.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants