Typed Hack of Cloudant using F#
Learn how to bridge typed F# with untyped Cloudant NoSQL databases. Discover a practical helper module for seamless data access in under 100 lines.
There is a continuous war between typed and untyped data. But then there is a need for finding a middle ground for ever-growing applications. There is a place, a requirement when I feel an application should be typed and the data store should be untyped. So, I created this little code snippet to access Cloudant (a CouchBase fork) NoSQL database.
I have known about Cloudant from long back. Even before IBM bought it. It is indeed a wonderful service and also kind of free for small stuff. I thought this would be the best fit for now. (I still don't know why I didn't use MongoDB).
As there is no need for scary relationships with the database, I decided to use this one.
Now, as one problem is solved, another started. It is saving data that is in JSON format. That is untyped, and F# is statically typed. I have tried a few libraries developed in C# but was not happy. So, I thought it is just an HTTP request—why shouldn't I give it a shot to make one of my own helper modules? It may be somewhat of a dirty attempt, but I got it working with what I needed in less than 100 lines. Here are the code snippets.
#r "../packages/Http.fs.1.4.0/lib/net40/HttpClient.dll"
#r "../packages/Newtonsoft.Json.6.0.6/lib/net45/Newtonsoft.Json.dll"
open HttpClient
open Newtonsoft.Json
open Newtonsoft.Json.Linq
[<CLIMutableAttribute>]
type Row<'a> =
{ id : string
key : string
value : 'a }
[<CLIMutableAttribute>]
type ResultSet<'a> =
{ total_rows : int
offset : int
rows : Row<'a> [] }
[<CLIMutableAttribute>]
type PostResult =
{ ok : string
id : string
rev : string }
[<CLIMutableAttribute>]
type PostError =
{ error : string
reason : string }
let cloudantUrl = @"<cloudanturl/databasename/>"
let username = @"<username>"
let password = @"<password>"
let private cloudantGet url =
let request =
createRequest Get url
|> withBasicAuthentication username password
|> withHeader (ContentType "application/json")
request |> getResponseBody
let private cloudantPost url data =
let request =
createRequest Post url
|> withBasicAuthentication username password
|> withBody data
|> withHeader (ContentType "application/json")
request |> getResponseBody
let private checkDataForNewId (data : JObject) =
let removeIdrev (data : JObject) =
data.Remove("_id") |> ignore
data.Remove("_rev") |> ignore
if System.String.IsNullOrEmpty(data.["_id"].ToString()) || System.String.IsNullOrEmpty(data.["_rev"].ToString()) then
removeIdrev data
data
let PostJson<'a> data =
let serializedObject = JObject.FromObject(data) |> checkDataForNewId
serializedObject.Add("$doctype", JToken.Parse("'" + data.GetType().Name + "'"))
cloudantPost cloudantUrl <| serializedObject.ToString()
let GetJsonByType<'a> =
let resultset =
JsonConvert.DeserializeObject<ResultSet<'a>>
(cloudantGet (cloudantUrl + "/_design/Type/_view/" + typeof<'a>.Name))
query {
for row in resultset.rows do
select row.value
}
let GetJsonById<'a> Id = JsonConvert.DeserializeObject<'a>(cloudantGet (cloudantUrl + Id))
[<CLIMutableAttribute>]
type Person =
{ _id : string
_rev : string
FirstName : string
LastName : string }
let newPerson =
{ _id = ""
_rev = ""
FirstName = "Boom"
LastName = "Baam" }
let inline isNull (x:^a when ^a : not struct) =
obj.ReferenceEquals (x, Unchecked.defaultof<_>)
let findPerson =
query {
for p in GetJsonByType<Person> do
where (p.FirstName = "Boom")
select p
headOrDefault
}
isNull findPerson
GetJsonById<Person> ("3b389dc6b8ee0dcbf7f366faaa59cf42")
The above code below this part is just for testing. And even with that, the code snippet is 98 lines. So, with blank lines removed, it is even shorter.
Now, in Cloudant I need to create views, so the code is like:
function(doc) {
if (doc.$doctype !== "Person") return;
var copydoc = JSON.parse(JSON.stringify(doc));
delete copydoc["$doctype"];
emit(doc._id,copydoc);
}
I need $doctype while I am inserting or updating data only, not while reading. So, I am removing it since I am already filtering based on type.
In the above code, two libraries are being used: one is Http.fs and the other is JSON.net.
In the library, as you can see, I am not doing anything special. I am inserting data with type information. If new data is there, I am removing _id and _rev, and for simplicity, I have put _id and _rev in all the types I am using to interact with Cloudant data store.
So, now whenever I am reading, I just need to give the type, and that will be fetched as a collection of that typed record. Once the collection comes to memory, I have the powerful F# to process data. In the above code, I am using a query expression to do so. It becomes very easy and fun to use. And it is damn fast. At least for now.
Now, I don't know if it is perfect or not. It is kind of part functional and part object-oriented, as far as I know. Should I convert this to a Type Provider, or is it possible or not? I don't know. But one thing is sure: I am achieving what I wanted with the simplest possible code.
Dear F# community members, do provide your views on this. And also let me know if it will be useful to make it more mature and push it as a NuGet package. Or maybe a Type Provider for Cloudant?
Highly inspired by Daniel Mohl's old library FSharpCouch. Thank you... :)
Frequently Asked Questions
The author found existing C# libraries unsatisfactory for their needs. Since Cloudant is accessed via HTTP requests, they created a custom helper module in under 100 lines of F# code that perfectly fit their requirements without unnecessary overhead.
F# uses type definitions with CLIMutable attributes to map untyped JSON responses into strongly-typed records. This approach bridges the gap between Cloudant's untyped JSON format and F#'s static typing system, providing type safety while working with NoSQL data.
The code uses basic authentication with username and password credentials. These credentials are applied to each HTTP request to Cloudant via the withBasicAuthentication function, securing access to your database.
This approach works well when your application doesn't require complex relationships between data entities, making a NoSQL database suitable. It's ideal for scenarios where you want the flexibility of NoSQL while maintaining type safety in your application code.
The checkDataForNewId function removes internal Cloudant fields (_id and _rev) if they're empty, indicating a new record. This ensures proper handling of both new document creation and updates to existing documents in Cloudant.