[nodejs][question] Events and middleware are competing
See original GitHub issueIt seems to me that events are competing with middleware.
- If I have middleware, it is called for every message. It seems that a type that represents the event could be passed in here to make a more consistent path for responding to and route based on events.
Example (Suggestion)
bot.use(function (session, next) {
// type no longer just reports 'message'
if(session.message.type === 'deleteUserData') {
// Delete User Data
session.replaceDialog('/dataDeleted');
}
});
- The listening for events is cleaner and may be more performant (not waiting for everything before reacting to a event being emitted), but right now the interface and options after a event is triggered is extremely limited. Personally, reacting to the listening of events is cleaner than a conditional chain within the middleware.
Example (Suggestion)
// Listener should take a session instead of a message object
bot.on('DeleteUserData', function(session) {
// Delete User Data
session.replaceDialog('/dataDeleted');
});
Instead right now you get a message object, which can not replace the dialog, and does not let you end a dialog or control the flow correctly.
Question - What is the correct way to handle these events? For example the firstRun middleware example to me should be done with the BotAddedToConversation
event instead of a middleware.
Ideally, the message
event should replace the need for the use
method on the BotConnectorBot
and should be triggered with every message.
// Triggers First Run
bot.on('BotAddedToConversation', function(session) {
if (!session.userData.firstRun) {
session.userData.firstRun = true;
session.beginDialog('/firstRun');
}
});
// Replaces Middleware that is enacted for all messages. This event is currently not working. (or should this be the reply event)
bot.on('message', function(session) {
if(process.env.LOGGING) {
console.log(session.type + ":" + session.message.text);
}
});
In the end, no matter which way we go, I think it makes sense to either remove the middleware and have events trigger the actions, or keep the middleware, and allow the event types to be addressed within the middleware function. The change should allow session to be the object that is handling the message flow regardless of direction.
There is room to keep both though. Many node libraries (including built in ones for node) support both callback (all data is returned and takes up memory) and event listners (can be handled at time of event, takes up less memory, does place items on event stack).
Example - give me all rows in database query results before I act (callback) vs give me each row and act against each row (event).
Given the amount of data, the event model seems to make more sense, provided race conditions can be avoided.
Issue Analytics
- State:
- Created 7 years ago
- Comments:5 (3 by maintainers)
Great discussion… We were just talking about this internally the other day. So first. Why can’t you handle DeleteUserData from middleware.
Currently, middleware only supports something I’ve been calling “Dialog Middleware”. When a message is received its first mapped into a Session object and then run through middleware before being dispatched to the active dialog for processing. Currently on messages of type “message” go through this flow. Why is that? Because they’re the only types of messages that allow the bot to have a full conversation with the user. All of the other message types allow only a single reply to be sent to the user at best. So if the bot is added to a conversation for instance you can send a single greeting message but you can’t start a full conversation. Since you can’t take full advantage of bot builders dialog system we currently don’t map these messages into a Session object. That sort of sucks because I can currently give you a way to reply with a static message (you set it as an option for the bot) but we don’t have a way to reply with a dynamic message.
One idea we had for solving this was to create a ReplyOnlySession class which would let you call session.send() like your used to but you can’t call session.beginDialog() or anything else. We would love feedback on that idea.
The other issue is that with certain events like DeleteUserData you can’t send a reply at all so it seems like firing an event is the right way to go.
Back to the topic of just middleware, just events, or both. Middleware has a number of advantages over events that I wouldn’t want to lose. For one the calling order is way more predictable than with events, they’re called in the exact order they’re mounted. Second you can do async operations in middleware which you can’t do with events. Events may be run in sequence but conceptually they all run in parallel. Middleware runs truly in sequence. Third, and probably most important, middleware can intercept execution. That’s super useful in BotBuilder as it lets you redirect to other dialogs based on some condition. You couldn’t do that very cleanly in an eventing model. So the real question for me is can you get rid of events and go with just middleware? At this point I’d say I don’t think so but I do think we can make improvements to BotBuilders middleware support so maybe.
Here’s what I have planned for middleware in a future release. I want to add to the current Dialog Middleware a new type of middleware called Message Middleware. Where Dialog Middleware is about processing messages before they’re routed to a dialog, Message Middleware is about processing incoming and outgoing messages. Here’s how it would look code wise:
When a message is first received we’d run it through all of the received functions. Then for messages of type “message” we’d map the message to a Session object and run it through all of the dialog functions. For out going messages we’d run the messages through all of the “sending” functions before actually sending the message. The primary idea here is to let you plugin things like machine translation but it could also potentially eliminate the need for events. Or at least provide a pure middleware based approach for those that desire it.
The thing that keeps me from thinking you could get rid of events all together is the “error” event. This event is special cased by node and its the primary way you should (and that we do) bubble errors up the stack. Calling
bot.on('error', function (err) {});
today lets you register a global handler for any errors that occur within your bot. I think we’d always have to support that.Again… Thanks for starting this discussion.
Hi @mmathias -
You can view the full session object by adding a
console.log(session);
statement to any dialog route handler. Or more specifically, you can log the userData property withconsole.log(session.userData);
. One thing to note, the JSON object will not be logged in the Bot Emulator console, instead it will appear in the terminal where you are running the bot.js NodeJS file.Check out this code example for a working demo.