objects |> functions


Tres - Step 3

We'll start out, as we did with Dos, with adding the RavenDB dependency to paket.references:

1: 
RavenDb.Client

paket install installs it for this project.

Configuring the Connection

Since Tres is more-or-less a C#-to-F# conversion from Dos, we'll use the same data-config.json file, in the root of the project:

1: 
2: 
3: 
4: 
{
  "Url": "http://localhost:8080",
  "Database": "O2F3"
}

We'll also add it to Tres.fsproj to make sure it gets copied to the output; for this, though, add it to the ItemGroup with the compiled items, just under App.fs:

1: 
2: 
3: 
<Content Include="data-config.json">
  <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>

Now, we'll create a file Data.fs to hold what we had in the Data directory in the prior two solutions. It should be added to the build order after Domain.fs and before HomeModule.fs. We'll start this file off with our DataConfig implementation:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
namespace Tres

open Newtonsoft.Json
open System

type DataConfig =
  { Url      : string
    Database : string
    }
  with
    [<JsonIgnore>]
    member this.Urls = [| this.Url |]

This should be familiar at this point; we're using a record type instead of a class, and we've added an instance member the same way we did the static Empty members on our entities. Json.NET will have no trouble deserializing to a record type instead of a class; it actually has very good support for F# data constructs. We will tweak some of them when we get to Quatro.

The Collection.cs static class with constants is brought over as a module (below the data configuration code). The RequireQualifiedAccess attribute means that Collection cannot be opened; this prevents us from possibly providing an unintended version of the identifier Post (for example).

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
[<RequireQualifiedAccess>]
module Collection =
  let Category = "Categories"
  let Comment  = "Comments"
  let Page     = "Pages"
  let Post     = "Posts"
  let User     = "Users"
  let WebLog   = "WebLogs"
  
  let IdFor coll (docId : Guid) = sprintf "%s/%s" coll (docId.ToString "N")

  let FromId docId =
    try
      let parts = (match isNull docId with true -> "" | false -> docId).Split '/'
      match parts.Length with
      | 2 -> parts.[0], Guid.Parse parts.[1]
      | _ -> "", Guid.Empty
    with :?FormatException -> "", Guid.Empty

Ensuring Indexes Exist

To hold our indexes, we'll create a file Indexes.fs, and add it to the build process between Domain.fs and Data.fs. This way, Data.fs is reserved for our data access logic, and we will be able to reference those index types in it.

At this point, though, we encounter one of our first "seams" between the F# world and the C# world. RavenDB's strongly-typed index creation task is designed to receive an anonymous object (the select new { }... construct). F# does not have that feature, and as of this writing, index creation does not work with F#'s anonymous record type construct ({| Name: value; ... |}). However, there is another way to create indexes, and we will still have them named, and can still create them easily on application startup.

Instead of an AbstractIndexCreationTask<'T>, we can use at AbstractJavaScriptIndexCreationTask instead. This type allows the user to provide a JavaScript mapping function that will be used to generate the index. Even better, though, was something I stumbled across while trying to define RavenDB indexes in F#; you can also provide LINQ expressions. RavenDB Studio allows us to view index definitions, and in viewing the indexes from Uno, I saw that those definitions were text versions of the functional equivalent of our LINQ query (xs.Select(x => new { .. }) instead of from x in xs select new { ... }). So, while RavenDB does provide a mechanism to describe indexes in JavaScript (and, if you are so inclined, read all about it on their docs), we will use it to specify the same indexes we've already designed.

Here's a look at our example index, utilizing the AbstractJavaScriptIndexCreationTask:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
open Raven.Client.Documents.Indexes
open System.Collections.Generic

type Categories_ByWebLogIdAndSlug () as this =
  inherit AbstractJavaScriptIndexCreationTask ()
  do
    this.Maps <-
      HashSet<string> [
        "docs.Categories.Select(category => new {
            WebLogId = category.WebLogId,
            Slug = category.Slug
        })"
        ]

