Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I create a concise and RESTful wizard under MVC?

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

like image 856
Brian Avatar asked Apr 03 '09 19:04

Brian


1 Answers

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.

like image 52
eduncan911 Avatar answered Sep 28 '22 01:09

eduncan911