Let business scenarios be reflected in your code

I’m working with a project where I use something I call “scenarios”. The idea is that each scenario in the businessmodel is represented by three classes.

Command – Contains the inputdata needed to perform the Scenario. Is optional since the operation might not need any input parameters.

Scenario – Validates and Executes the Command and is where the logic is kept. It is not ment to cross boundraries. It also provides an eventmodel, publishing events for Validated, Executed etc.

Result – Contains the command (input data), any exception and violations generated by the Validation- or Execution step in the scenario, as well as scenariospecific outputdata.

The reason for why I wan’t this is that I want each scenario easily detected in my coding-model. I want to be able to quickly look in the solutionexplorer in the Scenarios namespace, and directly see what the system targets in the domain. By looking at the Command- and the Result-classes I can also see what the scenario accomplishes. By isolating each scenario-flow-logic to one single class, I hopefully achieve a more cohesive and readable class.

An example

The example is not drawn from the business model since it’s about signing in to the system, hence it’s sort of an indirect business scenario, since you can’t run the “Place order scenario” if you’r not an authorized user.

The shown example is about logging in to the system and is using OpenId in an Asp.Net Mvc 2 application. This concludes two steps. First a request has to be initiated to an OpenId-provider. Second, we need to handle the callback request from the provider, hence the enum LogOnSteps. (I guess it would have been more clear to put these in two different scenarios.)

[Serializable]
public enum LogOnSteps
{
    Initiate,
    Finalize
}

The Command contains properties that are required for Step 1 as well as a flag indicating where we are in the process so that the validation and execution in the scenario-class nows what to do.

[Serializable]
public class LogOnCommand
{
    public LogOnSteps Step { get; set; }

    public string OpenId { get; set; }

    public string ReturnToUrl { get; set; }

    public LogOnCommand(LogOnSteps step)
    {
        Step = step;
    }
}

The implemented scenario extends a baseclass so that boilerplate code for catching exceptions etc. is kept away. The scenario is defined in an interface and is accessed via an IoC-container.

public class LogOnScenario : Scenario<LogOnCommand, LogOnResult>, ILogOnScenario
{
    public IOpenIdAuthenticationService AuthenticationService { protected get; set; }

    public IMembershipService MembershipService { protected get; set; }

    public LogOnScenario(
        IOpenIdAuthenticationService authenticationService,
        IMembershipService membershipService)
    {
        AuthenticationService = authenticationService;
        MembershipService = membershipService;
    }

    protected override IViolations OnValidate(LogOnCommand command)
    {
        var violations = new Violations();

        if (command.Step == LogOnSteps.Initiate)
        {
            violations.AddIf(command.OpenId.IsNullOrEmpty(), new Violation(
                ResourceStrings.LogOnCommand_OpenId_IsRequired, CommandName, "OpenId"));
            
            violations.AddIf(command.ReturnToUrl.IsNullOrEmpty(), new Violation(
                ResourceStrings.LogOnCommand_ReturnToUrl_IsRequired, CommandName, "ReturnToUrl"));
        }

        return violations;
    }

    protected override LogOnResult OnExecute(LogOnCommand command)
    {
        return command.Step == LogOnSteps.Initiate
            ? HandleInitiateStep(command)
            : HandleFinalizeStep(command);
    }

    private LogOnResult HandleInitiateStep(LogOnCommand command)
    {
        var logOnResult = new LogOnResult
        {
            Command = command,
            MvcActionResult = AuthenticationService
                                .InitiateMvcAuthentication(command.OpenId, command.ReturnToUrl)
        };

        return logOnResult;
    }

    private LogOnResult HandleFinalizeStep(LogOnCommand command)
    {
        var logOnResult = new LogOnResult { Command = command };
        var openIdAuthentication = AuthenticationService.AuthenticateRequest();

        if (openIdAuthentication.Violations.IsNotEmpty)
        {
            logOnResult.Violations.Add(openIdAuthentication.Violations);

            return logOnResult;
        }

        var mapper = IoCContainer.Instance.GetInstance<IObjectMapper>();
        var identity = mapper.Map<OpenIdAuthentication, Identity>(openIdAuthentication);
            
        logOnResult.UserSession = new UserSession(identity);
        logOnResult.Member = MembershipService.GetByOpenId(identity.OpenId);

        return logOnResult;
    }
}

Since we want every scenario to return any caught exception and violations, we have a base-class that the ScenarioResults can extend, which contains members for this.

[Serializable]
public class LogOnResult : ScenarioResult
{
    public LogOnCommand Command { get; set; }

    public ActionResult MvcActionResult { get; set; }

    public IUserSession UserSession { get; set; }

    public Member Member { get; set; }
}

Now if we take a look at some consumer code, I think it gets a bit easier to understand, and the application controller only handles logic for controlling which view and viewdata should be presented.

Step 1 – Initiate the scenario

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult LogOn(LogOnViewModel viewModel)
{
    if (!ModelState.IsValid)
        return View(viewModel);

    var returnToUrl = Url.AbsoluteUrlFromAction("AuthenticateOpenIdRequest");
    var command = new LogOnCommand(LogOnSteps.Initiate)
                      {
                          OpenId = viewModel.OpenId, 
                          ReturnToUrl = returnToUrl
                      };

    var scenario = IoCContainer.Instance.GetInstance<ILogOnScenario>();
    var scenarioResult = scenario.Execute(command);

    if (!scenarioResult.Succeeded)
    {
        this.HandleScenarioResult(scenarioResult);
        return View(viewModel);
    }

    return scenarioResult.MvcActionResult;
}

Step 2 – Finalize the scenario

public ActionResult Authenticate()
{
    WebUserSession.SetGuest();

    var viewModel = new LogOnViewModel(OpenIdProviders);
    var command = new LogOnCommand(LogOnSteps.Finalize);
    var scenario = IoCContainer.Instance.GetInstance<ILogOnScenario>();

    var scenarioResult = scenario.Execute(command);
    if (!scenarioResult.Succeeded)
    {
        this.HandleScenarioResult(scenarioResult);

        return View("LogOn", viewModel);
    }

    if (scenarioResult.Member == null)
        return View("SetupNewMembership");

    if (!scenarioResult.Member.IsActivated)
        return View("ConfirmMembership");

    WebUserSession.Set(scenarioResult.UserSession);

    if (WebUserSession.Current.IsAuthenticated)
        return Redirect("~/");

    return View("LogOn", viewModel);
}

That’s it.

//Daniel

About these ads

4 thoughts on “Let business scenarios be reflected in your code

  1. Pingback: DotNetShoutout

  2. Pingback: DotNetKicks.com

    • Hi,

      Yes, I know. Just want to fiddel a bit self first. :-) Then I’m going to take a closer look at http://ncqrs.org/

      To much fun things to look at and to little time. ;-)

      //Daniel

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s