Intro

In the last blog post, I discussed domain event handlers whose sole purpose is to execute side effects for application usefulness. In this blog post, I’ll be discussing file structure and how it relates to specification.

File Structure

An application can be organized into the following file folders:

  • Domains
  • Integration
  • Specifications
  • Tests

Here’s an example:

domain-folders

Domain Libraries

The domain folder harbors domain responsibilities for the application. These responsibilities include workflows, attempts, and queries.

domains-library

A domain library is an interpreter. As a result, each domain library maintains a reference to its specification library’s data types and function types for implementing workflows, attempts, queries, and external dependencies.

access-specification-reference

Specification Library

As stated earlier, a domain relies on a specification. Thus, a specification library supplies a domain library with the data types required for workflows, attempts, queries, and external dependencies.

access-specification

Code is shown for each of the files below.

Language.fs

The Language file supplies the domain specific types that the Access domain requires for its operations.

namespace Nikeza.Access.Specification

type Email =      Email      of string
type Password =   Password   of string
type EntityName = EntityName of string
type FirstName =  FirstName  of string
type LastName =   LastName   of string

type Form = {
    Email:    Email
    Password: Password
    Confirm:  Password
}

type UnvalidatedForm = Unvalidated of Form
type ValidatedForm =   Validated   of Form

Commands.fs

The Commands file supplies commands that the Access domain requires for its operations.

namespace Nikeza.Access.Specification.Commands

open Nikeza
open DataTransfer
open Nikeza.Access.Specification

type LoginCommand =  Login  of Credentials
type LogoutCommand = Logout of Provider

module Registration =

    type ValidateCommand = Validate of UnvalidatedForm
    type AttachCommand =   Attach   of ValidatedForm

    module Validate =
        module ResultOf = type Validate = Executed of Result

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

module Session =

    module ResultOf =

        type Login =  Login  of Result
        type Logout = Logout of Result

Events.fs

The Events file supplies the domain specific events that the Access domain requires for its operations.

namespace Nikeza.Access.Specification

open Nikeza
open DataTransfer

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

Functions.fs

The Functions file supplies domain specific functions for the Access domain.

namespace Nikeza.Access.Specification

open Nikeza
open Common
open Events

module Workflows =
    open Nikeza.Access.Specification.Commands
    type ValidateWorkflow = Registration.ValidateCommand -> RegistrationValidationEvent nonempty

module Session =
    open Nikeza.Access.Specification.Commands.Session

    type HandleLogin =  ResultOf.Login  -> LoginEvent  nonempty
    type HandleLogout = ResultOf.Logout -> LogoutEvent nonempty

module Submission =
    open Commands.Registration.Submit

    type RegistrationSubmission = ResultOf.Submit -> RegistrationSubmissionEvent nonempty

module Validation =
    open Commands.Registration.Validate

    type ValidateForm = UnvalidatedForm -> Result

    type RegistrationValidation = ResultOf.Validate -> RegistrationValidationEvent nonempty

Attempts.fs

The Attempts file supplies domain specific interfaces that the Access domain requires for exception prone operations.

namespace Nikeza.Access.Specification

open Nikeza
open DataTransfer

module Attempt =

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

module Attempts =

    open Nikeza.Access.Specification.Commands

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

Dependencies.Login.fs

The Login Dependencies file supplies the types that are used for injecting the dependencies required for the Login operation.

namespace Nikeza.Access.Specification

open Events
open Nikeza.Common

module Login =

    open Attempt

    type Attempt =  {
        Login : Login
    }

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

    type Dependencies = {
        Attempt     : Attempt
        SideEffects : SideEffects
    }

 

Dependencies.registration.fs

The Registration Dependencies file supplies the types that are used for injecting the dependencies required for the Registration operation.

namespace Nikeza.Access.Specification

open Events

module Registration =

    open Nikeza.Access.Specification.Attempt

    type Attempt = {
        Submit : Submit
    }

    type SideEffects = {
        ForRegistrationSubmission : (RegistrationSubmissionEvent -> unit) list
    }

    type Dependencies = {
        Attempt     : Attempt
        SideEffects : SideEffects
    }

Access Library

The Access library is a domain library that harbors interpreters for the attempt and workflow specifications. In addition to the interpreters, the library also has modules that are responsible for converting the status result of registration and session operations into domain events.

Here are the files for the Access domain:

Interpret.attempts.fs

The Interpret Attempts file harbors the implementation details for attempt operations. These operations usually rely on IO (i.e. web requests). In this implementation, we just return a status result of Error for now.

module Nikeza.Mobile.Access.Attempt

open Nikeza.Access.Specification

