Intro

In a previous blog post, I described how to consume a specification as input and in return, output a set of isolated business domains. These business domains will serve as our initial blueprint for constructing the application.

The following are the business domains that were extracted from the specification:

  • Access
  • Profile
  • Subscriptions
  • Portfolio

These domains can serve as the initial foundation for our architecture. Let’s now explore the implementation phase of this application by adding a specification library that our Access domain will interpret.

Identify Domain Operations

We’ll first establish a general design for handling requests inside of our application. These requests will most likely come from the user of the application.

Attempts

To start off, we need to identify what a user can attempt to do within the Access domain:

  • Submit registration
  • Login to session
  • Logout of session

If you’ve noticed, I used the term “attempt” instead of “command” in regards to the requests that a user may make within the application. So what’s the difference between an attempt and a command?

An attempt is an operation that’s not guaranteed to succeed. Thus, I would like to define a command as simply an order that gets dispatched for observers to respond to.

The following types are the supported commands within the Access domain of our application:

(*Registration*)
type ValidateCommand = Execute of UnvalidatedForm
type AttachCommand =   Attach  of ValidatedForm

(*Session*)
type LoginCommand =  Login  of Credentials
type LogoutCommand = Logout of Provider

 

The following are attempt types:

module Attempt =
 
    type Submit = ValidatedForm -> Result
    type Login =  Credentials   -> Result
    type Logout = Provider      -> Result

In order to perform an attempt, a command must be issued. Therefore, we will bundle commands as precursors to attempts.

The following types are interfaces that require a command and an attempt and thus return a Result type.

type SubmitAttempt = Attempt.Submit -> Registration.Attach -> Result
type LoginAttempt =  Attempt.Login  -> LoginCommand        -> Result
type LogoutAttempt = Attempt.Logout -> LogoutCommand       -> Result

 

Attempt as a Data Structure

If you remember, we defined Submit as an interface (i.e. function type):

module Attempt =
 
    type Submit = ValidatedForm -> Result

 

Let’s now expose this Submit type as a member of another type that we’ll call Attempt:

type Attempt = {
        Submit : Submit
    }

Also remember that our Submit type is just an interface. Hence, in functional programming languages, function types are interfaces. This means that we’ll eventually need to provide an implementation for what it means to Submit.

Once our attempt and Submit interfaces get implemented, we could call it inside of a view-model like this:

type ViewModel(dependencies) as x =
 
    inherit ViewModelBase()
 
    let submit = dependencies.Attempt.Submit

    let validate() =
        …
        validatedForm |> function 
                         | Some form -> 
                                form |> Attach 
                                     |> attempt submit
                                     |> ResultOf.Submit.Executed
                                     |> Are.Submission.events
                                     |> broadcast
                                     
                         | None -> ()

  

Workflows

Contrary to an attempt operation, a user can also request an operation that’s completely controlled within the confines of a domain. This form of operation accepts a command as input and emits a list of domain events for further processing. This form of operation can be called a workflow.

Validating a registration form, does not require external systems. Translated, we can validate a registration form internally before submitting the form to an external system for additional processing.

The following is an interface for validating a form:

type ValidateWorkflow = Registration.ValidateCommand -> RegistrationValidationEvent nonempty

 
Remember, we defined the validate command as the following:

type ValidateCommand = Validate of UnvalidatedForm

 
Unlike the attempt function type that we declared in the last section, the ValidateWorkflow interface doesn’t return a Result type. Instead, ValidateWorkflow returns a nonempty list of domain events.

Here’s a sneak peak of how the ValidateWorkflow interface will be invoked inside of a view-model:

  Validate ( Unvalidated { Email=    Email    x.Email
                            Password= Password x.Password
                            Confirm=  Password x.Confirm
                          }
                            |> In.ValidateRegistration.workflow
                            |> Update.statusOf isValidated
           )

 
Our Access domain is essentially an interpreter. Hence, like the Access domain, other domains in our system will also rely on interfaces that are exposed by a specification library.

Here’s a sneak peek of the ValidateWorkfow implementation:

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

 
The specification library can expose the Validate interface as the following:

type ValidateForm = UnvalidatedForm -> Result

 

Thus, our Access domain provides the following implementation for the ValidateForm interface:

let validate : ValidateForm =
 
    fun (Unvalidated form) ->
 
        let  isValidEmail (Email email) = email |> String.length > 3
 
        let (password,confirm) =
            (form.Password |> function Password p -> p, form.Confirm  |> function Password p -> p)
 
        if   not (form.Email |> isValidEmail) then
             Unvalidated form |> Error
 
        elif String.length password < 3 then
             Error <| Unvalidated form
 
        elif not (password = confirm) then
             Error <| Unvalidated form
 
        else Ok <| Validated form

 
Once our validation workflow gets implemented, we could call it inside of a view-model like this:


    let mutable validatedForm = None
 
    let validate() =
 
        let isValidated = function
            | FormValidated form -> validatedForm  false
        
        Unvalidated { Email=    Email    x.Email
                      Password= Password x.Password
                      Confirm=  Password x.Confirm
                    } 
                      |> Validate
                      |> In.ValidateRegistration.workflow
                      |> Update.statusOf isValidated

 

Results

As stated earlier, an attempt returns a Result type and a workflow returns a list of domain events. The Result type from an attempt requires further processing and needs to get converted into a list of domain events similar to workflows.

The Submit type under the ResultOf module will assist in converting a Result type into a list of domain events.

module Submit =
        module ResultOf = type Submit = Executed of Result

The following shows how a submit attempt gets converted into domain events:

…
|> attempt submit
|> ResultOf.Submit.Executed
|> Are.Submission.events

Conclusion

In this blog post, I provided some hints on how we could encode a specification as a library. Thus, I shared how we could leverage function types as interfaces that a specification library can expose for a corresponding domain to implement. In addition, I discussed how a command will either trigger an attempt or a workflow. An attempt will return a Result type. A workflow will return a nonempty list of domain events. In the next blog post, I will explain the processing of what gets returned from an attempt and a workflow.

The saga continues…

Advertisements