elast0ny / wamp_async

An asynchronous WAMP client implementation written in rust
22 stars 10 forks source link

Consider having high-level interfaces #3

Open frol opened 4 years ago

frol commented 4 years ago

I think, we should be able to use RawValue instead of a parsed Value (see #2), and thus avoid unnecessary deserialization (see https://github.com/pandaman64/serde-query/). I would also like to have a more high-level interface to kwargs, so instead of:

async fn echo(args: Option<WampArgs>, kwargs: Option<WampKwArgs>) -> Result<(Option<WampArgs>, Option<WampKwArgs>), WampError> {
}

I would prefer to have an option to write:

async fn echo(input: MyDeserializableInputType) -> Result<MySerializableOutputType, WampError> {
}

And from the caller side instead of:

let (Some(positional_args), Some(keyword_args)) = client.call("peer.echo", args, kwargs).await?;

use:

let result = client.call("peer.echo", my_serializable_struct).await?;
elast0ny commented 4 years ago

I like this idea but I'm not so sure how this could be achieved. I remember struggling with the fact that variable number of arguments & named arguments just didn't translate well to Rust.

Looking at some python examples, I can see that RPC functions are simply python functions with decorations that do a bunch of magic that I assume are :

I really like this style as it offloads any validation from the API endusers. Do you think this is something that could be do-able in Rust ?

Im thinking with macros, we might be able to generate code that does the "deserialization"/validation of WampArgs/KwArgs and call Rust RPC endpoints without them needing any WAMPisms tacked on ?

The only downside is that Rust doesnt have KwArgs so we would still need to pass some dict as a parameter if the RPC endpoint uses named arguments.

For simplifications at the call site, im guessing macros could also be used to convert regular Rust types into a vec of WampArgs. Maybe it could be done for KwArgs too...

oberstet commented 2 years ago

Hi guys, just had been looking around for WAMP and Rust, stumbled across the repo here (), and wanted to chime into this discussion .. comment .. rgd

I think, we should be able to use RawValue instead of a parsed Value (see https://github.com/elast0ny/wamp_async/pull/2), and thus avoid unnecessary deserialization

WAMP as in AutobahnPython and Crossbar.io has 2 ways of avoiding serialization overhead:

  1. no serialization/deserialization for the application payload (args/ kwargs) transported in WAMP messages
  2. use of Flatbuffers for the WAMP messages itself (plus passthrough of serializer-less args/kwargs)

The former feature is called "payload transparency", and instead of args/kwargs, it uses one payload of type binary which is left untouched and uninterpreted by routers

https://github.com/crossbario/autobahn-python/blob/cec0e991b9cb4dd575a1ee783b5ac0069bb13681/autobahn/wamp/message.py#L3963

In Autobahn for client side, this is exposed via "payload codecs"

https://github.com/crossbario/crossbar-examples/blob/0f52db626fd1e1a9bfee1daaea1eeadc30bb08e9/payloadcodec/cbor2_codec.py#L117

Two higher level uses of above is

Looking at some python examples, I can see that RPC functions are simply python functions with decorations that do a bunch of magic that I assume are

yes, Autobahn-Python features function decorators that can register/subscribe the python function, plus type check arguments and auto-map exception classes from/to WAMP error URIs

AutobahnPython also includes experimental code for the Flatbuffers based WAMP messages stuff:

https://github.com/crossbario/autobahn-python/tree/master/autobahn/wamp/flatbuffers

Here is a WAMP Event as written as Flatbuffers:

https://github.com/crossbario/autobahn-python/blob/cec0e991b9cb4dd575a1ee783b5ac0069bb13681/autobahn/wamp/message.py#L3601

This code also illustrates the distinction of the serialization/parsing question between 1) application payload and 2) WAMP message itself

In summary: If you combine Flatbuffers for WAMP messages plus Flatbuffers for application payload, you can have zero-serialization, zero-copy for RPC/PubSub app payload in Rust.

And finally: using Flatbuffers for WAMP application payload definition also allows to specify the whole WAMP application interfaces with Procedures and Topics in Flatbuffers, eg https://github.com/wamp-proto/wamp-xbr/blob/master/work/schema/example1.fbs


Now, I should probably add: this Flatbuffers stuff isn't in the WAMP spec, and is experimental. However, I think it demonstrates one option how we could evolve WAMP and implementations to support zero-copy/serialization and strongly typed interfaces in WAMP ..

NeoVance commented 2 years ago

Payload transparency isn't in the spec for a reason. All participating clients need to be able to encode/decode the payload format independently. While this is a feature of Crossbar (A commercial product I might add) specifically, it doesn't make a lot of sense to create a generic wamp client library for use with a specific router, and which can only communicate with other clients using the same payload encoding.

If a client using Flatbuffers could send/receive data transparently with one that wasn't as is the case with the serialization formats described in the spec that wouldn't be a problem, but as of now Crossbar.io is the only router I know of that has any amount of Flatbuffer support, and it is only through a technique that skirts the requirements of the router to be able to convert payloads between the different clients requested serialization types. (JSON/MsgPack).

That doesn't really go to the original question which was about the user facing API. In that vein, I would say for the most part leave the API as low level as possible and allow users to implement higher level abstractions over it, as the library implementor cannot possibly foresee all applicable use cases.

I could see maybe wrapping the payload in a struct rather than having it hang out in a generic tuple, but when it comes down to it, the users should be able to decide the data they are putting into Args, and KwArgs, because there is no telling what other clients they may be trying to interact with, and what expectations they might have for the payload structure.

pub struct Payload(Option<WampArgs>, Option<WampKwArgs>);

impl Payload {
// Implement interface to interact with data in the Args, and KwArgs
}

async fn echo(input: Payload) -> Result<Payload, WampError> {
}
oberstet commented 2 years ago

Payload transparency isn't in the spec for a reason.

Indeed that is one reason why WAMP is using negotiated serializers and router-side automatic re-serialization between clients using different serializers.

Nothing is "free", so this capability has a price:

  1. efficiency (1a. client-side and 1b. router-side)
  2. impossible to use with end-to-end encrypted application payload

Because of 1a and 2., "payload transparency mode" is an additional and optional feature that we might add to WAMP.

Which would make the price (1/2 above) paid disappear, or better: pay a different price;) which is:

.. which can only communicate with other clients using the same payload encoding.


this is a feature of Crossbar (A commercial product I might add)

crossbar - including this feature - is completely open-source these days https://github.com/crossbario/crossbar/blob/master/LICENSE


That doesn't really go to the original question which was about the user facing API.

This is a formal specification of a user facing WAMP based API:

https://github.com/wamp-proto/wamp-xbr/blob/master/work/schema/example1.fbs

Using this you can generate payload bindings for dozens of languages in clients. Obviously, the router does not need that schema, as it doesn't touch or even interpret the app payload.

I should probably add: if the router also had the FBS schema of the app payload traveling per URI, then the router could also auto-translate to dynamic serializers like JSON or CBOR from and to Flatbuffers.

This would only work when no end-to-end encryption is also used of course.