From 7be81e3e4b60d99380c694279a1dd8cbe6b13f66 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Wed, 18 May 2022 12:13:32 -0400 Subject: [PATCH] Beta release prep - Modify retry logic to always attempt a reconnect --- README.md | 18 ++-- src/RethinkDb.Driver.FSharp/README.md | 80 ++++++++++++++++++ .../RethinkDb.Driver.FSharp.fsproj | 11 ++- src/RethinkDb.Driver.FSharp/Retry.fs | 9 +- src/RethinkDb.Driver.FSharp/icon.png | Bin 0 -> 15989 bytes 5 files changed, 98 insertions(+), 20 deletions(-) create mode 100644 src/RethinkDb.Driver.FSharp/README.md create mode 100644 src/RethinkDb.Driver.FSharp/icon.png diff --git a/README.md b/README.md index 9a6db7d..3e34663 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,11 @@ Idiomatic F# extensions for the C# RethinkDB driver ## Using -Install the [NuGet](https://www.nuget.org/packages/RethinkDb.Driver.FSharp/) package `RethinkDb.Driver.FSharp`. You will need to specify pre-release, as the package has an alpha designation at this time. +Install the [NuGet](https://www.nuget.org/packages/RethinkDb.Driver.FSharp/) package `RethinkDb.Driver.FSharp`. You will need to specify pre-release, as the package currently has a beta designation. -## Goals +## What It Provides -The goal is to provide: -- A composable pipeline for creating ReQL statements: +### A composable pipeline for creating ReQL statements ```fsharp open RethinkDb.Driver.FSharp.Functions @@ -24,7 +23,7 @@ let fetchPost (postId : string) = |> withRetryDefault ``` -- An F# domain-specific language (DSL) using a `rethink` computation expression (CE): +### An F# domain-specific language (DSL) using a `rethink` computation expression (CE) ```fsharp open RethinkDb.Driver.FSharp @@ -39,7 +38,7 @@ let fetchPost (postId : string) = } ``` -- A standard way to translate JSON into a strongly-typed configuration: +### A standard way to translate JSON into a strongly-typed configuration ```fsharp /// type: DataConfig @@ -55,15 +54,15 @@ let conn = config.Connect () let! post = fetchPost "the-post-id" conn ``` -- Robust queries +### Robust queries The RethinkDB connection is generally stored as a singleton. Over time, this connection can lose its connection to the server. Both the CE and functions have `withRetryDefault`, which will retry a failed command up to 3 times (4 counting the initial try), waiting 200ms, 500ms, and 1 second between the respective attempts. There are other options as well; `withRetryOnce` will retry one time immediately. `withRetry` takes a list of `float`s, which will be interpreted as seconds to delay between each retry; it will retry until it has exhausted the delays. The examples above both use the default retry logic. -- Only rename functions/methods where required +### Only rename functions/methods where required -Within the CE, there are a few differing names, mostly notably at the start (selecting databases and tables); this is to allow for a more natural language flow. Its names may change in the 0.8.x series; it is the most alpha part of the project at this point. Also, while CEs now support overloading (thank you F# 6 developers!), they do not detect if the first value in the tupled arguments is different. This is most noticeable once `result*` or `write*` commands have been issued; these support `Task<'T>`, `Async<'T>`, and synchronous `'T` operations, but the follow-on commands will be different (e.x. `withRetryDefault` (tasks) vs. `withAsyncRetryDefault` vs. `withSyncRetryDefault`). There are also versions of these that support optional arguments (for all) and cancellation tokens (for task/async). +Within the CE, there are a few differing names, mostly notably at the start (selecting databases and tables); this is to allow for a more natural language flow. Also, while CEs now support overloading (thank you F# 6 developers!), they do not detect if the first value in the tupled arguments is different. This is most noticeable once `result*` or `write*` commands have been issued; these support `Task<'T>`, `Async<'T>`, and synchronous `'T` operations, but the follow-on commands will be different (e.x. `withRetryDefault` (tasks) vs. `withAsyncRetryDefault` vs. `withSyncRetryDefault`). There are also versions of these that support optional arguments (for all) and cancellation tokens (for task/async). The functions show this pattern throughout, as functions in a module do not support overloading; an example for `filter` is below. @@ -88,4 +87,3 @@ license on this project's dependencies. Please see [the heading on the C# driver If you are using the project, feel free to file issues about your pain points; there is no substitute for real-world feedback! [license]: https://github.com/bchavez/RethinkDb.Driver#open-source-and-commercial-licensing -[nuget]: https://ci.appveyor.com/nuget/danieljsummers-rethinkdb-driver-fsharp \ No newline at end of file diff --git a/src/RethinkDb.Driver.FSharp/README.md b/src/RethinkDb.Driver.FSharp/README.md new file mode 100644 index 0000000..515ea1f --- /dev/null +++ b/src/RethinkDb.Driver.FSharp/README.md @@ -0,0 +1,80 @@ +## RethinkDb.Driver.FSharp + +This package provides idiomatic F# extensions on the [official C# driver][csharp-pkg]. Within this package: + +### Connection Configuration / Creation + +```fsharp +open RethinkDb.Driver.FSharp + +let dataCfg = DataConfig.fromJson "rethink-config.json" +// - or - +let dataCfg = DataConfig.fromConfiguration [config-section] + +let conn = dataCfg.CreateConnection () // IConnection +``` + +### Domain-Specific Language (DSL) / Computation Expression (CE) Style + +```fsharp +open RethinkDb.Driver.FSharp + +// Remove the conn parameter and usage for point-free style + +let getPost postId conn = + rethink { + fromTable "Post" + get postId + resultOption + withRetryOptionDefault conn + } + +let updatePost post conn = + rethink { + fromTable "Post" + get post.id + update post + write + ignoreResult + withRetryDefault conn + } +``` + +### Function Style + +```fsharp +open RethinkDb.Driver.FSharp.Functions + +// NOTE: this returns Task; checking for null/option is not handled +// as it is with the CE version +let getPost postId conn = + fromTable "Post" + |> get postId + |> runResult + |> withRetryDefault conn + +// NOTE: this returns Task; ignoring inline is not available as +// it is with the CE version +let updatePost post conn = + fromTable "Post" + |> get post.id + |> update post + |> runWrite + |> withRetryDefault conn +``` + +### Retry Logic + +The driver does not reconnect automatically when the underlying connection has been interrupted. When specified, the retry logic attempts to reconnect; default retries wait 200ms, 500ms, and 1 second. There are also functions to retry once, and those that allow the intervals to be specified. + +### Strongly-Typed Optional Arguments + +Many RethinkDB commands support optional arguments to tweak the behavior of that command. A quick example using the `between` command (clause): + +```fsharp +// ... + between 1 100 [ LowerBound Open; UpperBound Closed ] +// ... +``` + +[csharp-pkg]: https://www.nuget.org/packages/RethinkDb.Driver/ \ No newline at end of file diff --git a/src/RethinkDb.Driver.FSharp/RethinkDb.Driver.FSharp.fsproj b/src/RethinkDb.Driver.FSharp/RethinkDb.Driver.FSharp.fsproj index e4da046..4bf73a3 100644 --- a/src/RethinkDb.Driver.FSharp/RethinkDb.Driver.FSharp.fsproj +++ b/src/RethinkDb.Driver.FSharp/RethinkDb.Driver.FSharp.fsproj @@ -6,13 +6,14 @@ Daniel J. Summers Apache-2.0 https://github.com/danieljsummers/RethinkDb.Driver.FSharp - + https://github.com/danieljsummers/RethinkDb.Driver.FSharp/raw/main/pkg/icon.png + icon.png + README.md false See LICENSE RethinkDB document F# - 0.8.0 - alpha-0009 - Alpha; use at your own risk + 0.9.0 + beta-01 @@ -21,6 +22,8 @@ + + diff --git a/src/RethinkDb.Driver.FSharp/Retry.fs b/src/RethinkDb.Driver.FSharp/Retry.fs index c19eb7c..1aee920 100644 --- a/src/RethinkDb.Driver.FSharp/Retry.fs +++ b/src/RethinkDb.Driver.FSharp/Retry.fs @@ -13,12 +13,9 @@ let retryPolicy (intervals : float seq) (conn : IConnection) = .WaitAndRetryAsync( intervals |> Seq.map TimeSpan.FromSeconds, System.Action (fun ex _ _ _ -> - printf $"Encountered RethinkDB exception: {ex.Message}" - match ex.Message.Contains "socket" with - | true -> - printf "Reconnecting to RethinkDB" - (conn :?> Connection).Reconnect false - | false -> ())) + printfn $"Encountered RethinkDB exception: {ex.Message}" + printfn "Reconnecting to RethinkDB..." + (conn :?> Connection).Reconnect false)) /// Create a retry policy that attempts to reconnect to RethinkDB when a synchronous operation encounters an error let retryPolicySync (intervals : float seq) (conn : IConnection) = diff --git a/src/RethinkDb.Driver.FSharp/icon.png b/src/RethinkDb.Driver.FSharp/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..08362a96477fc34ede3b14522f8e11616ff661c9 GIT binary patch literal 15989 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 z%0D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