let submit : Attempt.Submit =
    fun validatedForm -> Error validatedForm

let logout : Attempt.Logout =
    fun provider -> Error provider

let login : Attempt.Login =
    fun credentials -> Error credentials

Interpret.workflow.fs

The Interpret Workflow file harbors the implementation details for workflow operations.

module internal Logic.Registration

open System
open Nikeza.Common
open Nikeza.Access.Specification
open Nikeza.Access.Specification.Validation

type ValidatedForm =   Nikeza.Access.Specification.ValidatedForm
type UnvalidatedForm = Nikeza.Access.Specification.UnvalidatedForm

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

let isValid (credentials:LogInRequest) =
    let validEmail =    not <| String.IsNullOrEmpty(credentials.Email)
    let validPassword = not <| String.IsNullOrEmpty(credentials.Password)

    validEmail && validPassword

ResultOf.session.fs

The ResultOf Session file converts a status result to a list of domain events.

namespace Are

open Nikeza.Access.Specification.Commands.Session
open Nikeza.Access.Specification.Events
open Nikeza.Access.Specification.Session
open Nikeza.Common
open Nikeza.DataTransfer

module Login =

    let events : HandleLogin = function
        ResultOf.Login result ->
                       result |> function
                                 | Ok    info ->
                                         info |> function
                                                 | Some p -> { Head= LoggedIn p; Tail=[] }

                                                 | None   -> let credentials = { Credentials.Email=""; Credentials.Password="" }
                                                             { Head= FailedToAuthenticate credentials; Tail=[] }

                                 | Error info -> { Head= FailedToConnect info; Tail=[] }

module Logout =

    let events : HandleLogout = function
        ResultOf.Logout result ->
                        result |> function
                                  | Ok    p -> { Head= LoggedOut    p; Tail= [] }
                                  | Error p -> { Head= LogoutFailed p; Tail= [] }

ResultOf.registration.fs

The ResultOf Registration file converts a status result to a list of domain events.

namespace Are

open Nikeza.Common
open Nikeza.Access.Specification.Events

module Submission =
    open Nikeza.Access.Specification.Commands.Registration.Submit
    open Nikeza.Access.Specification.Submission

    let events : RegistrationSubmission = function
        ResultOf.Submit
                .Executed result ->
                          result |> function
                                    | Ok    profile -> { Head= RegistrationSucceeded profile; Tail=[] }
                                    | Error form    -> { Head= RegistrationFailed    form;    Tail=[] }

module internal Validation =
    open Nikeza.Access.Specification.Commands.Registration.Validate
    open Nikeza.Access.Specification.Validation

    let events : RegistrationValidation = function
        ResultOf.Validate
                .Executed result ->
                          result |> function
                                    | Error form -> { Head= FormNotValidated form; Tail=[] }
                                    | Ok    form -> { Head= FormValidated    form; Tail=[] }

Workflows.fs

The Workflows file harbors the high-level implementation details for workflow operations.

module Nikeza.Mobile.Access.In

open Nikeza.Access.Specification.Commands.Session
open Nikeza.Access.Specification.Workflows
open Nikeza.Access.Specification.Commands
open Logic

module ValidateRegistration =
    open Registration.Validate
    open Nikeza.Access.Specification.Commands.Registration

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

Attempt.fs

The Attempts file harbors the high-level implementation details for attempt operations.

module Nikeza.Mobile.Access.Using

open Nikeza.Access.Specification.Attempts
open Nikeza.Access.Specification.Commands
open Logic

module ValidatedForm =

    let attempt : SubmitAttempt =
        fun submit -> function
            Registration.Attach form ->
                                form |> submit

module Login =

    let attempt : LoginAttempt =
        fun login -> function
            Login credentials ->
                   credentials |> login

module Logout =

    let attempt : LogoutAttempt =
        fun logout -> function
            Logout provider ->
                   provider |> logout

Integration Libraries

The Integration folder harbors libraries that are focused on side effects. This includes libraries that would focus on user interfaces, web requests, and data persistence.

The integration folder could look like the following:

Integregation-Libraries

AppLogic Library

The AppLogic library houses domain event handlers. In other words, this library handles the side effects for the application. Such side effects are page navigation, logging, and data persistence.

The following files deal with side effects:

AppLogic-Library

UILogic Library

The UILogic library houses the view-model factory, view-models, page types, and an MVVM Command implementation.

The following files deal with UILogic:

UILogic-Library

UI

The UI library contains the actual page implementations for the application to render:

UI-Library

Conclusion

In conclusion, I discussed the file structure that supports Specification as a Library. Specifically, I showed the Domains, Integration, and Specifications folders. I then showed the file contents for the Access Specification library.

Advertisements