Wednesday, May 4, 2011

Building DCI Context with ASP.NET MVC

In this post I'll address one of the questions that I tend to get when talking about DCI: How is the DCI context built at runtime?

To that end lets expand a bit on the DCI with ASP.NET MVC sample that I showed in an earlier post. The focus of that post was how to fit ASP.NET MVC and DCI together in a nice cohesive architecture. In this post I will zoom in on how the controller can build up the DCI context object in a conversation with the user.

The example I'm using is still that of a very simplified online bank, where the user can trasfer money between accounts. The core DCI parts of this example is explained in this post, and the connection with ASP.NET MVC in this post. Whereas the sample in the last post simply asked the user to input all the information about the transfer in one screen, this version asks for the information one thing at a time in a wizard like fashion. Whether this is good UX, is besides the point of this post. I just want to use an example where the context is build in a series of interactions with the user.

Interaction Between User and Code
First off the user is asked to choose a source account on this simple (ugly) page:



Serving up this page is done by this rather straight forward action method:

    1  public ActionResult SelectSource()
    2  {
    3      Session["Context"] = new TransferMoneyContext();
    4      return View(new SelectAccountVm
    5                  {
    6                      SelectedAccountId = string.Empty,
    7                      Accounts = accountRepo.Accounts
    8                  });
    9  }
   10 

Worth noticing though, is that a MoneyTransferContext object is created and stored in the session.
When the user hits the Next button this action is hit:

   55 [HttpPost]
   56 public ActionResult SelectSource(SelectAccountVm model)
   57 {
   58     var ctx = Session["Context"] as TransferMoneyContext;
   59     ctx.Source = 
   60       accountRepo.GetById(int.Parse(model.SelectedAccountId)) as TransferMoneySource;
   61     return View("SelectDestination", 
   62                 new SelectAccountVm
   63                 {
   64                     SelectedAccountId = string.Empty,
   65                     Accounts = accountRepo.Accounts
   66                 });
   67 }
   68 

This code updates the context with the chosen source account, and serves the next view in the interaction, where the user is asked for a destination account:



When the user hits this Next button this action is hit:

   69 [HttpPost]
   70 public ActionResult SelectDestination(SelectAccountVm model)
   71 {
   72     var ctx = Session["Context"] as TransferMoneyContext;
   73     ctx.Sink = 
   74       accountRepo.GetById(int.Parse(model.SelectedAccountId)) as TransferMoneySink;
   75     return View("SelectAmount", new SelectAmountVm());
   76 }
   77 

which does almost the same as the previous action, except it puts the chosen account in the Sink property on the context, and then shows this page:



The post from this page does a bit more work. First it updates the context with the amount given by the user. At this point the MoneyTransferContext is complete and ready to be exectued. Finally a result page is shown:

   78 [HttpPost]
   79 public ActionResult SelectAmount(SelectAmountVm model)
   80 {
   81     var ctx = Session["Context"] as TransferMoneyContext;
   82     ctx.Amount = model.SelectedAmount;
   83 
   84     ctx.Execute();
   85 
   86     return ResultPage(ctx);
   87 }
   88 

and the result page is:



Summing Up

To sum up: To build up a DCI context in ASP.NET MVC simply store a context object in the session context when a use case is started and execute it when it makes sense from a functionality perspective. This way the controller code is kept very simple, concentrating only on building up the context through interaction with the user, and the details of the use case execution is - from the perspective of the cotroller - hidden behind the context. Furthermore because of the way the controller builds the context the context object only knowns the domain objects through the roles they have been assigned by the controller - or in fact the user - allowing for the full strenght of DCI to play out there.

You can find the complete code from this post on GitHub.

