Closed philliphoff closed 9 months ago
@philliphoff - Thanks for writing up this proposal! Sorry it took me a bit to get to it, I saw the PR but somehow missed the proposal associated with it.
Overall, I think this is a good idea. My main concern is as follows:
Does this require that we make a separate interface for the Client? Or is there a way we can still have this based off the actual implementation and the code generation looks back at the interface and sees the annotations that way? My main concern here is that the two interfaces may get out of sync, which would lead to invocation problems.
Though I do see the benefits here in regards to a generated client but for an actor that exists in a different language, so ideally we'd have both.
@halspang
Does this require that we make a separate interface for the Client? Or is there a way we can still have this based off the actual implementation and the code generation looks back at the interface and sees the annotations that way? My main concern here is that the two interfaces may get out of sync, which would lead to invocation problems.
It doesn't require separate interfaces; you could still inherit from IActor
and implement the service side using the same interface, but you might lose some of the benefits such as freedom to alter the names or support for record
types, at least until what I'd call "phase 2" is implemented. The next step is to do something very similar for the remoted server side, where a skeleton actor service is generated with routes generated to map from endpoints to the appropriate method. This would be similar to the mapping currently being done, but hopefully a bit "lighter". It seemed best to worry about the server side as a separate issue/PR as this proposal can stand alone and the server side is more complex.
@philliphoff - Thanks for the clarifications! I think this is good to continue as is, looking forward to seeing what we can make out of it :)
Would it not make sense to also generate strongly types service invocation clients? Ive saw someone use refit but I think the drawback of this is uses an http client and might not use grpc if thats enabled for app protocol
Would it not make sense to also generate strongly types service invocation clients? Ive saw someone use refit but I think the drawback of this is uses an http client and might not use grpc if thats enabled for app protocol
@theperm If I understand you correctly, you suggest users define an interface that represents all (or some subset) of method invocation supported by an application, then using that interface to generate wrappers around the existing Dapr .NET SDK method invocation method? That's an interesting idea. You might even extend that idea to input/output bindings, too. Those would both be great things to have proposals/contributions for.
Exactly. Here is someone doing it using Refit but with the HTTP client interface. Refit with Dapr A similar concept would source generate a concrete class that would encapsulate the dapr client calls for service invocation. Our devs are hand rolling clients that do this anyway to make service invocation easier.
I have an aspect generator that creates this for service invocation for web backends. I'll have to dig into how easily it's open sourced (and made more generically available).
Overview
The Dapr .NET SDK offers two means to invoke methods of hosted actor instances, via a strongly-typed "remoted" proxy* or via an untyped "non-remoted" proxy. There are advantages and disadvantages to both approaches.
Remoted proxy:
public
IActor
record
types)GetStateAsync()
)Non-remoted proxy:
record
types)Neither approach may be an exact fit for developer's needs.
await/async
patterns even if the hosted actor interface does notProposal
I propose the .NET SDK offer strongly-typed non-remoted actor clients, using .NET source generators to generate strongly-typed actor interface implementations built upon the existing non-remoted
ActorProxy
proxy.This approach would enable the following
internal
(as source generators operate "inside" the client assembly)Non-goals
Support for multiple arguments per method invocation
While source generators could be used to offer similar multi-argument serialization, given the ease with which values can be bundled together by the client, I believe there is little need for this capability and it is a capability that could be added later upon sufficient demand.
Design
Actors Generators Package
The source generator(s) would be implemented and distributed in a new NuGet package,
Dapr.Actors.Generators
. This NuGet package would be added as a reference to client projects but using theOutputItemType=Analyzer
.Actor Client Generation
Suppose the client/host defines the actor interface:
The current remoted proxy invocation pattern would be:
The current non-remoted proxy invocation pattern would be:
As mentioned above, both of these approaches have a number of caveats.
Instead, the developer could indicate the desire to generate an actor client using a completely independent interface definition from that of the hosted actor:
Items to note:
GenerateActorClientAttribute
internal
IActor
ActorMethodAttribute
async/await
patterns (e.g. acceptingCancellationToken
) even if the host actor interface does notThe generated client would be:
Generated Client Use
The new proxy invocation pattern would be:
Notes