views:

688

answers:

3

I try to be as RESTful as possible in building applications but one thing that I'm never sure about is how to create a wizard-type work flow, be RESTful and concise.

Take, for example, a multi-page sign-up process.

Option 1: I can create a controller for each step and call new or edit when user gets to that step (or back to it). I end with step1_controller, step2_controller, etc...

Option 2: I can create a one controller and track where they are in the sign-up process with a param, session variable, state-machine - whatever. So I'd have signup_controller/step?id=1

The first option is strictly REST, but not very concise and ends with some number of extra controllers. The second options is more concise, but breaks REST, which I'm willing to do, but I don't take it lightly.

Is there a better option?

I'm working in ruby on rails, but this question applies to other MVC implementations, like ASP.NET MVC

+6  A: 

I'm actually less concerned about maintaining REST in a one-shot wizard. REST is most important, I think, with repeatable actions -- you want the url to be basically bookmarkable so that you get back the same view of the data regardless of when you go there. In a multi-step wizard you have dependencies that are going to break this perspective of REST anyway. My feeling is to have a single controller with potentially separate actions or using query parameters to indicate what step you are on. This is how I've structured my activation wizards (which require multiple steps) anyway.

tvanfosson
+1 for "REST is not always the right approach." Looking at some of the REST evangelism out there, you might think doing non-REST makes you a troglodyte.
Sarah Mei
Its a good point - where REST is useful and where it might not be. The biggest reason for me is that I find it really keeps thing clean and when you think you need to break REST, you should give it a long hard think.
Brian
Good answer. REST doesn't really make sense for a wizard. If step 2 is dependent on data from step 1, what happens if the user bookmarks step 2?
ebrown
+8  A: 

If you apply some DDD logic here, which compliments the "M" in MVC, the state of the UI (registration progress) belongs in the Application Layer, which can talk directly with the Domain and Infrastructure layers (Four layers: UI, Application, Domain, and Infrastructure). The concept of DDD makes you "think" about how to solve the solution in code first. Let's step through this...

It's the Progress Bar

The state you want to maintain here is the step or progress of the registration. So, my first step would be to document progress or "steps". Such as, Step 1: Get Username/Pass, Step 2: Get Email. In this case, I would apply logic to "move" the Model to the next step. Most likely with a NextStep() method on the a RegistrationService (RegistrationService.NextStep()).

Ah, but it belongs in the App layer

I would create a service in the Application layer called RegistrationService. I would place a method on here called NextStep(). But remember, the Domain would not hold the state of the model here. In this case, you'd want to focus the state at the Application layer. So in this case, NextStep() would act upon not a model object (since it is not part of the Domain responsibility), but instead the UI. So, you need something to retain the state of the Registration process.

Get away from the Domain Model, how about a ViewModel?

So now we know we have to retain the state of something in the UI. MVC allows for a concept called ViewModels (in ASP.NET MVC, not sure what RoR calls it). A ViewModel represents the model that will be displayed by the view, and/or partial views.

The ViewModel would be an excellent place to save the state of this object. Let's call it, RegistrationProgressViewModel() and stick a NextStep() method on it. This, of course, means that the Application layer would have to retain the location of the RegistrationProgressViewModel, and the APplication layer would change the internals of it based on NextStep actions. if it is complex, you may want to create a RegistrationProgressService() in the application layer and place the NextStep() within it to abstract your logic away.

How to pass the ViewModel around?

The final piece is how to track the state of that object. Since web applications are stateless, you have to retain control by some other means then the application. In this case, I would revert to either: 1) Serializing the ViewModel to the client and letting the client pass it back and forth, or 2) Keep a server-side copy of the ViewModel, and pass some type of identifier back and forth to the client and back.

This is a good example to think about, as I have not performed this myself yet. For #2, the most secure and insured way of saving the state of this ViewModel is to persist it to the via the Infrastructure layer (yes, the APp layer can talk directly to the Infrastructure layer). That seems a lot of work to me, for something that may die off and I would have partial registrations sitting in my DB.

But, #2 would keep the user's private info (username, password, email, CC #, etc) all on the server side and not pass it back and forth.

Finally, an answer!

So, after walking through it, we came up with:

  • Create a RegistrationProgressViewModel() in the Application layer.
  • Create a RegistrationProgressService(), with a NextStep(ViewModel vm) method, in the Application layer.
  • When NextStep() is executed, persist the ViewModel to the database through the Infrastructure layer.

This way, you never have to track what "step?id=2" on the View or UI itself, as the ViewModel gets updated and updated (Authenticated, Verified, persisted to DB) as you move forward.

So, your next concern would be to "move forward" in the UI. This is easily done with 1 controller, using step or named steps.

I apologize, but I am writing C# code below as that is my language.

public class RegistrationController : Controller
{
  // http://domain.com/register
  public ActionResult Index()
  {
    return View(new RegistrationProgressViewModel);
  }

  // http://domain.com/register
  // And this posts back to itself.  Note the setting 
  // of "CurrentStep" property on the model below.
  //
  public ActionResult Index(
      RegistrationProgressViewModel model)
  {

    // The logic in NextStep() here checks the
    // business rules around the ViewModel, verifies its
    // authenticity, if valid it increases the
    // ViewModel's "CurrentStep", and finally persists
    // the viewmodel to the DB through the Infrastructure
    // layer.
    //
    RegistrationProgressService.NextStep(model);

    switch (model.CurrentStep)
    {
      case 2:
        // wire up the View for Step2 here.
        ...
        return View(model);
      case 3:
        // wire up the View for Step3 here.
        ...
        return View(model);
      case 4:
        // wire up the View for Step4 here.
        ...
        return View(model);
      default:
        // return to first page
        ...
        return View(model);
    }
  }
}

You'll notice that this abstracts the "Business Logic" of verifying the model's internal state into the RegistrationProcessService.NextStep() method.

Good exercise. :)

In the end, your "RESTful" url is a nice and clean POST to: /register, which expects a ViewModel with specific properties filled out. If the ViewModel is not valid, /register does not advance to the next step.

eduncan911
Thanks for posting. Its a good treatment. Its basically option 2. Its not RESTful urls that I care about, but actually sticking to the 7 defined verbs.
Brian
Not the "fastest" way of doing it, but my answer was to use business rules/logic to evaluate the object (viewModel) for authentication before "bumping the CurrentStep" up to 2, or 3, etc. But having business rules contained within could control 'what step' your on.
eduncan911
So the view models would have to be contained within the application layer correct? Otherwise it would create a circular dependency with the UI layer.
Vince
@Vince: For this answer, yes they would be in the Application layer. For some projects, I've created my code in App_Code, and noted that that directory is my "application layer". Other projects, I've created a ProjectName.UI for my applicatino layer, and ProjectName.UI.Website for my UI, along with my ProjectName.UI.WcfService, etc. Note that they both share the ProjectName.UI. But all in all, this all mute as I have moved to Entity Framework 4 with POCO classes. I generate another set of POCOs now for my WCF layers, isolating my domain - but yet using the same T4 from the same container!
eduncan911
A: 

Although the answers are very nice options indeed I still use the approach given in:

Shoulders of Giants | A RESTful Wizard Using ASP.Net MVC.

It is definitely worth a look. Though I must say, the answers given here make me think to remake this wizard when I have the time.

bastijn