12 comments:

  1. How / where would you add a data context to this sample for an actual implementation? In the TransferMoneyContext? In the Trait?

    ReplyDelete
  2. @DeeSee: I'm not sure I follow our question. If you are talking about where to put persistence code, then I would hide that behind the account repository, such that neither the controller, the domain object, the context or the roles knows about the, say NHibernate. Alternatively, if the domain analysis leads you their there may be a persister role.

    ReplyDelete
  3. @Christian: Sorry, I should have been more clear on that question.

    I do have another question. With regards to the Account abstract class, you have methods for Withdraw and Deposit; however, a lot of business functionality would be within these methods. Shouldn't these belong elsewhere in a proper implementation DCI?

    Based on what I understand, the domain objects should contain "dumb" functionality, such as "IncreaseBalance" and "DecreaseBalance". Shouldn't algorithms such as "Withdraw" and "Deposit" containing Interactions with transaction logs, ledgers, and other Objects ought to be held within other Contexts? I believe Cope describes these as Habits.

    I'm just trying to understand how all of this comes together. When you have a use case (Context) which may contain multiple Habits; How can we tie all of this together in a great ASP.NET MVC Sample like you've started with so that the community can see a full picture of a complete implementation of the DCI best practice?

    ReplyDelete
  4. Really nice to see DCI put to use with ASP.NET MVC.

    I was wondering about the limitations of using a session context to store the DCI context. As far as I can tell this would only work for use cases that are short lived and that only involves a single user or at least only a single client. In the example presented here this is probably not an issue.

    But there is also the fact that the TransferMoney web resource is made stateful which tends not to work very well with the navigation in the browser.

    Personally I would rather use an unique resource for each context something like /MoneyTransfer/{transfer id}/SelectDestination/ and then use the transfer id for retrieving the context from either a database or some cache. This approach would require some more work but would in my opinion be a cleaner solution.
    An updated example that uses this approach can be found here however I did not bother to change the default routing of MVC.

    ReplyDelete
  5. @Kim ik2: Thanks for your feedback.

    FWIW the ASP.NET session can be configured to be stored in a number different places, including a custom data provider (see http://msdn.microsoft.com/en-us/library/ms178586.aspx), so this approach also support storing session state in a database.

    I think you would be able to go even further in the REST direction by encoding the whole context into the URI - something like: POST - /MoneyTransfer/{transfer id\}/SourceAccount/{source account id}/DestinationAccount/{destination account id}?amount=...

    ReplyDelete
  6. @DeeSee: Yes, I think probably the withdraw and deposit methods entail to much business logic for the dumb data classes. How that should be handled exactly depends on the domain analysis for the specific application.

    One possibility - I guess - is that transaction logs and ledgers are domain object in their own right, and as such are objects that play their own roles in the transfer interaction.

    Another possibility is to use DCI recursively, such that an account is not really a data object, but a context with transaction log and ledger object inside, that play roles in the withdraw and deposit interaction. The TransferMoneyContext doesn't not that the account aren't straight data object, but another context.

    Hope that makes sense to you.

    ReplyDelete
  7. I was not concerned about persistence of the DCI context but about tying it to a session context due to the reasons mentioned in my last post.

    Thank you for your example it is nice to see some alternative solutions.
    I assume that we a still are discussing a wizard like application. In that case the POST in your example would be the result of the last step. The first step would be something like the source account id is POST'ed to /MoneyTransfer and a redirect to /MoneyTransfer/{transfer id}/SourceAccount/{source account id}/ is returned. However, I'm not sure what transfer id then would refer to or why amount is encoded as a query string in your example.

    Encoding the context into the URI would remove the need to store a context object on the server, hence would require less coding than my example. But I will have to disagree with you when saying that this is “further in the REST direction”. Maybe you could tell me what REST constraint you are thinking about?

    The only limitation I can think of when encoding the context into the URI is that it will be impractical with a large number of input fields or big input fields.

    ReplyDelete
  8. @Kim ik2: Ok, I see your point about storing the context in the session. The usual limitations of session data apply: The server side state can hurt scalability, not appropriate for long running scenarios and so on. I agree with that. I guess in a situation donimated by long running use cases a thick client may be more appropriate. But that's a case by case judgement call.

    On combining DCI and REST Rikard Öberg's work is probably of interest, like: http://www.jroller.com/rickard/entry/implementing_dci_in_qi4j.

    ReplyDelete
  9. Hi,
    First of all thanks for your nice post and really useful information,
    I have a question, actually I have same question as you, but in windows applications,
    the question is : How is the DCI context built at run-time in windows applications?
    I wanna make context based on selection that user will make through windows forms (selection for source, sink and amount).
    Do you have any Idea at that matter?

    ReplyDelete
  10. @Abo: Your welcome.
    How to setup the context in a winforms app? Well a client application can easily be statefull, so just instantiate a context object when the use case starts, and hold on to while the user goes through whatever steps are needed meanwhile building out the context. You see?

    ReplyDelete
  11. Actually I do that, for example the user select sender to be "accountX" and receiver be "accountY" and transferring amount is 500 , now if I have lots of account object, after this state I wanna have the context object based on this selection.
    but as you know I can't make expression evaluation as code in C# (at runtime) .
    if there was something like EVAL() function like Flash or other language maybe I can do something like this :
    EVAL ("new TransferMoneyContext("+ textbox1.text +","+ textbox2.text+", 500).Execute()");
    I search for the solution and find some solution to simulate that eval function ,
    I wanna know isn't there any other clever idea ?

    ReplyDelete
  12. @Abo: I would assume the widgets for selecting account are comboboxes and that you can map from the selected index back to an account object. Right? If so you can just new up the context passing in those accounts. Alternatively new up the context before the selection are done and set properties on the context. I don't see where EVAL comes into play, honestly.

    ReplyDelete