Wiring a ViewModel to a DDD-Workflow

Intro

I recently connected a viewmodel to a workflow that I was inspired to write as a result of reading the book: Domain Driven Design Made Functional. All of my source code can be found on GitHub/Nikeza.

ViewModel

In the viewmodel, I handle validating and submitting a form. The interesting part of this exercise is that I receive domain events as a response to requesting validation and submission.

Here’s the logic for validating and submitting a form:

type ViewModel() as x =

    let mutable validatedForm = None

    let eventOccurred = new Event<_>()

    let validate() =
        let isValidated = function
            | FormValidated form -> validatedForm <- Some form; true
            | _ -> false

        { Form.Email=    x.Email
          Form.Password= x.Password
          Form.Confirm=  x.Confirm
        } 
        |> ofUnvalidated
        |> Validate.Execute 
        |> In.ValidateRegistration.workflow
        |> Updates.statusOf isValidated
               
    let submit() =
        let publishEvents events =
            events |> List.iter(fun event -> eventOccurred.Trigger(event))

        validatedForm |> function 
                         | Some form -> 
                                form |> Submit.Execute 
                                     |> In.SubmitRegistration.workflow
                                     |> publishEvents
                         | None -> ()

    let validateCommand = DelegateCommand( (fun _ -> x.IsValidated <- validate()) , fun _ -> true)

    let submitCommand =   DelegateCommand( (fun _ -> submit() |> ignore), 
                                            fun _ -> x.IsValidated <- validate(); x.IsValidated )

    let mutable email =    ""
    let mutable password = ""
    let mutable confirm =  ""
    let mutable isValidated = false

    member x.Validate = validateCommand :> ICommand
    member x.Submit =   submitCommand   :> ICommand

    [<CLIEvent>]
    member x.EventOccured = eventOccurred.Publish

The remaining viewmodel is the following:

type ViewModel() as x =

    ...
    member x.Email
             with get() =      email
             and  set(value) = email <- value

    member x.Password
             with get() =      password
             and  set(value) = password <- value

    member x.Confirm
        with get() =      confirm
        and  set(value) = confirm <- value

    member x.IsValidated
             with get() =      isValidated
             and  set(value) = isValidated <- value

Here’s some viewmodel helpers:

type UIForm = Registration.Types.Form
type DomainForm = Nikeza.Mobile.Profile.Registration.Form

module That =
    let generatesPage events =
        let eventToPage = function
        | RegistrationSucceeded _ -> () // displayPortal()
        | RegistrationFailed    _ -> () // displayError()

        events |> List.iter eventToPage

module Updates =
    let statusOf formValidated events =
        events |> List.exists formValidated

Registration Workflow

Here’s the Registration workflow:

module In

open Nikeza.Mobile.Profile.Commands
open Nikeza.Mobile.Profile.Commands.Registration
open Nikeza.Mobile.Profile.Events
open Logic

module SubmitRegistration =
    type private SubmitWorkflow = Submit -> RegistrationSubmissionEvent list

    let workflow : SubmitWorkflow =
        fun command -> command |> function
            Submit.Execute form ->
                form |> Try.submit
                     |> ResultOf.Submit.Executed
                     |> Are.Registration.Submission.events

module ValidateRegistration =
    type private ValidateWorkflow = Validate -> RegistrationValidationEvent list

    let workflow : ValidateWorkflow =
        fun command -> command |> function
            Validate.Execute form ->
                             form |> Registration.validate
                                  |> ResultOf.Validation.Executed
                                  |> Are.Registration.Validation.events

Domain Events

The domain events are structured as follows:

module Nikeza.Mobile.Profile.Events

open Nikeza.Common
open Nikeza.DataTransfer

type UnvalidatedForm = Nikeza.Mobile.Profile.Registration.UnvalidatedForm
type ValidatedForm =   Nikeza.Mobile.Profile.Registration.ValidatedForm

type RegistrationValidationEvent =
    | FormValidated         of ValidatedForm
    | FormNotValidated      of UnvalidatedForm

type RegistrationSubmissionEvent =
    | RegistrationSucceeded of Profile
    | RegistrationFailed    of ValidatedForm

Here’s how these events get emitted:

module internal Are.Registration

open Nikeza.Mobile.Profile.Commands
open Nikeza.Mobile.Profile.Events

module Submission =
    type private RegistrationSubmission = ResultOf.Submit -> RegistrationSubmissionEvent list

    let events : RegistrationSubmission =
        fun resultOf -> 
            resultOf |> function
                        ResultOf.Submit.Executed result -> 
                                                 result |> function
                                                         | Ok    profile -> [ RegistrationSucceeded profile]
                                                         | Error form    -> [ RegistrationFailed    form ]

module Validation =
    type private RegistrationValidation = ResultOf.Validation -> RegistrationValidationEvent list

    let events : RegistrationValidation =
        fun resultOf -> 
            resultOf |> function
                        ResultOf.Validation.Executed result -> 
                                                     result |> function
                                                               | Error form -> [FormNotValidated form]
                                                               | Ok    form -> [FormValidated    form]

Tests

Here’s the test that I wrote:

module Tests.Registration

open FsUnit
open NUnit.Framework
open Nikeza.Mobile.UILogic.Registration
open TestAPI
open Nikeza.Mobile.Profile.Events

[<Test>]
let ``Registration validated with email and matching passwords`` () =
    
    // Setup
    let registration = ViewModel()
    registration.Email    <- someEmail
    registration.Password <- somePassword
    registration.Confirm  <- somePassword

    // Test
    registration.Validate.Execute()

    // Verify
    registration.IsValidated |> should equal true

[<Test>]
let ``Registration submitted after being validated`` () =
    
    // Setup
    let mutable registrationSucceeded = false

    let registration = ViewModel()
    Apply.valuesTo registration

    registration.EventOccured.Add(fun event -> 
                                      event |> function
                                               | RegistrationSucceeded _ -> registrationSucceeded <- true
                                               | RegistrationFailed    _ -> registrationSucceeded <- false)
    registration.Validate.Execute()

    // Test
    registration.Submit.Execute()

    // Verify
    registrationSucceeded |> should equal true

 

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 )

Connecting to %s

%d bloggers like this: