七爪网源码交易平台:七爪源码F
七爪网源码交易平台:七爪源码F.AddSingleton<IRavenClientFactory RavenClientFactory>(fun sp -> let configurationSection = config.GetSection("Raven") let url = configurationSection.GetValue("Url") let dbName = configurationSection.GetValue("Database") new RavenClientFactory([ url ] dbName) )从 RavenDB 读取module RavenDb open Raven.Client.Documents open Raven.Client.Json.Serialization.Newt
几年前,我使用了 RavenDB,这是一次很好的体验 - 从那以后它才突飞猛进。 我曾经使用过很多 MongoDB,我厌倦了“SQL 体验”,需要更好的面向 OOP 的东西。 但现在我是 FP 和 DDD,SQL 不会削减它,Mongo 一直对我不利。
RavenDB 入门
首先,我们需要一个本地环境
docker run --rm -d -p 9010:8080 -p 38888:38888 --name ravendb -e RAVEN_Setup_Mode=None -e RAVEN_License_Eula_Accepted=true -e RAVEN_Security_UnsecuredAccessAllowed=PrivateNetwork ravendb/ravendb
那里有一些环境变量,它们是必需的。 RAVEN_Setup_Mode=None 这会在设置过程中选择安全性,本质上是要求 RavenDB 在没有设置过程的情况下启动 - 删除,并且每次创建容器时都必须通过设置向导,这对于自动化来说很痛苦。
RAVEN_License_Eula_Accepted=true 再次,另一个设置过程。 RAVEN_Security_UnsecuredAccessAllowed=PrivateNetwork 这是因为 TLS 和证书身份验证默认开启,这使得服务器无需身份验证即可访问。
连接到 RavenDB
有一个 C# 示例,我想出的是:
module RavenDb
open Raven.Client.Documents
open Raven.Client.Json.Serialization.NewtonsoftJson
type IRavenClientFactory =
abstract member Create : unit -> IDocumentStore
type RavenClientFactory (urls databaseName) =
let lazyClient = Lazy<IDocumentStore>(fun () ->
let s = new DocumentStore(
Urls = (urls |> List.toArray)
Database = databaseName
)
s.Conventions.Serialization <- (NewtonsoftJsonSerializationConventions(
CustomizeJsonSerializer = (fun serializer -> ())
CustomizeJsonDeserializer = (fun serializer -> ())
))
s.Initialize()
)
interface IRavenClientFactory with
member this.Create() = lazyClient.Value
interface IDisposable with
member this.Dispose() =
lazyClient.Value.Dispose()
我使用类和接口,因为 DI 仍然很简单,而且我在 F# 中使用 - 无需搞乱 monad 并陷入多个 monad 地狱。
.AddSingleton<IRavenClientFactory RavenClientFactory>(fun sp ->
let configurationSection = config.GetSection("Raven")
let url = configurationSection.GetValue("Url")
let dbName = configurationSection.GetValue("Database")
new RavenClientFactory([ url ] dbName)
)
从 RavenDB 读取
我只为阅读和写作创建了一个简单的包装器。 无非就是处理稍后出现的额外正交问题。
module RavenDbRepository
open Raven.Client.Documents
open Raven.Client.Documents.BulkInsert
open Raven.Client.Documents.Linq
type IRavenRepository<'a> =
abstract member Load : id: string -> ct: CancellationToken -> Task<Result<'a ErrorResponse<string>>>
type RavenRepository<'a>(factory: IRavenClientFactory) =
interface IRavenRepository<'a> with
member this.Load id ct =
let client = factory.Create()
use session = client.openAsyncSession()
let! data = session.LoadAsync<'a>(id = id token = ct)
return match data :> obj with
| null -> DatabaseErrorResponse (EntryNotFound id) |> Error
| _ -> Ok data
不用担心 ErrorResponse<'t> ,它只是一个有区别的联合,允许我将异常和其他事件转化为有用的信息,以便在 API 边界上理解。这样如果是暂时性错误,我可以返回 500 并要求客户端重试或其他什么……
这没有什么太令人惊奇的,只是非常标准的东西。
有趣的时间
因此,让我们深入探讨一些更有趣的方面。
- 我想要自动创建数据库
- 与域对象和值对象的无缝集成
自动创建数据库
为什么?在我的本地开发过程中,我在计算机之间切换,有时我使用我的“大型”计算机 - 也就是塔式 PC - 有时我喜欢一边写代码一边坐着看电视 - 就像我现在在写这篇博客时所做的那样。每次,我都希望我的数据库和我的 API/网站的其他资源能够自动生成它们的种子数据等……
事实证明,在 RavenDB 中,创建新数据库有点麻烦。与 SQL 或 Mongo 不同 - 只需触发查询将创建数据库或自动生成集合 - RavenDB 需要您与维护 API 进行交互。在该链接中,您将看到 C# 示例,让我们让它变得时髦。
module RavenDbRepository
open System
open System.Threading
open System.Threading.Tasks
open Raven.Client.Documents
open Raven.Client.Documents.BulkInsert
open Raven.Client.Documents.Linq
open Raven.Client.Exceptions
open Raven.Client.Json.Serialization.NewtonsoftJson
open Raven.Client.ServerWide.Operations
open Raven.Client.ServerWide
open Raven.Client.Exceptions.Database
open Raven.Client.Documents.Operations
open FsToolkit.ErrorHandling
open Microsoft.AspNetCore.Hosting
open Microsoft.Extensions.Hosting
type IRavenClientFactory =
abstract member Create : unit -> IDocumentStore
abstract member InitialiseDatabase : env: IWebHostEnvironment -> Task
type RavenClientFactory (urls databaseName) =
let lazyClient = Lazy<IDocumentStore>(fun () ->
let s = new DocumentStore(
Urls = (urls |> List.toArray)
Database = databaseName
)
s.Conventions.Serialization <- (NewtonsoftJsonSerializationConventions(
CustomizeJsonSerializer = (fun serializer -> ())
CustomizeJsonDeserializer = (fun serializer -> ())
))
s.Initialize()
)
let checkForDatabase () = task {
let store = lazyClient.Value
try
let! _ = store.Maintenance.ForDatabase(databaseName).SendAsync(GetStatisticsOperation())
return Ok ()
with
| :? DatabaseDoesNotExistException ->
return Error ()
}
let tryCreateDatabase () =
Retries.createPolicy<RavenException> 20
|> Retries.executeCustom CancellationToken.None (fun _ -> task {
try
let store = lazyClient.Value
do! store.Maintenance.Server.SendAsync(CreateDatabaseOperation(DatabaseRecord(databaseName))) :> Task
with
| :? ConcurrencyException ->
do! Task.CompletedTask
})
let createIfNotExists result : Task = task {
match result with
| Ok () -> return! Task.CompletedTask
| _ -> do! tryCreateDatabase ()
}
interface IRavenClientFactory with
member this.Create() = lazyClient.Value
member this.InitialiseDatabase env: Task = task {
if env.IsDevelopment() then
let! r = checkForDatabase ()
do! createIfNotExists r
}
interface IDisposable with
member this.Dispose() =
lazyClient.Value.Dispose()
在这里,你会看到 Retries,它是我写的 Polly 的一个小包装。
这从返回 Result<unit unit> 的 checkForDatabase 开始,本质上如果它返回 Error () 那么我们需要创建一个数据库。
然后我们 createIfNotExists -> tryCreateDatabase 。 tryCreateDatabase 触发 CreateDatabaseOperation 。 如果这引发了 ConcurrencyException 异常,那么我们可以忽略,因为它已经在其他地方创建。 如果异常是 RavenException,那么很可能是因为服务器还没有准备好,只需要重试直到成功。 它在 20 次重试中从未失败,所以这似乎是合理的。
env 是一个 IWebHostEnvironment ,这样我就可以检查我们是否处于开发模式。 像这样完成 ravenClientFactory.InitialiseDatabase env 。
所以,就是这样。
关注七爪网,获取更多APP/小程序/网站源码资源!