F# 4.7 should be bringing the nameof operator that C# has had for a while. When this operator arrives, we could use it to create this string, which would serve as an access of the field, enabling it to show up when looking for all the places it is used. For now, though, we'll have to remember to check these indexes if we decide to start renaming things.

Dependency Injection

We'll do the same thing we did for Dos - override DefaultNancyBootstrapper and register our connection there. We'll do all of this in App.fs. We will need some additional open statements:

1: 
2: 
3: 
4: 
5: 
6: 
open Indexes
open Nancy
open Newtonsoft.Json
open Raven.Client.Documents
open Raven.Client.Documents.Indexes
open System.IO

Then, we'll create our bootstrapper above the definition for type Startup():

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
type TresBootstrapper () =
  inherit DefaultNancyBootstrapper ()

    override __.ConfigureApplicationContainer container =
      base.ConfigureApplicationContainer container
      let cfg = File.ReadAllText "data-config.json" |> JsonConvert.DeserializeObject<DataConfig>
      let store = new DocumentStore (Urls = cfg.Urls, Database = cfg.Database)
      container.Register<IDocumentStore> (store.Initialize ()) |> ignore
      IndexCreation.CreateIndexes (typeof<Categories_ByWebLogIdAndSlug>.Assembly, store)

This should look very familiar, but we were able to flip some parameters to make the lines more expression-like. This also illustrates the F# version of C#'s object initializer syntax; the line instantiating DocumentStore is a direct translation of the C# version.

Now, as with Dos, we need to modify Startup (just below where we put this code) to use this new bootstrapper.

1: 
2: 
  member __.Configure (app : IApplicationBuilder) =
    app.UseOwin (fun x -> x.UseNancy (fun opt -> opt.Bootstrapper <- new TresBootstrapper()) |> ignore) |> ignore

At this point, once dotnet run displays the "listening on port 5000" message, we should be able to look at RavenDB's O2F3 database and indexes, just as we could for Uno and Dos.


Back to Step 3

namespace Newtonsoft
namespace Newtonsoft.Json
namespace System
DataConfig.Url: string
Multiple items
val string : value:'T -> string

--------------------
type string = String
DataConfig.Database: string
Multiple items
type JsonIgnoreAttribute =
  inherit Attribute
  new : unit -> JsonIgnoreAttribute

--------------------
JsonIgnoreAttribute() : JsonIgnoreAttribute
val this : DataConfig
Multiple items
type RequireQualifiedAccessAttribute =
  inherit Attribute
  new : unit -> RequireQualifiedAccessAttribute

--------------------
new : unit -> RequireQualifiedAccessAttribute
val Category : string
val Comment : string
val Page : string
val Post : string
val User : string
val WebLog : string
val IdFor : coll:string -> docId:Guid -> string
val coll : string
val docId : Guid
Multiple items
type Guid =
  struct
    new : b:byte[] -> Guid + 4 overloads
    member CompareTo : value:obj -> int + 1 overload
    member Equals : o:obj -> bool + 1 overload
    member GetHashCode : unit -> int
    member ToByteArray : unit -> byte[]
    member ToString : unit -> string + 2 overloads
    static val Empty : Guid
    static member NewGuid : unit -> Guid
    static member Parse : input:string -> Guid
    static member ParseExact : input:string * format:string -> Guid
    ...
  end

