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: trimmed down context object #66
Comments
This would address issue #59 |
I'm a big fan of the overall direction towards simplicity. If I understand correctly, you're essentially proposing to turn BotContext into a CRUD + Flush container. The only genuine issue I have is that we're getting awfully close to being formal "State Management". By that, I mean the the combination of adding responses, manipulating existing responses, then syncing that state with a remote user. We're walking up to that state management issue via CRUD operations, and not through a more deliberate approach. In that regard, I really like the Action/Reducer pattern that Redux uses. I also wonder if we should treat sending more akin to replication, where the local state of the bot is replicated to the user wherever they might be. Now, at the risk of undermining my own thinking, if we implement a strict CRUD layer here in the BotContext, I don't think anything prevents us from enriching it with another layer down the road. At the Nitpicky level, I have a few minor concerns:
That pattern should be semantically simple.
//Chris |
So to be clear... The update and delete methods I've added are to better model the v3 protocol that the Bot Framework supports. If this looks like a more formal "State Management" that's because the v3 protocol supports this level of CRUD operation. You'll notice that I'm specifically NOT modeling read because that's not consistently supported across all channels. With this proposed interface you could send a typing indicator directly using This layer is intended to be a very raw layer not unlike HTTP Request and Response. There's very little that's intuitive about those interfaces either. You basically need to read the documentation to understand the ins and outs of working at this raw of a layer. The idea is that you will add middleware plugins that extend the context object with members that are semantically more meaningful. There's nothing hear that even tells you what types of activities you can send. You need to add a piece of middleware that adds The addition of the I'm not opposed to renaming |
There's actually a further simplification that can be made here... I think we can remove the batching aspects of export class BatchOperations implements Middleware {
public contextCreated(context: BotContext, next: () => Promise<void>): Promise<void> {
let responses: Partial<Activity>[] = [];
context.batch = {
reply: (text: string) => {
responses.push({ type: 'message', text: text });
},
typing: () => {
responses.push({ type: 'typing' });
},
flush: () => {
const flushed = responses;
responses = [];
return context.postActivity(flushed);
}
}
return next()
.then(() => context.batch.flush());
}
} This will actually simplify our internal middleware implementation because we no longer need to explicitly flush() the response queue as part of our pipeline processing and the lifetime of the context object becomes cleanly tied to the lifetime of the |
Updating this proposal to reflect the latest thinking spanning several other discussion threads: export interface TurnContext {
/** The Bot object for this context. */
bot: Bot;
/** The received activity. */
request: Activity;
/** If `true` at least one response has been sent for the current turn of conversation. */
readonly responded: boolean;
/** Gets a value previously cached on the context. */
get<T = any>(key: string): T;
/** Returns `true` if [set()](#set) has been called for a key. The cached value may be `undefined`. */
has(key: string): boolean;
/** Caches a value for the lifetime of the current turn. */
set(key: string, value: any): this;
/** Sends a set of activities to the user. */
sendActivities(activities: Partial<Activity>[]): Promise<ResourceResponse[]>;
/** Replaces an existing activity. */
updateActivity(activity: Partial<Activity>): Promise<void>;
/** Deletes an existing activity. */
deleteActivity(id: string): Promise<void>;
/** Registers a handler to be notified of and potentially intercept the sending of activities. */
onSendActivities(handler: (activities: Partial<Activity>[], next: () => Promise<ResourceResponse[]>) => Promise<ResourceResponse[]>): this;
/** Registers a handler to be notified of and potentially intercept an activity being updated. */
onUpdateActivity(handler: (activity: Partial<Activity>, next: () => Promise<void>) => Promise<void>): this;
/** Registers a handler to be notified of and potentially intercept an activity being deleted. */
onDeleteActivity(handler: (id: string, next: () => Promise<void>) => Promise<void>): this;
} Changes of Note
|
One question about the proposed interface is that C# programmers tend to like really well spelled out methods like |
I don't know how to respond to this DCR. It seems like a catch-all summarizing a variety of proposals being discussed in other issues, although you are also discussing some of them here. |
I know you don't like the on handlers. are their other specific line items you have issues with? I just started work on this last night so now is a great time to comment. I'll be publishing an m2 branch sometime today and I would expect all of these various changes to just collect there until after the Milan hack is over. I implemented the above changes to the context object last night and so far they feel pretty good implementation wise. But nothing is set in stone of course. |
BTW... The vast majority of DCR discussions is happening over in the .NET repo. I'm actually lagging behind them for once... lol |
I propose putting the cache functionality under a name JavaScriptif (context.cache.has("mykey")) {
value = context.cache.get("myKey");
context.cache.delete("myKey");
} C#if (context.Cache.ContainsKey("myKey")) {
value = context.Cache["myKey"};
context.Cache.Remove("myKey");
} To me that strikes the right balance between common naming across SDKs and language-specific idiom. It also gives context to the |
This work is done. |
One of the biggest things we've talked about for M2 is stripping down the core botbuilder package to its essence. Thought I'd start with proposing a trimmed down context object:
see updated shape proposal #66 (comment)
Let me go through and try to justify each part on the context object...
bot
This is really to provide access to the adapter as we've found you often need access to this. In particular, Middleware for MS Teams needs access to the underlying adapter to get an access token for calling other REST services they need to call.
request
Obviously provides access to the received activity.
responded
This is a flag that indicates whether a reply has been sent for the current turn. This is potentially useful from within middleware to know if you should run logic on the trailing edge of a request. For instance, QnA Maker can look at this flag on the trailing edge of the request and only call the service in a fallback case should no other logic in the bot respond.
responses and flushResponses()
This provides a means of queuing up outbound responses. Bots often end up sending more than one message to the user for the same turn and given that your bots logic might be spread across multiple components it's possible for several components to queue up an outgoing response on the same turn. Since the bots state is written out every time activities are sent it's best to queue up all of the responses so that you can collapse state updates down to a single write per turn.
The
flushResponses()
method gives you a way of manually flushing the response queue should you need to and it's also the method called when the context is about to be destroyed. All this method would do is call the newpostActivity()
method and then delete the flushed responses on completion.postActivity(), updateActivity(), and deleteActivity()
These are new methods I'm proposing to explicitly perform CRUD operations against the underlying adapter in a way that goes through middleware. We recently realized that while we have a
postActivity
middleware pipe we're missing delete and update pipes. Currently updates and deletes for existing activities need to happen using the adapter directly which bypasses middleware meaning that updates & deletes won't be logged.I have another proposal coming which will recommend we add new
updateActivity
anddeleteActivity
pipes to middleware. What's nice about these 3 new methods I'm proposing is that they will a) provide a direct means of posting activities (something we've been asked for) and b) they will align name wise with the names of the middleware pipes that will get run when each method is called. Should make it clearer what triggers the running of those pipes.The text was updated successfully, but these errors were encountered: