I really miss declarative service exporters. I'm happy to see the declarative rsocket client option available, but the serverside still requires a ton of boilerplate. Creating controllers to mirror service interfaces is tedious.
To this end I'd like to see an option to trivially mark an interface and have it autogen the proxy etc required to expose it as an rsocket service. This should work with webflux and mvc worlds.
thoughts?
Comment From: rstoyanchev
It's not clear what you mean. On the one hand, you mention service exporters which reference the implementation to use. On the other, autogen and proxy which is to create a client, but we already have that with RSocketServiceProxyFactory
.
Comment From: jeacott1
@rstoyanchev sorry for the confusion. afaik the RSocketServiceProxyFactory is for client creation which while helpful is not what I am looking for. I want to restore a facility that works like the old service exporters to create rsocket endpoints via trivial interface definitions, and to be able to configure the serialisation. eg ``` RSocketServiceServiceExporter exporter = new RSocketServiceServiceExporter(); exporter.setService( new MyServiceImpl() ); exporter.setServiceInterface( MyServiceInterface.class ); exporter.setSerializer(new FurySerializer())
having to write all the controller boilerplate for backend services is ridiculous vs the above snippet.
**Comment From: rstoyanchev**
I understand now. Can you add `@MessageMapping` annotations on `MyServiceInterface`? The declarative service exporters were created in a very different time, and were a lot more viable as an option before Java 5 and annotations.
**Comment From: jeacott1**
@rstoyanchev imo the declarative service exporters were completely viable post java annotations, they worked!
@MessageMapping is method level only, so far from convenient:
@Target({ElementType.TYPE, ElementType.METHOD})
Also, afaik its stomp only. I like stomp well enough, but it's not rsocket.
It's also not really what I want. I have literally hundreds of interfaces I want to expose as services. The old apache cxf libraries do this for soap without pain, and without requiring modification, or annotation of those interfaces. The old declarative service exporters also could have supported my case with minimal effort.
I really think that spring should endeavour to replace the lost capability instead of having folks go back to inventing their own.
Remote invocation/listen is a pretty universal requirement, but nobody wants to create controllers for this stuff, they're just noise.
**Comment From: rstoyanchev**
@jeacott1 have you looked at the documentation? `@MessageMapping` is what is use for RSocket https://docs.spring.io/spring-framework/reference/rsocket.html#rsocket-annot-messagemapping, and that's both method and type level, with the former inheriting and extending the latter as usual.
Annotations declare routes, which allows us to map incoming requests to specific methods. How would this be expressed and done otherwise? I suppose you're asking for us to also create a client that inserts metadata in the RSocket request that indicates the service/method to call on the server side, and of course this would only work if using that client we would provide.
**Comment From: jeacott1**
@rstoyanchev yes I have, and I think you are missing the point.
@MessageMapping defines a route and is intended for use on controllers with an event-ish nature. I certainly dont want to create controllers with thousands of routes to facilitate rpc - thats boilerplate that buys me nothing.
Its just not suitable for use to expose hundreds of interfaces each with 50+ methods as the basis for rpc.
I'd argue that having to create controllers at all is a waste of time generally when you already have well defined interfaces.
In order to practically use @MessageMapping I'd have to create myself a controller endpoint, and then manufacture myself (or leverage something) an RPC protocol, build myself a proxy that can map the incoming messages to the appropriate interface methods, and then build myself a client lib generator so that clients could use the rpc client. Spring remoting, particularly with hessian previously gave me all of this for the cost of a few lines of code! its simply not possible now without resurrecting and altering old spring code or DIY from scratch.
What I'd like to be able to do is in the most simple way possible define service endpoints without touching existing code.
That could look like an annotation I'd have to add onto a copy of an interface that also declared its intended delegate impl, which could look a bit like spring data repository declarations - but that seems very not IOC. The example RSocketServiceServiceExporter I offered earlier seems flexible, and relatively simple.
Spring needs something to fill this space now.
**Comment From: rstoyanchev**
> I'd have to create myself a controller endpoint, and then manufacture myself (or leverage something) an RPC protocol, build myself a proxy that can map the incoming messages to the appropriate interface methods, and then build myself a client lib generator so that clients could use the rpc client.
Right, for this to work, both client and server have to agree on the routing metadata used. That means not only a service exporter but also a declarative client proxy. You mentioned the declarative rsocket client option that's now available, but that's not the same.
What is available currently with the [RSocket Interface](https://docs.spring.io/spring-framework/reference/rsocket.html#rsocket-interface) expects an `@RSocketExchange` annotation to define the route, but with that in place it is supported for both client and server use. So it's not just a declarative client proxy. You can also use it for server handling, and that doesn't have to be a controller. Just an implementation of the interface to provide server handling.
There are some customizations possible. `RSocketMessageHandler` can be configured with a handler `Predicate` (e.g. no need for `@Controller`), and its `getCondition` method is protected (e.g. determine route without `@RSocketExchange`). The `RSocketServiceProxyFactory` is a less open right now in how it determines what methods to proxy and what route to use, but we could make that more flexible too.
In short, I think what we have with the RSocket service interface support is already quite close to what you are asking for. It just requires the `@RSocketExchange` annotation to be on interface methods. In the short term, we could make some changes to make it possible to customize this so that annotations are not even needed. In addition, we could explore first class support for a mode where you only need an interface, and routes are determined using default conventions.
**Comment From: jeacott1**
@rstoyanchev it sounds like you have something in mind here. I'm not entirely sure its what I'm looking for, but what you suggest sounds ok. " In addition, we could explore first class support for a mode where you only need an interface, and routes are determined using default conventions."
This sounds ideal. I would hope I would not have to add @RSocketExchange on every method on an interface nor declare routes. I'd want to add it at the interface level, and preferably not have to add it at all for situations where I'm dealing with generated code for example. For me, creating a mirror api of my target interfaces with annotations and delegating to my target whilst being a bit verbose is probably a better pattern for the cases I'm thinking of anyway. Still need a client lib generator (or something) to match and pluggable serialisation and I think we're home. A client via proxy is ok but it's not going to work out of the box for other language clients (javascript, python etc).
Thank you.
**Comment From: MrWangGang**
I have already implemented an RPC framework for RSocket in my framework. You can check out my code repository.
I am starting a business and this is my open-source framework. The README.md is not up-to-date, but I will update it once the project is finished, as my team needs the documentation.
"In this framework, the focus is on user authorization and authentication, and propagating authorization information across the microservice chain. To retrieve user information for any service, it’s as simple as:
`securityPrincipalHolder.fetchPrincipal2Object(UserPO.class).flatMap(seller -> { ... });`
Moreover, this information is not stored in Redis cache and does not require network retrieval every time. Instead, it is included in the socket setup when establishing the connection through the RPC framework and then extracted into the contentView. The RPC framework utilizes a service registry such as Nacos or Zookeeper, or even direct TCP connections, for service calls."
https://github.com/MrWangGang/lambda-framework
Service Provider:
@RSocketRpcDiscorvery("ace-microservices-social")
public interface SocialRelationApi {
@RSocketRpcType
public Mono
@RSocketRpcApi public class RelationApi implements SocialRelationApi {
@Resource
private CompadreFunction compadreFunction;
public Mono<Void> establishCompadre(EstablishCompadreDTO dto) {
return compadreFunction.establishCompadre(dto);
}
}
Service Consumer
@RSocketRpc
private SocialRelationApi socialRelationApi;
EstablishCompadreDTO establishCompadreDTO = EstablishCompadreDTO.builder()
.compadreId($trade.getBuyerId())
.origin(ENUM_RELATION_ORIGIN_TRADE_SKILL)
.build();
return socialRelationApi.establishCompadre(establishCompadreDTO);
**Comment From: MrWangGang**
"The RPC framework operates by using ASM bytecode manipulation for beans. You can check out my source code, which is located in model:lambda-framework-rpc, specifically in the following classes:
org.lambda.framework.rpc.adapter.RsocketAsmProxyClassFactoryPostProcessor org.lambda.framework.rpc.adapter.RsocketRpcProxyBeanFactoryPostProcessor
It is crucial to note the `org.lambda.framework.rpc.adapter.support.CustomClassLoader `class loader. Since Spring’s class loaders are provided by ClassLoader, you must use the Spring class loader, as shown in:
public CustomClassLoader() { super(CustomClassLoader.class.getClassLoader()); }
Using Spring's class loader is necessary to avoid class loader exceptions."
**Comment From: MrWangGang**
"By using inheritance, you can achieve rapid CRUD development."
@Repository
public interface ITradeSkillRepository extends ReactiveMongoCrudRepositoryOperation
public interface ITradeSkillService extends IDefaultBasicService
@Service
public class TradeSkillServiceImpl extends DefaultBasicServiceImpl
"Below are some common CRUD-related operations. Even the long-standing issue of pagination in Reactor has been abstracted out for easier use.
Currently, the framework supports MongoDB and R2DBC for databases. The entire framework is designed around reactive development. As long as the database supports reactive drivers, support for more databases will be gradually added."
public interface IDefaultBasicService
Mono<PO> insert(PO po);
Flux<PO> update(Publisher<PO> pos);
Flux<PO> insert(Publisher<PO> pos);
Mono<Void> delete(ID id);
Mono<Void> delete(List<ID> ids);
Mono<Void> deleteBy(PO po);
Mono<Void> deleteBy(List<PO> pos);
Mono<Void> deleteAll();
Flux<PO> find(PO po);
Flux<PO> find();
Mono<Paged<PO>> find(Paging page, PO po, Sort sort);
Mono<Paged<PO>> find(Paging page, PO po);
<Condition, VO> Mono<Paged<VO>> find(Paging paging, Condition condition, UnifyPagingSqlOperation<Condition, VO> operation);
<VO> Mono<Paged<VO>> find(Paging paging, UnifyPagingSqlDefaultOperation<VO> operation);
Mono<Long> count(PO po);
Flux<PO> fuzzy(PO po);
Mono<PO> get(ID id);
Mono<PO> get(PO po);
} ```
Comment From: rstoyanchev
@MrWangGang I think it's useful to have a reference to such framework. However, this issue is not the right place for further details on how it works.
Comment From: rstoyanchev
I would hope I would not have to add @RSocketExchange on every method on an interface nor declare routes. I'd want to add it at the interface level, and preferably not have to add it at all for situations where I'm dealing with generated code for example.
Firstly, I'm pointing out that the @RSocketExchange
is for both client and server use. Secondly that there is almost enough flexibility for you to be able to customize it so that all you need is an interface, e.g. transparently plugging in a route based on the interface name and method, and applying the route for mapping on the server side. For 6.2, I can make sure that flexibility is in place.
Still need a client lib generator (or something) to match and pluggable serialisation and I think we're home. A client via proxy is ok but it's not going to work out of the box for other language clients (javascript, python etc).
Yes, this is what I was also alluding to earlier. We can only help with Java peer interactions, not with other languages. There is no codegen involved, and this is not different from declarative service exporters.
Comment From: jeacott1
There is no codegen involved, and this is not different from declarative service exporters.
true, but at least hessian had almost universal client lib support, made it super easy to use from anywhere
Comment From: jeacott1
@MrWangGang thanks. I'm not really interested in the reactive api though myself and ASM bytecode manipulation sounds sketchy. and perhaps prone to not playing well with others (you've already got classloader constraints). I'm also not sure why I need another framework to propagate authorization? I think I'd be opting for integrations with zanzibar clones, openFGA etc if I was looking for something more than what spring already provides. This is all off topic though.
@rstoyanchev Thanks for taking the time, and I think from your responses the future is already looking much brighter for this particular niche.
Comment From: bclozel
We didn't get any demand nor upvote in this niche space over the last few months. We don't intend to invest in this particular feature for now, so I'll decline this issue. We can reconsider in the future and reopen it.
Comment From: jeacott1
@bclozel niche space? seriously? the current spring direction is a disaster of boilerplate. I'm incredibly disappointed with this decision.