--------------------
Guid ()
Guid(b: byte []) : Guid
Guid(g: string) : Guid
Guid(a: int, b: int16, c: int16, d: byte []) : Guid
Guid(a: uint32, b: uint16, c: uint16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : Guid
Guid(a: int, b: int16, c: int16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : Guid
val sprintf : format:Printf.StringFormat<'T> -> 'T
Guid.ToString() : string
Guid.ToString(format: string) : string
Guid.ToString(format: string, provider: IFormatProvider) : string
val FromId : docId:string -> string * Guid
val docId : string
val parts : string []
val isNull : value:'T -> bool (requires 'T : null)
property Array.Length: int
Guid.Parse(input: string) : Guid
field Guid.Empty: Guid
Multiple items
type FormatException =
  inherit SystemException
  new : unit -> FormatException + 2 overloads

--------------------
FormatException() : FormatException
FormatException(message: string) : FormatException
FormatException(message: string, innerException: exn) : FormatException
namespace Raven
namespace Raven.Client
namespace Raven.Client.Documents
namespace Raven.Client.Documents.Indexes
namespace System.Collections
namespace System.Collections.Generic
Multiple items
type Categories_ByWebLogIdAndSlug =
  inherit AbstractJavaScriptIndexCreationTask
  new : unit -> Categories_ByWebLogIdAndSlug

--------------------
new : unit -> Categories_ByWebLogIdAndSlug
val this : Categories_ByWebLogIdAndSlug
Multiple items
type AbstractJavaScriptIndexCreationTask =
  inherit AbstractIndexCreationTask
  member CreateIndexDefinition : unit -> IndexDefinition
  member Fields : Dictionary<string, IndexFieldOptions> with get, set
  member IsMapReduce : bool
  member Maps : HashSet<string> with get, set

--------------------
AbstractJavaScriptIndexCreationTask() : AbstractJavaScriptIndexCreationTask
property AbstractJavaScriptIndexCreationTask.Maps: HashSet<string>
Multiple items
type HashSet<'T> =
  new : unit -> HashSet<'T> + 3 overloads
  member Add : item:'T -> bool
  member Clear : unit -> unit
  member Comparer : IEqualityComparer<'T>
  member Contains : item:'T -> bool
  member CopyTo : array:'T[] -> unit + 2 overloads
  member Count : int
  member ExceptWith : other:IEnumerable<'T> -> unit
  member GetEnumerator : unit -> Enumerator<'T>
  member GetObjectData : info:SerializationInfo * context:StreamingContext -> unit
  ...
  nested type Enumerator

--------------------
HashSet() : HashSet<'T>
HashSet(comparer: IEqualityComparer<'T>) : HashSet<'T>
HashSet(collection: IEnumerable<'T>) : HashSet<'T>
HashSet(collection: IEnumerable<'T>, comparer: IEqualityComparer<'T>) : HashSet<'T>
namespace System.IO
namespace Microsoft
namespace Microsoft.AspNetCore
namespace Microsoft.AspNetCore.Builder
namespace Nancy
namespace Nancy.Owin
Multiple items
type TresBootstrapper =
  inherit DefaultNancyBootstrapper
  new : unit -> TresBootstrapper
  member Configure : app:IApplicationBuilder -> unit
  override ConfigureApplicationContainer : container:TinyIoCContainer -> unit

--------------------
new : unit -> TresBootstrapper
Multiple items
type DefaultNancyBootstrapper =
  inherit NancyBootstrapperWithRequestContainerBase<TinyIoCContainer>
  new : unit -> DefaultNancyBootstrapper
  member GetEnvironment : unit -> INancyEnvironment
  static val DefaultAutoRegisterIgnoredAssemblies : IEnumerable<Func<Assembly, bool>>

--------------------
DefaultNancyBootstrapper() : DefaultNancyBootstrapper
val container : TinyIoc.TinyIoCContainer
val cfg : DataConfig
type File =
  static member AppendAllLines : path:string * contents:IEnumerable<string> -> unit + 1 overload
  static member AppendAllText : path:string * contents:string -> unit + 1 overload
  static member AppendText : path:string -> StreamWriter
  static member Copy : sourceFileName:string * destFileName:string -> unit + 1 overload
  static member Create : path:string -> FileStream + 3 overloads
  static member CreateText : path:string -> StreamWriter
  static member Decrypt : path:string -> unit
  static member Delete : path:string -> unit
  static member Encrypt : path:string -> unit
  static member Exists : path:string -> bool
  ...
File.ReadAllText(path: string) : string
File.ReadAllText(path: string, encoding: Text.Encoding) : string
type JsonConvert =
  static val True : string
  static val False : string
  static val Null : string
  static val Undefined : string
  static val PositiveInfinity : string
  static val NegativeInfinity : string
  static val NaN : string
  static member DefaultSettings : Func<JsonSerializerSettings> with get, set
  static member DeserializeAnonymousType<'T> : value:string * anonymousTypeObject:'T -> 'T + 1 overload
  static member DeserializeObject : value:string -> obj + 7 overloads
  ...
JsonConvert.DeserializeObject<'T>(value: string) : 'T
JsonConvert.DeserializeObject(value: string) : obj
JsonConvert.DeserializeObject<'T>(value: string, settings: JsonSerializerSettings) : 'T
JsonConvert.DeserializeObject<'T>(value: string, [<ParamArray>] converters: JsonConverter []) : 'T
JsonConvert.DeserializeObject(value: string, type: Type) : obj
JsonConvert.DeserializeObject(value: string, settings: JsonSerializerSettings) : obj
JsonConvert.DeserializeObject(value: string, type: Type, settings: JsonSerializerSettings) : obj
JsonConvert.DeserializeObject(value: string, type: Type, [<ParamArray>] converters: JsonConverter []) : obj
type DataConfig =
  {Url: string;
   Database: string;}
    member Urls : string []
val store : DocumentStore
Multiple items
type DocumentStore =
  inherit DocumentStoreBase
  new : unit -> DocumentStore
  member AggressivelyCacheFor : cacheDuration:TimeSpan * ?database:string -> IDisposable + 1 overload
  member BulkInsert : ?database:string * ?token:CancellationToken -> BulkInsertOperation
  member Changes : ?database:string -> IDatabaseChanges + 1 overload
  member DisableAggressiveCaching : ?database:string -> IDisposable
  member Dispose : unit -> unit
  member GetLastDatabaseChangesStateException : ?database:string * ?nodeTag:string -> Exception
  member GetRequestExecutor : ?database:string -> RequestExecutor
  member Identifier : string with get, set
  member Initialize : unit -> IDocumentStore
  ...

--------------------
DocumentStore() : DocumentStore
property DataConfig.Urls: string []
TinyIoc.TinyIoCContainer.Register<'RegisterType,'RegisterImplementation (requires reference type and reference type and 'RegisterImplementation :> 'RegisterType)>() : TinyIoc.TinyIoCContainer.RegisterOptions
   (+0 other overloads)
TinyIoc.TinyIoCContainer.Register<'RegisterType (requires reference type)>() : TinyIoc.TinyIoCContainer.RegisterOptions
   (+0 other overloads)
TinyIoc.TinyIoCContainer.Register<'RegisterType (requires reference type)>(factory: Func<TinyIoc.TinyIoCContainer,TinyIoc.NamedParameterOverloads,'RegisterType>) : TinyIoc.TinyIoCContainer.RegisterOptions
   (+0 other overloads)
TinyIoc.TinyIoCContainer.Register<'RegisterType,'RegisterImplementation (requires reference type and reference type and 'RegisterImplementation :> 'RegisterType)>(instance: 'RegisterImplementation) : TinyIoc.TinyIoCContainer.RegisterOptions
   (+0 other overloads)
TinyIoc.TinyIoCContainer.Register<'RegisterType (requires reference type)>(instance: 'RegisterType) : TinyIoc.TinyIoCContainer.RegisterOptions
   (+0 other overloads)
TinyIoc.TinyIoCContainer.Register<'RegisterType,'RegisterImplementation (requires reference type and reference type and 'RegisterImplementation :> 'RegisterType)>(name: string) : TinyIoc.TinyIoCContainer.RegisterOptions
   (+0 other overloads)
TinyIoc.TinyIoCContainer.Register<'RegisterType (requires reference type)>(name: string) : TinyIoc.TinyIoCContainer.RegisterOptions
   (+0 other overloads)
TinyIoc.TinyIoCContainer.Register(registerType: Type) : TinyIoc.TinyIoCContainer.RegisterOptions
   (+0 other overloads)
TinyIoc.TinyIoCContainer.Register<'RegisterType (requires reference type)>(factory: Func<TinyIoc.TinyIoCContainer,TinyIoc.NamedParameterOverloads,'RegisterType>, name: string) : TinyIoc.TinyIoCContainer.RegisterOptions
   (+0 other overloads)
TinyIoc.TinyIoCContainer.Register<'RegisterType,'RegisterImplementation (requires reference type and reference type and 'RegisterImplementation :> 'RegisterType)>(instance: 'RegisterImplementation, name: string) : TinyIoc.TinyIoCContainer.RegisterOptions
   (+0 other overloads)
type IDocumentStore =
  inherit IDisposalNotification
  inherit IDisposable
  member AggressivelyCache : ?database:string -> IDisposable
  member AggressivelyCacheFor : cacheDuration:TimeSpan * ?database:string -> IDisposable + 1 overload
  member BulkInsert : ?database:string * ?token:CancellationToken -> BulkInsertOperation
  member Certificate : X509Certificate2
  member Changes : ?database:string -> IDatabaseChanges + 1 overload
  member Conventions : DocumentConventions
  member Database : string with get, set
  member DisableAggressiveCaching : ?database:string -> IDisposable
  member ExecuteIndex : task:AbstractIndexCreationTask * ?database:string -> unit
  member ExecuteIndexAsync : task:AbstractIndexCreationTask * ?database:string * ?token:CancellationToken -> Task
  ...
DocumentStore.Initialize() : IDocumentStore
val ignore : value:'T -> unit
type IndexCreation =
  static member CreateIndexes : assemblyToScan:Assembly * store:IDocumentStore * ?conventions:DocumentConventions * ?database:string -> unit + 1 overload
  static member CreateIndexesAsync : assemblyToScan:Assembly * store:IDocumentStore * ?conventions:DocumentConventions * ?database:string * ?token:CancellationToken -> Task + 1 overload
IndexCreation.CreateIndexes(indexes: IEnumerable<AbstractIndexCreationTask>, store: IDocumentStore,?conventions: Conventions.DocumentConventions,?database: string) : unit
IndexCreation.CreateIndexes(assemblyToScan: Reflection.Assembly, store: IDocumentStore,?conventions: Conventions.DocumentConventions,?database: string) : unit
val typeof<'T> : Type
val __ : TresBootstrapper
val app : IApplicationBuilder
type IApplicationBuilder =
  member ApplicationServices : IServiceProvider with get, set
  member Build : unit -> RequestDelegate
  member New : unit -> IApplicationBuilder
  member Properties : IDictionary<string, obj>
  member ServerFeatures : IFeatureCollection
  member Use : middleware:Func<RequestDelegate, RequestDelegate> -> IApplicationBuilder
(extension) IApplicationBuilder.UseOwin() : Action<Func<Func<IDictionary<string,obj>,Threading.Tasks.Task>,Func<IDictionary<string,obj>,Threading.Tasks.Task>>>
(extension) IApplicationBuilder.UseOwin(pipeline: Action<Action<Func<Func<IDictionary<string,obj>,Threading.Tasks.Task>,Func<IDictionary<string,obj>,Threading.Tasks.Task>>>>) : IApplicationBuilder
val x : Action<Func<Func<IDictionary<string,obj>,Threading.Tasks.Task>,Func<IDictionary<string,obj>,Threading.Tasks.Task>>>
(extension) Action.UseNancy(action: Action<NancyOptions>) : Action<Func<Func<IDictionary<string,obj>,Threading.Tasks.Task>,Func<IDictionary<string,obj>,Threading.Tasks.Task>>>
(extension) Action.UseNancy(?options: NancyOptions) : Action<Func<Func<IDictionary<string,obj>,Threading.Tasks.Task>,Func<IDictionary<string,obj>,Threading.Tasks.Task>>>
val opt : NancyOptions
property NancyOptions.Bootstrapper: Bootstrapper.INancyBootstrapper
Fork me on GitHub