Skip to content
This repository has been archived by the owner on May 28, 2020. It is now read-only.

Submit service API proposal #21

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open

Conversation

Gozala
Copy link

@Gozala Gozala commented Nov 5, 2018

No description provided.

@pfrazee
Copy link
Member

pfrazee commented Nov 6, 2018

Good proposal, thanks for submitting. I also want to explore RPC-focused mechanisms and long-lived execution models before we decide on anything. The HTTP-like interface can be used for RPC, but it's a bad mapping to language semantics and I'd like to explore what something built on persistent message channels (perhaps with JSON-RPC on top) looks like.

@Gozala
Copy link
Author

Gozala commented Nov 6, 2018

Good proposal, thanks for submitting. I also want to explore RPC-focused mechanisms

👍 Let me know if I can help in any way.

and long-lived execution models before we decide on anything.

Since long-lived execution came up multiple times, I'd like to propose to not tie this proposal to the lifetime. While I do think that committing to lifetime guarantees would be a mistake that can be a separate discussion neither RPC nor IPC imply specific lifetime guarantees so maybe it's best to have that discussion on it's own merits.

The HTTP-like interface can be used for RPC, but it's a bad mapping to language semantics and

I am still not sure I understand reasoning for this claim. If you can provide some specific limitations / inconvenience that would really help and I'd love to include that into list of constraints.

I'd like to explore what something built on persistent message channels (perhaps with JSON-RPC on top) looks like.

On top of this proposal or as an alternative ? If that is on top of this one I can try and help with this exploration. Maybe in a branch somewhere or as separate node based server just for illustration.

If you thinking of JSON-RPC as an alternative, I'm not sure I understand your motivation, HTTP is a form of RPC, according to wikipedia in reference to RPC

This is a form of client–server interaction (caller is client, executor is server), typically implemented via a request–response message-passing system.

JSON-RPC is that except it's limited to message format being in JSON and I strongly favor to not have such limitation.

@RangerMauve
Copy link

I think it would be a good idea to look at how Worklets are structured since you could think of this as a sort of "ServiceWorklet" proposal.

@Gozala
Copy link
Author

Gozala commented Nov 6, 2018

I think it would be a good idea to look at how Worklets are structured since you could think of this as a sort of "ServiceWorklet" proposal.

Can you provide a link ? I don't know what are you referring to ? If Service Workers it's closely based on it, except I left out some event listener awkwardness. Request / Response and lifetimes are all from ServiceWorkers spec.

@RangerMauve
Copy link

@RangerMauve
Copy link

Right now worklets are aimed at being a lightweight alternative to WebWorkers which are used for stuff like rendering, layout, and audio processing.

@pfrazee
Copy link
Member

pfrazee commented Nov 6, 2018

The HTTP-like interface can be used for RPC, but it's a bad mapping to language semantics and

I am still not sure I understand reasoning for this claim. If you can provide some specific limitations / inconvenience that would really help and I'd love to include that into list of constraints.

Ideally, we'd have RPC which looks like this:

rpc.exportAPI(channel, {
  foo () {
    ...
  }
})

var api = rpc.importAPI(channel)
await api.foo()

That's a really direct mapping to the programming language, because you can just export and import functions.

With HTTP or HTTP-style, you use fetch() and write server request-handlers, which is all fairly clunky. HTTP maps to a files metaphor rather than to functions. On the client-side, that results in every Ajax call being wrapped in a function:

async function updateUser (hostname, userId, data) {
    var response = await fetch(hostname + '/users/' + userId, {
        method: "POST",
        headers: {
            "Content-Type": "application/json; charset=utf-8"
        },
        body: JSON.stringify(data)
    })
    return await response.json()
}

Which is pretty crummy compared to an RPC solution that just maps to function calls:

var api = rpc.importAPI(channel)
await api.updateUser('bob', {bio: 'Cool hacker guy'})
// ^ no boilerplate needed

Writing server request-handlers is much worse, because you have to route the method and path using conditionals or some kind of mapping structure, and then you handle content-type negotiation, and then a number of other headers such as caching. If we're just doing RPC, it's much easier to simply export the RPC function.


The two scenarios we're considering for Beaker right now are

  1. Exporting APIs between local apps
  2. Exporting APIs between peers using PeerSocket

Both of which favor long-lived channels, are designed for RPC, and don't currently require HTTP-like semantics. So I'm basically wondering, could we create something nicer for RPC than HTTP?

JSON-RPC is just a default thought, to me, because it's relatively simple, but I'm open to discussing all the requirements of what the ideal RPC solution would have.

@Gozala
Copy link
Author

Gozala commented Nov 6, 2018

Ideally, we'd have RPC which looks like this:

rpc.exportAPI(channel, {
  foo () {
    ...
  }
})

var api = rpc.importAPI(channel)
await api.foo()

That's a really direct mapping to the programming language, because you can just export and import functions.

Thanks for clarifying what you meant. I'd like bring up two things:

  1. ExportAPI and ImportAPI can be a user space library just as well:

Server

export default gen_serve({
   foo() {
    // ...
  }
})

Client

const client = await gen_client(endpointURL)
client.foo(...)

In fact that is how how Firefox devtools are implemented. Note that gen_serve is able to also define message for describing protocol such that client can be populated with right methods and even do some limited type verification.

