icerpc / icerpc-csharp

A C# RPC framework built for QUIC, with bidirectional streaming, first-class async/await, and Protobuf support.
https://docs.icerpc.dev
Apache License 2.0
102 stars 13 forks source link

Using Slice without Slice definitions #4011

Open bernardnormier opened 3 months ago

bernardnormier commented 3 months ago

The current process when writing an IceRPC+Slice application is: a) describe your Slice interfaces in a Slice file (.slice) b) generate code from these Slice files with the Slice compiler c) write your own code that calls the generated proxy (for invocations) or implements the generated Service interfaces (for service implementations)

This is a proposal to allow IceRPC users to skip (a) and (b), and write IceRPC+Slice applications without Slice files or the Slice compiler.

Note that this proposed path is an alternative to the current path. Not a replacement.

Proxies

If you write a client application, you could define a Slice proxy by adding an attribute to your own hand-written interface, for example:

namespace Xyz;

[SliceProxy] 
interface IGreeter
{
    Task<string> GreetAsync(string name, CancellationToken cancellationToken = default);
}

[IceRpc.Slice.SliceProxy] instructs the IceRpc+Slice source generator to generate a NameProxy struct that implements all the methods of this interface. These methods must return a Task or Task<T> and have an Async suffix. The features parameter is optional (i.e. the user doesn't need to include it). Maybe the cancellationToken can be optional too.

We would offer some level of customization for the generated proxy, such as the ability to set the default path and the ability to set the operation name (all via attributes).

Services

If you write a server application, you can define a service by adding an attribute to your hand-written class:

namespace Xyz.Server;

[SliceService] 
internal partial class Chatbot // must be partial
{
   // any access-level is fine
   internal ValueTask<string> GreetAsync(string name, CancellationToken cancellationToken)
   {
        Console.WriteLine($"Dispatching greet request {{ name = '{name}' }}");
        return new($"Hello, {name}!");
   }
}

The (existing) attribute [IceRpc.Slice.SliceService] instructs the IceRpc+Slice source generator to implement IDispatcher using the xxxAsync methods defined by this class.

Like for the proxy methods, IFeatureCollection and possibly CancellationToken would be optional. We would also provide an attribute to exclude methods from the source-generated dispatch.

Related issue: #3738.

pepone commented 3 months ago

How would you deal with Enum/struct parameters that rely on generated code for marshaling?

bernardnormier commented 3 months ago

The parameters of implicitly defined Slice operations need to be encodable/decodable. So they are either primitive types like String and int, or they are user-defined types with Slice attributes.

For example:

[SliceType] // source generator provides encoding/decoding support
struct MyStruct {
   ...
}