Snippets From A Real World FSharp Architecture

The following post explains interesting implementation details of a functional architecture for

  1. Domain Driven Design with F#
  2. Physical and Logical application structure
  3. Hiding Third-Party dependency seams
  4. Railway Oriented Programming

The architecture is guided by domain driven design featuring two distinct domains, the DatabaseModels, the representations stored in the database and the API Models, the representations delivered to the client.

I use F# Single Case Union Types to represent Ids in both domains

type ProblemDatabaseModel =
id : ProblemDatabaseIdModel
title : string
problemContent : Json
childSolutions : SolutionDatabaseIdModel list
originatorId : UserDatabaseIdModel
tags : TagDatabaseIdModel list
usersWhoUpvotedSet : Set<UserDatabaseIdModel>

I use F#’s capability to represent mutually recursive types for the ApiModels

type ProblemModel =
id : ProblemIdModel
title : string
problemContent : Json
childSolutions : SolutionModel list
originator : UserModel
tags : TagDatabaseModel list
usersWhoUpvotedSet : Set<UserDatabaseIdModel>
and SolutionModel =
solutionId : SolutionIdModel
title : string
proposalContent : Json
childProblemStubs : ProblemModel list
tags : TagDatabaseModel list
originator : UserModel
comments : CommentMultiTreeModel
parentProblemStub : ProblemModel
usersWhoUpvotedSet : Set<UserDatabaseIdModel>

The physical structure contains four projects including a

  1. “Repository” project adhering to the Repository pattern,
  2. “ApiModels” project responsible for building up the ApiModels
  3. “QueryApi” project responsible for returning the built ApiModels to the client or in the case of an error an HTTP error statuscode
  4. “CommandApi” project responsible for receiving post updates from the client.

Additionally there is corresponding unit test project for each

The QueryApi and CommandApi projects logically represent an API layer and are therefore within the same namespace. However, they are separated into projects because the CommandApi project only depends on the Repository project while the QueryAPi project depends on both the Repository and ApiModels projects.

Third party dependencies such as database objects present challenges to effectively test requiring complex mocking libraries. However, the added mocking complexity can be avoided with partial application. Below is the readDatabaseModel function, found in the Repository project, that takes as its first parameter a function getDocument. The partially applied getDocument function (see below) accepts the desired document’s id and returns an asynchronous computation expression that returns the requested document (shown in bold).

let readDatabaseModel getDocument deserializeDocument id =
async {
let! routeProblemDatabaseModel = getDocument id
|> (fun (resourceResponse :ResourceResponse<Document>) -> resourceResponse.Resource)
|> deserializeDocument
|> function
| JPass x ->
Ok x
| JFail error ->
Result.Error <| WonktonkJsonFailure error
|:? System.ArgumentNullException as argumentNullException ->
return Result.Error <| WonktonkArgumentNullError argumentNullException
    |:? DocumentClientException as documentClientException -> 
return Result.Error <| WonktonkDocumentClientError documentClientException
    |:? System.Exception as generalException ->
return Result.Error <| WonktonkError generalException

The getDocument function accepts the documentClient object from the CosmosDB SDK as well as the required connection constants databaseId and collectionId, a discriminated union de-structuring function and the id to query.

let getDocument (documentClient : DocumentClient) databaseId    collectionId destructureModel idModel =
let id = destructureModel idModel
UriFactory.CreateDocumentUri(databaseId, collectionId, id)
|> documentClient.ReadDocumentAsync
|> Async.AwaitTask

Originally the readDatabaseModel accepted an interface for the documentClient object. The design made testing possible however difficult because mocking the documentClient object was required.

The readDatabaseModel also wraps de-serialization errors and database errors into a WonktonkServerError type following Railway Oriented Programming design guidelines.

type WonktonkServerError =
| WonktonkJsonFailure of ErrorValue:JsonFailure
| WonktonkArgumentNullError of ErrorValue:System.ArgumentNullException
| WonktonkDocumentClientError of ErrorValue:DocumentClientException
| WonktonkError of ErrorValue:System.Exception

Specifically capturing the nature of an error with the WonktonkServerError allows sending the client a more specific HTTP statuscode from the API layer

match problemModelResult with
| Ok problemModel ->
let serializedProblemModel = problemModelSerialize problemModel
new HttpResponseMessage(HttpStatusCode.OK, Content = new StringContent(serializedProblemModel))
| Error wonktonkServerError ->
match wonktonkServerError with
| WonktonkArgumentNullError argumentNullException ->
new HttpResponseMessage(HttpStatusCode.BadRequest)
| WonktonkDocumentClientError documentClientException ->
new HttpResponseMessage(HttpStatusCode.NotFound)
| WonktonkError generalError ->
new HttpResponseMessage(HttpStatusCode.InternalServerError)
| WonktonkJsonFailure jsonFailure ->
new HttpResponseMessage(HttpStatusCode.InternalServerError)

I hope you found some valuable F# implementation details regarding DDD with F#, physical and logical structuring of applications, hiding third-party dependency seams and ROP.

P.S. if you are a policy wonk, broadly defined (informed citizen, student, advocate, practitioner or professional who likes to apply logic and facts to public issues) please consider joining out beta. It would be much appreciated and help an F# startup start!