-
Notifications
You must be signed in to change notification settings - Fork 64
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
[Proposal] Dispatching to custom remote functions based on the message type #3670
Comments
We can get it to work even without Lets make |
If we generate a client using the generated AsyncAPI how would it look like for the below? @websocket:ServiceConfig {
descriminator: {
key: "event",
allowedValues: ["ping", "heartbeat", "subscribe"]
}
}
service / on new websocket:Listener(9090) {
resource function get .() returns websocket:Service {
return new MyService();
}
}
service class MyService {
*websocket:Service;
remote function onPing(Ping message) returns Pong {
return {'type: WS_PONG};
}
remote function onSubscribe(Subscribe message) returns SubscriptionStatus {
return {id: "4", 'type: WS_SUBSCRIBE};
}
remote function onHeartbeat(Hearbeat message) {
io:println(message);
}
} |
I prefer |
Updated the proposal by removing the |
How about a client code generated as follows? public function main() {
// Initiate an asyncapi WebSocket client by passing the server to connect. Individual clients will be generated for each `channel`.
// This will connect to the given server (this client is generated for the `root` channel) and subscribe to that.
// Name of the client will be `AsyncAPI title` + `<channel>` + `Client` ignoring the spaces, underscores etc.
// Note:- We might need to discuss and come up with a proper naming convention.
KrakenWebSocketApiRootClient rootClient = new(KrakenWebSocketApiClient.Server_Public);
// Publish messages to the server. The message types are retrieved from the response types of the `publish`
Ping pingMessage = {'type: PING});
check rootClient->ping(pingMessage);
Subscribe subscribeMessage = {'type: SUBSCRIBE, "pair": ["XBT/USD", "XBT/EUR"], "subscription": {"name": "ticker"}};
check rootClient->subscribe(subscribeMessage);
UnSubscribe unSubscribeMessage = {'type: UNSUBSCRIBE, "pair": ["XBT/USD", "XBT/EUR"]};
check rootClient->unsubscribe(unSubscribeMessage);
// Then the client can listen to the server publishing messages. The message types are retrieved from the response types
// `subscribe` operation.
// Can be done in a separate strand in a loop.
Pong|Heartbeat|SystemStatus|SubscriptionStatus message = check rootClient->listen();
} |
Looks good. Except we don't need public function main() {
// Initiate an asyncapi WebSocket client by passing the server to connect. Individual clients will be generated for each `channel`.
// This will connect to the given server (this client is generated for the `root` channel) and subscribe to that.
// Name of the client will be `AsyncAPI title` + `<channel>` + `Client` ignoring the spaces, underscores etc.
// Note:- We might need to discuss and come up with a proper naming convention.
KrakenWebSocketApiRootClient rootClient = new(KrakenWebSocketApiClient.Server_Public);
worker A {
// Publish messages to the server. The message types are retrieved from the response types of the `publish`
while true {
Ping pingMessage = {'type: PING});
Pong pongMessage = rootClient->ping(pingMessage);
}
}
worker B {
Subscribe subscribeMessage = {'type: SUBSCRIBE, "pair": ["XBT/USD", "XBT/EUR"], "subscription": {"name": "ticker"}};
stream<SubscriptionStatus> statusStream = check rootClient->subscribe(subscribeMessage);
// loop stream
}
UnSubscribe unSubscribeMessage = {'type: UNSUBSCRIBE, "pair": ["XBT/USD", "XBT/EUR"]};
check rootClient->unsubscribe(unSubscribeMessage);
} |
Reopened the proposal as it does not discuss about error handling of each custom remote method. At the moment we have @websocket:ServiceConfig {
dispatcherKey: "event"
}
service / on new websocket:Listener(9090) {
resource function get .() returns websocket:Service {
return new MyService();
}
}
service class MyService {
*websocket:Service;
remote function onPing(Ping message) returns Pong {
return {'type: WS_PONG};
}
remote function onPingError(error err) {
return io:println(message);
}
remote function onSubscribe(Subscribe message) returns SubscriptionStatus {
return {id: "4", 'type: WS_SUBSCRIBE};
}
remote function onSubscribeError(error err) returns ErrorMessage {
return {id: "4", 'type: WS_SUBSCRIBE};
}
remote function onHeartbeat(Hearbeat message) {
io:println(message);
}
remote function onHeartbeatError(error err) {
io:println(message);
}
} |
In the case of there is no matching custom |
In addition to |
|
Summary
Dispatching messages to custom remote functions based on the message type(declared by a field in the received message) with the end goal of generating meaningful Async APIs.
Goals
Generating meaningful AsyncAPIs and improving the readability of the code.
Motivation
With AsyncAPI gaining its's popularity with increased usage of event-driven microservices it is worthwhile to think of ways to generate AsyncAPI specifications using WebSocket service code. The motivation is to improve the service code to be more understandable to retrieve the maximum details to generate meaningful AsyncAPIs and to improve the readability of the code.
Description
In most real-world use cases, the WebSocket protocol will be used along with a sub-protocol. Most of the time those sub-protocols are differentiated from a dedicated field in the message and it contains the type of the message. For example: In Kraken API the type of the message is identified by the
event
field.Another example is
GraphQL over WebSocket Protocol
The WebSocket sub-protocol for the above specification is:
graphql-transport-ws
. And the type of the message can be identified by the value of the field namedtype
of the message.As of now, when using the Ballerina WebSocket service, all these messages are dispatched to the generic
onMessage
remote function. When the user writes a logic based on the received message, all have to be handled inside theonMessage
using an if/else ladder or similar. This reduces the readability of the code.And also, if we want to generate an AsyncAPI specification by referring to the service code, it is not possible to capture all the details like the response message for a particular type of message.
Ex:
Following is a part of the Kraken AsyncAPI specification describing the types of messages and their responses.
In the above AsyncAPI specification, it has the messages given as
ping
andunsubscribe
. Their response messages are given by the fieldx-response
.If this part is written using existing WebSocket service functionalities, it would look like the following.
Therefore, if we have all the messages dispatched to a single
onMessage
remote function, it is difficult to differentiate the response forping
message and the response message forunsubscribe
operation.As a solution for this, the idea is to have custom remote functions based on the message type within the WebSocket service. For example, if the message is
{"type": "ping"}
it will get dispatched toonPing
remote function. Similarly,Dispatching rules
The
dispatcher
is used to identify the event type of the incoming message by its value. The default value is'type
.Ex:
incoming message =
{"event": "ping"}
dispatcherKey = "event"
event/message type = "ping"
dispatching to remote function = "onPing"
onMessage
remote function if it is implemented. Or else it will get ignored.An example code for Kraken API with the proposed changes.
The text was updated successfully, but these errors were encountered: