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

Support function invocation in Rib #442

Closed
afsalthaj opened this issue Apr 19, 2024 · 13 comments · Fixed by #532
Closed

Support function invocation in Rib #442

afsalthaj opened this issue Apr 19, 2024 · 13 comments · Fixed by #532

Comments

@afsalthaj
Copy link
Contributor

Based on the discussion, instead of providing functionName: <name of the function> in the worker-binding, users should be able to directly invoke the function when writing Rib program.

In this way, we simply write a program when it comes to worker-bridge/api-gateway instead of a overly granular way of setting up worker-binding information.

// just define the response

let worker_response = get_cart_contents(request.body.user);
let result = match worker_response {
  ok(result) => result;
  err(msg) => 'internal error'
}

And a way to bind the result with the actual http response. Good if Rib allows it by exposing type

let response: HttpResponse = { status: 200, body: result, header: { } };
 

This way , we can ask users to provide Rib<HttpResponse> or Rib<GrpcResponse> etc

cc @jdegoes

Let me know if this is all making sense

@jdegoes
Copy link
Contributor

jdegoes commented Apr 20, 2024

You took the idea further than I imagined! However, I kind of like it, because it results in more uniformity and takes advantages of features already in "Rib".

But I would say, force the user to return the response in the last line of the expression (or with the return keyword), rather than relying on the name to be result or anything like that.

The one thing we have to worry about is durability guarantees for Rib: if a user can invoke many functions on many different workers, what sort of guarantees would they (a) expect, and (b) require?

I think a user might assume Rib is durable too. I think we cannot guarantee that for now. Eventually Rib could be compiled to WASM and executed as a worker. But that's far in the future.

@vigoo
Copy link
Contributor

vigoo commented Apr 21, 2024

Agreed with @jdegoes - I think the worker bridge should remain a 1-1 mapping between an arbitrary HTTP (or gRPC etc) request/response pair and a worker invocation. It should not have any side effects such as doing multiple invocations - it should just declare a pure mapping from input to invocation, and from invocation result to response.

Otherwise it is not only having the durability issues John mention, but also becomes quite confusing where you should implement your application logic. And I think we need to have a single way for defining application logic, by writing a component and running it as a worker.

@afsalthaj
Copy link
Contributor Author

afsalthaj commented Apr 22, 2024

Sure. I think we can constrain the Rib to 1-1 mapping to request/response, with the last expression in the program to be HttpResponse or GrpcResponse. The flexiblefunction invocation done within the program is still geared to the idea "that's the function you invoke as part of the http endpoint".

This will make sure we have the use-case supported by Rib constrained, yet flexible enough to deal with the use case.

@afsalthaj
Copy link
Contributor Author

And at the same time, it paves the path to future of being able to compile Rib to be able to run as a worker. For now the answer - "as far as it's a worker in Golem, it is durable, and Rib is not running as a worker".

@afsalthaj
Copy link
Contributor Author

afsalthaj commented Apr 22, 2024

Also currently the Expression's result is the result of last line

let x = { a : 1}
let y = { b: 2} 
x.a  > y.b

So if the user needs to do a request-response mapping, they are already forced to return http response as the last line. I wish we had types, but as of now they need to return the wasm record { status: 200, body: worker.response }

Also, to mention, the idea of invoking function within the expression may not be done before 1.0. It needs a lot more changes

@vigoo
Copy link
Contributor

vigoo commented Apr 22, 2024

Why would we want to compile Rib to be a worker? I think I'm missing the goal here.
The last expression being forced to be a response does not solve the problem we raised - with this you can still have multiple function invocations in your Rib code and that in my opinion is a problem.

Is there any real-world example we are trying to solve with this level of flexibility?

@jdegoes
Copy link
Contributor

jdegoes commented Apr 22, 2024

@vigoo @afsalthaj I think for now we could just restrict to a single worker invocation per Rib script. Note that's not really enough to entirely solve the problem because worker-bridge provides no guarantees that just because this invocation takes place, a value will be returned by the API. We need to solve the idempotency problem before we could even talk about a solution to this problem.

But keep in mind there will be other functions we introduce over time, which do not perform RPC invocation, and which are nonetheless useful. An example is a function that parses a JSON string into a component model value. Or a function that generates a random number. Or a function that uppercases strings.

Rib is designed to be the "glue" that allows you to put different APIs atop components. Every API Gateway has something like Rib (Lua for Kong, an unnamed expression language for Tailcall, user-defined language for Tyk, etc.). I don't think we can escape from having something like Rib but we should avoid it growing too big for us to handle.

@afsalthaj
Copy link
Contributor Author

So, should we place this ticket as part of milestone 1.0?

@afsalthaj
Copy link
Contributor Author

afsalthaj commented Apr 30, 2024

We can totally avoid calling multiple worker functions at once. I don't think the purpose of this ticket pointed to be able to invoke multiple functions.

However, in a larger context, not everything that uses/works-wiith Golem has to be durable. Take Golem-Timeline, for instance. With that, I can whip up some Rib code that just meshes together results from different workers and shoots them back to the user. To have a more precise example: Write an endpoint that returns a Json of all subcomputation results of a complex computation done by golem-timeline in a durable way.

Summary:

  • As all of us agreed here, Rib doesn't need to be durable (through compiled to wasm and be a worker in Golem) straight away or even in a considerable future.
  • Solving this ticket doesn't (or shouldn't) result in multiple function invocations from worker
  • I am yet to understand the reasoning of why multiple function worker function invocation from Rib is a serious issue.

@vigoo
Copy link
Contributor

vigoo commented Apr 30, 2024

I am yet to understand the reasoning of why multiple function worker function invocation from Rib is a serious issue.

As I wrote, I think it gives too much power by introducing an alternate way of doing things (compared to writing your worker) instead of just mapping a worker's request/response to an alternative protocol defining a pure 1-1 mapping.

Solving this ticket doesn't (or shouldn't) result in multiple function invocations from worker

How is it prevented?

@afsalthaj
Copy link
Contributor Author

We build expressions, and when building it, if we see more than 1 invocation we simply fail and return, without even going to evaluation of Rib.

@jdegoes
Copy link
Contributor

jdegoes commented May 1, 2024

I think we should propagate idempotence keys from the API into any worker invocations. If we do that, I say it is not necessary to prevent multiple worker invocations. But neither is it necessary to support them now.

What we need for now is:

  1. Support for invoking one worker function
  2. Support for idempotence keys (i.e. Idempotence-Key header is propagated to worker invocation)

That's enough to solve existing use cases, and room to grow in the future.

I don't think we should entertain durable Rib or compilation. Though at some point, I think we will do performance work on Rib in order to lower infrastructure costs, and whether or not that could involve compilation is anyone's guess.

Perhaps to return the final value, we should use return keyword?? This follows the lead of the Claw programming language (return response;), and sort of imagines that the entire Rib script is the body of a Rib function that is passed the worker variable (which, as a mental model, I like).

@afsalthaj
Copy link
Contributor Author

Fixed in #532

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

Successfully merging a pull request may close this issue.

3 participants