fermyon / spin

Spin is the open source developer tool for building and running serverless applications powered by WebAssembly.
https://developer.fermyon.com/spin
Apache License 2.0
5.26k stars 249 forks source link

PolyGlot- Enable capability to write external triggers in C# and .NET core and many other languages -New features #910

Open VenkateshSrini opened 1 year ago

VenkateshSrini commented 1 year ago

Hi,

This feature is for allowing to create triggers in other languages like C# or Java. As of today, I see the triggers can all be built only using the RUST language. I also understand that Triggers can be any microservice. The one reason why only RUST can be used is that the triggers are doing a call-back to the Spin code that is RUST. I saw the code in main. rs. Though I'm not good at Rust I see the callback module loader and engine of the SPIN.

f that understanding is correct then additional thing needs to be done. Apart from exposing the engine and modules as an API, Is it also possible to provide a gRPC endpoint of the spin execution engine? The external polyglot language triggers can be clients of the Spin engine. Once the trigger starts running the first request will register itself as a client along with the wasm module that the spin SDK needs to invoke when triggering conditions are met. When a triggering condition occurs say a modification in the database or packet in the queue, we can invoke the gRPC endpoint called to execute with the instance id as a parameter and the record or message packet as another parameter.

There are similar components like DAPR or KEDA engines that are allowing external calling using gRPC. For Dapr this is still in the experimental stage.

If for some reason you decide the gRPC is not good candidate(though my personal preference will be gRPC) you can also look into RSockets

itowlson commented 1 year ago

This is interesting. Spin is designed to support triggers that live in external programs: the boundary is defined in terms of arguments, environment variables and files. In fact even built-in triggers are run as separate programs - albeit separate instances of the Spin executable. On the other, that trigger program needs to run the Spin engine. It doesn't "call back" to the Spin launcher: it's more like it replaces it. And, as you note, all the engine bits are written in Rust.

The timer trigger sample you linked to isn't really how triggers worked - that's more a demo/POC of running a specific timer program. For example, it doesn't read a Spin manifest to know which Wasm module to load, or for its configuration.

The place to do what you suggest in real usage would be to implement an ExternalTriggerExecutor in Rust that launched yet a listener process and set up a gRPC or whatever protocol with that. Such an executor should be compatible with the trigger program CLI - crates/trigger/src/cli - and the happy path might not be too complex (see craters/redis for how simple the TriggerExecutor infra can be). This could be costly for high-traffic or high-volume triggers, as every message and response would need to travel over gRPC, and would need quite a bit of thought over the protocol and regarding error handling.

I don't think this is on our radar at the moment, but if it's something you want to experiment with, let us know - very happy to chat!

VenkateshSrini commented 1 year ago

@itowlson ,

I do not have knowledge on Rust. I'm basically from C# and .NET core background. I want to build the trigger in .NET core and if you could share the proto file of the external process that can is launched by ExternalTrigggerExecutor, then I would make a call to that gRpc server and launch the spin SDK process. At present I'm unable to make much details out of craters/redis as that in RUST. If you could share some thing in JavaScript or C# or some guidance document it would be of much help

itowlson commented 1 year ago

To be clear, ExternalTriggerExecutor doesn't exist. So I can't give you a .proto file or share a sample. At the moment I'm just thinking out loud about how Spin could support what you want to do.

And I'm still wondering about whether we can do all this in one process, e.g. using a C-style interface rather than a remote one (a la P/Invoke), because the performance impact of marshalling large messages cross process. Goodness knows how that would work with all the async stuff going on though.

VenkateshSrini commented 1 year ago

@itowlson , I got your point on ExternalTriggerExecutor. At this point, I'm also in the same state of discussing my thoughts and jogging it down in a feature request. When someday when you build your backlogs these can be very useful items to create features and stories. Based on an ask from an external community you can then prioritize.

Now to the question of whether it should be a Platform Invoke (P/Invoke) or should it be gRpc. I will still feel that it should be gRpc. In this case, we are not alone. KEDA (Kubernetes event-driven auto-scaling, website https://keda.sh/) has wanted to have external scalers. There also the performance is critical and the scaling should happen within a short span of time. They have adopted gRPC to create external scalers (https://keda.sh/docs/2.8/concepts/external-scalers/). Dapr (Distributed Application programming, web site https://dapr.io/) is in the process of including external state stores for which there is no way to integrate the state store SDK into daprd sidecar. They have resorted to calling external state services through gRpc service ( https://github.com/dapr/dapr/issues/3787).

Now coming to the issue large message. I think from trigger we can go through the route of the message store to avoid bulky requests. I'm attaching a flow diagram that I have in mind. The diagram is made using darw.io so you can edit the same using stencil based on the need. SPIN-SDK-gRpc drawio

itowlson commented 1 year ago

Yes, in this case the event would just be the URI, which would be quick to transmit.

I've been having a play with this to explore the issues (like I said, this isn't really on our radar as a feature, but it's something I'm enjoying thinking about). I think I'm close to a limited demo, but I see at least one remaining major obstacle and that is what the interface to the Wasm module should look like. For a POC I can make it a dictionary or a JSON string, but the ideal is to have it strongly typed. Anyway it's a learning experience...!

