Search code examples
botframework

WaterfallStep design vs. SOLID principales in Bot Framework v4


After moving over from Bot Framework v3 and studying the docs/samples, I'm still not understanding why the WaterfallStep dialog handling in v4 was implemented in such a way.

Why was it chosen to process the result of the previous step in the next step within a waterfall?

Given a waterfall with 3 Steps PromptName, PromptAge and PromptLocation, I see the following:

  • Method naming: Given the 2nd and 3rd prompt, method naming gets unclear. Naturally we would do AskForAge() or AskForLocation() but this is misleading due to the next point
  • SOLID Principals: Isn't "single responsibility principal" violated as we do two things in each step? Storing the previous response while asking for the next one in the same method, which ultimately leads in method names like AskForLocationAndStoreAge()
  • Code duplication: Due to the fact that each step assumes concrete input from its previous step, it can't be reused easily nor can order be changed. Even the simplest samples are hard to read.

I'm looking for some clarification on why the design was chosen this way or what I have missed in the concept.


Solution

  • This question seems largely opinion-based and therefore I don't know that it is appropriate for Stack Overflow. It turns out there is actually a good place to ask these kinds of questions, and it's the BotBuilder GitHub repo. I will attempt to answer it all the same.

    Why was it chosen to process the result of the previous step in the next step within a waterfall?

    This has to do with how bot conversations work. In its most basic form, without any dialogs or state or anything, a bot works by answering questions or otherwise responding to messages from the user. That is to say, a bot program's entire lifespan is to receive one message, process it, and provide a response while the message it received is still "active" in some sense. Then every following message the bot receives from the user is processed in the same way as though the first message was never received, like the message is being processed by an entirely new instance of the program with no memory of previous messages.

    To make bot conversations feel more continuous, bot state and dialogs are needed. With dialogs and specifically prompt dialogs, the bot is now able to ask the user a question rather than just answer questions the user asked. In order to do this, the bot needs to store information about the conversation somewhere so that when the next message is received from the user the new "instance" of the bot program will know that the message from the user should be interpreted as a response to the question that the previous instance of the program asked. It's like leaving a note for someone working the next shift to let them know about something that happened during the previous shift that they need to follow up on.

    Knowing all this, it seems only natural to process the result of the previous step in the next step within a waterfall because of the nature of conversations and dialogs. In a waterfall dialog containing prompts, the bot will receive messages pertaining to the last message the bot sent. So it needs to process the result of the previous step in the next step. It also needs to respond to the message, and in a waterfall that often means asking another question.

    Isn't "single responsibility principal" violated as we do two things in each step? Storing the previous response while asking for the next one in the same method, which ultimately leads in method names like AskForLocationAndStoreAge()

    As I understand it, the single responsibility principle refers to classes and not methods. If it does refer to methods, then that principle may well be violated or bent in some way in this case. However, it doesn't have to be. You are free to make multiple methods to handle each step, or even to make multiple steps to handle each message. If you wanted, your waterfall could contain a step that processes the result of the previous prompt and then continues on into the next step which makes a new prompt.

    Due to the fact that each step assumes concrete input from its previous step, it can't be reused easily nor can order be changed. Even the simplest samples are hard to read.

    You ultimately have control over how the input is validated/interpreted so it can be as concrete as you want. The reusability of a dialog or waterfall step has everything to do with how similar the different things you want to do are, the same as in any area of programming. If the samples are hard to read, I recommend raising issues with those samples. Of course, you can still raise an issue with the design of the SDK itself in the appropriate repo, but please do consider including suggestions for the way you think it should be instead.