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 Libraries
The domain folder harbors domain responsibilities for the application. These responsibilities include workflows, attempts, and queries.
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.
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.
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:
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:
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:
UI
The UI library contains the actual page implementations for the application to render:
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.