Tres - Step 3
We'll start out, as we did with Dos, with adding the RavenDB dependency to paket.references
:
1:
|
|
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: |
|
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: |
|
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: |
|
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 open
ed; 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: |
|
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: |
|
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: |
|
Then, we'll create our bootstrapper above the definition for type Startup()
:
1: 2: 3: 4: 5: 6: 7: 8: 9: |
|
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: |
|
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.
val string : value:'T -> string
--------------------
type string = String
type JsonIgnoreAttribute =
inherit Attribute
new : unit -> JsonIgnoreAttribute
--------------------
JsonIgnoreAttribute() : JsonIgnoreAttribute
type RequireQualifiedAccessAttribute =
inherit Attribute
new : unit -> RequireQualifiedAccessAttribute
--------------------
new : unit -> RequireQualifiedAccessAttribute
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
Guid.ToString(format: string) : string
Guid.ToString(format: string, provider: IFormatProvider) : string
type FormatException =
inherit SystemException
new : unit -> FormatException + 2 overloads
--------------------
FormatException() : FormatException
FormatException(message: string) : FormatException
FormatException(message: string, innerException: exn) : FormatException
type Categories_ByWebLogIdAndSlug =
inherit AbstractJavaScriptIndexCreationTask
new : unit -> Categories_ByWebLogIdAndSlug
--------------------
new : unit -> Categories_ByWebLogIdAndSlug
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
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>
type TresBootstrapper =
inherit DefaultNancyBootstrapper
new : unit -> TresBootstrapper
member Configure : app:IApplicationBuilder -> unit
override ConfigureApplicationContainer : container:TinyIoCContainer -> unit
--------------------
new : unit -> TresBootstrapper
type DefaultNancyBootstrapper =
inherit NancyBootstrapperWithRequestContainerBase<TinyIoCContainer>
new : unit -> DefaultNancyBootstrapper
member GetEnvironment : unit -> INancyEnvironment
static val DefaultAutoRegisterIgnoredAssemblies : IEnumerable<Func<Assembly, bool>>
--------------------
DefaultNancyBootstrapper() : DefaultNancyBootstrapper
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, encoding: Text.Encoding) : string
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(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
{Url: string;
Database: string;}
member Urls : string []
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
(+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)
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
...
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(assemblyToScan: Reflection.Assembly, store: IDocumentStore,?conventions: Conventions.DocumentConventions,?database: string) : unit
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(pipeline: Action<Action<Func<Func<IDictionary<string,obj>,Threading.Tasks.Task>,Func<IDictionary<string,obj>,Threading.Tasks.Task>>>>) : IApplicationBuilder
(extension) Action.UseNancy(?options: NancyOptions) : Action<Func<Func<IDictionary<string,obj>,Threading.Tasks.Task>,Func<IDictionary<string,obj>,Threading.Tasks.Task>>>