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