Additional benefit you can get is plugeable transport layer which. I'm happy to share more details if you like as I have experience with this and was the one who added ability for server to describe itself so that client can be generated.

  1. It seems convenient but it's deceiving and what I mean by that is once you start considering lifetimes & identities of objects along with how values crossed across get GC-ed it becomes a very difficult problem. From my experience (again from Firefox devtools) this is often the source of many issues that cause either memory leaks or references to obsolete instances. And that is the case even if you have access to things not available to JS like weak refs.

Larger point I'm trying to make it is while it does look nice it does come with non obvious trade-offs.

@Gozala
Copy link
Author

Gozala commented Nov 6, 2018

Writing server request-handlers is much worse, because you have to route the method and path using conditionals or some kind of mapping structure, and then you handle content-type negotiation, and then a number of other headers such as caching. If we're just doing RPC, it's much easier to simply export the RPC function.

If all you want is RPC, I think it's reasonable to generate server / client. On the downside it does allow importing some library to do so but on the upside it allows us cases where RPC isn't ideal.

@Gozala
Copy link
Author

Gozala commented Nov 6, 2018

The two scenarios we're considering for Beaker right now are

1. Exporting APIs between local apps

2. Exporting APIs between peers using `PeerSocket`

Both of which favor long-lived channels, are designed for RPC, and don't currently require HTTP-like semantics. So I'm basically wondering, could we create something nicer for RPC than HTTP?

JSON-RPC is just a default thought, to me, because it's relatively simple, but I'm open to discussing all the requirements of what the ideal RPC solution would have.

In fact you could have same RPC generator library that could wrap either service transport or PeerSocket one which could be a benefit as well.

@Gozala
Copy link
Author

Gozala commented Nov 6, 2018

Both of which favor long-lived channels, are designed for RPC, and don't currently require HTTP-like semantics. So I'm basically wondering, could we create something nicer for RPC than HTTP?

I'm also not necessarily agreeing with this statement (although it depends who you were referring to with we).

Things I've being trying to build are in the lines of perform this operation please, and for that I would much rather not manage connections or manage references to it.

I have to admit that I might be on the fringe side as I also end up wrapping all of the DatArchive API to get out of the business of holding onto archive instances, etc..
dat://6dd4a37c98ef31d2c6a13b27d27a25e2fc7fa9b7bc16b72617852b043367a0be/src/Effect/dat.js

Which reminds me of the fact that I have always wondered how does the background process drops refs for archives that are no longer in use (GC-ed) ?

@Gozala
Copy link
Author

Gozala commented Nov 6, 2018

One more thing to consider is if you ever want to also allow Wasm based services JSON-RPC may not necessarily map all that well there and memory model could be very different (already case for Rust). Request / Response with a binary interface is far more flexible in that regard.

@Gozala
Copy link
Author

Gozala commented Nov 6, 2018

I think it would be a good idea to look at how Worklets are structured since you could think of this as a sort of "ServiceWorklet" proposal.

https://developer.mozilla.org/en-US/docs/Web/API/Worklet

I took a glance at the spec https://drafts.css-houdini.org/worklets/#worklet it appears to me that they are dedicated scripts that run in the same main thread just given much more limited access to the document they belong to. So I don't think it's very relevant. I'll take a deeper analyses if we end choosing to purse this approach. Although overall I don't think it would have much impact as spec intentionally omits implementation details like OS thread per worker vs thread pool and what not.

@pfrazee
Copy link
Member

pfrazee commented Nov 7, 2018

I wasn't suggesting we'd have distributed objects. That would be a bad idea. I'm just suggesting we use a protocol that maps naturally to functions.

@Gozala
Copy link
Author

Gozala commented Nov 7, 2018

I wasn't suggesting we'd have distributed objects. That would be a bad idea. I'm just suggesting we use a protocol that maps naturally to functions.

I understand you were not suggesting it, but you can't prevent people from doing mistakes. I would also argue that long-lived execution model does create an expectation that things on the other end will be across multiple requests, which is also a reason why I'm advocating no lifetime guarantees beyond request lifetime and no 1:1 client / server relation that sets up more realistic expectation IMO.

@pfrazee
Copy link
Member

pfrazee commented Nov 7, 2018

I understand you were not suggesting it, but you can't prevent people from doing mistakes.

I'm not concerned. We've had JSON-RPC over WebSockets for a long time, it hasn't led to a lot people doing distributed objects.

I would also argue that long-lived execution model does create an expectation that things on the other end will be across multiple requests, which is also a reason why I'm advocating no lifetime guarantees beyond request lifetime and no 1:1 client / server relation that sets up more realistic expectation IMO.

We'll just need to explore the options here. Idle long-running background scripts may take too much resources to be feasible, but we need to know more about our requirements before I can jump to any conclusions.

@Gozala
Copy link
Author

Gozala commented Nov 7, 2018

After thinking little more about it I'm realizing that it might make more sense to embrace ServiceWorkes API with a foreign fetch. If mainstream browsers would ever to adopt this it's likely the most viable option would be to just allow service workers accept requests across different origins.

That being said it does'n necessarily invalidates this proposal just something worth considering. At the end of the day adapting this API to Service worker would be fairly simple would just require:

self.addEventListener('fetch', event => event.respondWith(default(event.request)));

Still I though it was worth pointing out.

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

Successfully merging this pull request may close these issues.

3 participants