VenkateshSrini commented 1 year ago

@itowlson ,

The architecture that I suggested is inspired by Azure Event Grid. So, that is also a proven model. Now when discussing of the interface, the means of communication between Trigger and WASM engine will be gRPC. When we talk about the interface between the spin engine and the WASM module then I suggest we use CloudEvents (https://cloudevents.io/). That way WASM module just serializes the message using a standard SDK. Then within the WASM Module, it can take the message URI and then extract the message from message store and do the desired action. That way all interfaces will be standard. Hence, coding will be very easy

itowlson commented 1 year ago

Hmm, at that point, why not just bake a CloudEvents trigger into Spin? Then the external program is just an adapter from the desired source to CloudEvents, and is decoupled from the Spin runtime (for example allowing it to run as a daemon).

VenkateshSrini commented 1 year ago

@itowlson that is a good choice but we can do it later. I point out that most of such like KEDA and Dapr have now opened it up to gRPC to call back into their respective internal API to perform either scaling or state persistence. We should go with it ground up. Once we enable this feature even CloudEvents can be done as an external trigger. This will make the spin cli or Spin wasm edge to concentrate more on run time, while the community builds the triggers and the worker WASM. Basically I see it this way

The link in CloudEvents C# SDK group on using CloudEvents in .proto file . This will be used in the Trigger written in .NET which would store the real message in the message store and attach the message link as the CloudEvent message body. This CloudEvent Message will be received by the SPin engine and then pass on the same to the worker WASM as shown in the diagram above

VenkateshSrini commented 1 year ago

Hi All, Any other updates or thought processes you have in this regard will be very nice to share here. My suggestion would be that we will keep this simple. The second thought process is that we should keep it very generic. We should define a message standard like CloudEvents and a standard protocol (gRpc). The third thought process is that we should keep the message store a plug and play one. Just in case Fermyon Spin SDK is deployed in Azure then it could use azure file disk or Azure blob storage. If it is deployed on AWS cloud provider it could use EFS or elastic redis or S3. Since the Trigger and the WASM (worker) are the ones that is going to deal with message store, they both can decide where to place the content and how to access them.

itowlson commented 1 year ago

No further thoughts at the moment, sorry. As I mentioned, this is not an area of focus for us right now. We have talked about supporting (and there are issues for) both a gRPC trigger and the CloudEvents message format (initially over HTTP); and one of the envisaged use cases for the plugins feature is to support fully external triggers. But a fully polyglot solution, with efficient messaging and good typing at the Wasm level, is something we'd want to do a thorough design for, and there are other things we need for focus on first. Sorry.

VenkateshSrini commented 1 year ago

@itowlson Yes, I do understand that you could move forward based on product priorities and business asks. I do not even know if this thread can be kept open till the time this is conceptualized and implemented, as, GitHub Stale bot would close it. When scheduled please let me know I would be interested in doing implementation on C# parts.

itowlson commented 1 year ago

Yes, definitely, and if the powers that be want to close the issue I'll make sure it gets captured somewhere more suitable for discussions and longer-term ideas. Thanks heaps for all the ideas and for taking the time to share them!

VenkateshSrini commented 1 year ago

@itowlson thanks. It was an outcome of a few discussions with @radu-matei and @technosophos on Twitter. There we even had a small discussion on using ROCKETS (https://rsocket.io/). But, then on further reading and exploration, I found that those implementations are slightly outdated and there are also some serialization issues. Hence I suggested a gRPC approach here.

The second point is there are still a lot of serverless pain points which I think Fermyon's SPIN will address in the modern day like Vendor lock-in, time-bound execution etc:- and at the same time wanted to bring the code already running today as Triggers. The community trigger idea was influenced by Azure functions triggers and Keda Scalers.

thangchung commented 1 year ago

@VenkateshSrini Check this blog. We approach using Spin with Dapr over cloudevent between Rust and .NET/C# apps

image

itowlson commented 1 year ago

The link seems to be missing from the previous message but I believe this is the blog series:

https://dev.to/thangchung/webassembly-docker-container-dapr-and-kubernetes-better-together-part-2-build-and-run-the-coffee-shop-backend-services-3c0e

Thanks @thangchung this is very cool to see!