Introduction

In the previous blog post, I discussed the difference between workflows and attempts. In summary, a workflow accepts a command as input, and as a result, emits a list of domain events. Thus, an attempt also accepts a command as input, but instead, returns a status. Furthermore, the status of an attempt requires the caller to pipe the status into another function to generate domain events. This post will cover how domain events get processed so that they can inflict side effects within an application.

Domain Events

What exactly are domain events? Domain events can be viewed as historical observations related to a domain.

The following are domain events that could occur inside the Access domain:

module Events =

    type RegistrationValidationEvent =
        | FormValidated    of ValidatedForm
        | FormNotValidated of UnvalidatedForm

    type RegistrationSubmissionEvent =
        | RegistrationSucceeded of DataTransfer.Profile
        | RegistrationFailed    of ValidatedForm

    type LoginEvent =
        | LoggedIn             of Provider
        | FailedToConnect      of Credentials
        | FailedToAuthenticate of Credentials

    type LogoutEvent =
        | LoggedOut    of Provider
        | LogoutFailed of Provider

When a user attempts to submit their credentials for login, one of these domain events could occur:

    type LoginEvent =
        | LoggedIn             of Provider
        | FailedToConnect      of Credentials
        | FailedToAuthenticate of Credentials

Application

An application’s role is to respond to domain events. Hence, domain events are useless unless acted upon. Therefore, when a login attempt is successful, a LoggedIn event will be broadcasted to an application’s domain event handlers.

The following is an example of an attempt that results in domain events:

then Login { Email=email; Password=password }
              |> attempt login
              |> ResultOf.Login
              |> Are.Login.events

Login events are broadcasted at the end:

 …
 |> attempt login
 |> ResultOf.Login
 |> Are.Login.events
 |> broadcast

Here’s how a broadcast function can be implemented:

let broadcast events =
        events.Head::events.Tail
         |> List.iter (fun event -> sideEffects.ForLoginAttempt |> handle event)

Here’s what the handle function could look like:

let handle event handlers= 

    handlers.Head::handlers.Tail
     |> List.iter(fun handle -> handle event)

Domain event handlers

Domain event handlers are the primary subscribers to domain events. For example, when a LoggedIn event gets broadcasted, a page navigation handler can display a portal page.

The following example shows how navigation can take place from a LoginEvent case:

let navigate' = function
            | LoggedIn        provider    -> Application.Current |> navigate (portalPage provider.Profile) provider
            | FailedToConnect credentials -> Application.Current |> navigate errorPage credentials.Email

            | FailedToAuthenticate _ -> ()

Here’s an implementation for page navigation:

let navigate page context (app:Application) =

    try app.MainPage  Debug.WriteLine(ex.Message); raise ex

So far, I elaborated on the segregation between the processing of a command that will emit domain events versus the actual domain event handlers that inflict side effects. To make an application useful, side effects are usually required. Such side effects would involve writing to a database or navigating to a page. The next section will cover configuring domain event handlers.

Designing Side Effects

As discussed earlier, an application’s role is to respond to domain events by executing side effects. Such side effects include page navigation, data persistence, and logging. In order for these side effects to occur, domain event handlers must first subscribe to domain events. Once subscribed, a domain event handler can execute a side effect from a domain event.

The following reflects how we can define domain event handlers for a login attempt:

module Login =

    type SideEffects =  {
        ForLoginAttempt : (LoginEvent -> unit) nonempty
    }

The ForLoginAttempt property is a nonempty list of functions that takes a LoginEvent as input and outputs a unit. Note that a function will usually have side effects if it returns unit. Therefore, we can view the ForLoginAttempt property as a nonempty list of domain event handlers for a login attempt.

Domain event handlers can be defined as a dependency within our specification library:

module Login =

    …

    type SideEffects =  {
        ForLoginAttempt : (LoginEvent -> unit) nonempty
    }

    type Dependencies = {
        …
        SideEffects : SideEffects
    }

The definition of Login.dependencies could be the following:

module Login =

    open System.Diagnostics

    let dependencies =

        let log' = function
            | LoggedIn             user        -> Debug.WriteLine(sprintf "Login successful:\n %A" user)
            | FailedToConnect      credentials -> Debug.WriteLine(sprintf "Error: Unable to connect to server:\n %A" credentials)
            | FailedToAuthenticate credentials -> Debug.WriteLine(sprintf "Warning: Unable to authenticate user:\n %A" credentials)

        let navigate' = function
            | LoggedIn        provider    -> Application.Current |> navigate (portalPage provider.Profile) provider
            | FailedToConnect credentials -> Application.Current |> navigate errorPage credentials.Email

            | FailedToAuthenticate _ -> ()

        let handlers =    { Head=log'; Tail=[navigate'] }
        let sideEffects = { ForLoginAttempt= handlers }
        let attempt =     { Login= TestAPI.mockLogin }

        { SideEffects= sideEffects;
          Attempt=     attempt
        }

We could then inject these domain event handlers into a view-model as a dependency:

MainPage = new LoginPage { BindingContext = new ViewModel(Login.dependencies) };

Conclusion

In this post, I discussed how we can add side effect definitions to our specification library for a given domain event. In addition, I described how a domain event handler is essentially a function type that returns a unit. I then described how to inject domain event handlers as view-model dependencies so that they can inflict side effects inside our application. So what does the file solution look like when we model an application this way?

The saga continues…

Advertisements