Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Hi, I am the author of this project.

The choice to typecheck a SQL dialect directly instead of using an embedded DSL like LINQ is uncommon enough that it gets the majority of the focus in my own documentation. However, it wasn't what motivated me to create the project.

I wanted to have an ORM that could take full advantage of my resumption monad library, Rezoom. This library is similar in concept to Facebook's Haxl or Dataloader, in that it can combine independent requests into batches and cache ones that have already been resolved.

The main difference (besides being F#) is that it is intended to be used for mutations, not just queries. This is a bit tricky because when you mutate the stuff in the database, that may invalidate the cached results of previous queries. The problem isn't too bad since, as with Haxl and Dataloader, the cache is per-request so it's very short lived, but it could still cause problems. Contrived example:

  // domain layer line item logic, includes business rules
  // returns the updated invoice
  let addLineItem invoiceId lineItem =
    plan {
      // query for the invoice
      let! invoice = DB.getInvoiceById invoiceId
      if invoice.IsSent then
        failwith "Can't edit this invoice because it has been sent"
      // run INSERT/UPDATE statement(s)
      do! DB.addInvoiceLineItem invoiceId lineItem
      // re-query for the invoice to get its current state
      return! DB.getInvoiceById invoiceId
    }
`DB.addInvoiceLineItem` needs to invalidate the cached result of `DB.getInvoiceById`. Otherwise this code won't return the updated invoice, but the invoice before updating. Again, contrived example, but when the "reload the invoice" line is in a whole other function somewhere else it could be a real problem.

The way Rezoom solves this is to have "errands" (its word for a batchable data dependency) specify 2 128-bit masks. One represents its dependencies, the other represents its invalidations. When you run a command with a non-zero invalidation mask, all the cached results whose dependency bits overlap with the invalidation bits get evicted.

The caches are local to a "category" which is pretty much just a tag for which library created the errand, so that the errands wrapping, say, a web API don't stomp on the caches for the errands talking to a SQL database (actually the connection string is involved too so you can deal with multiple databases from one app). For the errands generated by Rezoom.SQL, each bit in the mask represents one table, so running an UPDATE/INSERT/DELETE invalidates the cache for all commands that read from the affected tables. If you have more than 128 tables in your database the IDs wrap around so you'll get some false invalidations possibly hurting performance, but never a false cached result hurting correctness.

This all could make it very easy to write a GraphQL server backed by Rezoom + Rezoom.SQL.



This is super awesome. Unfortunately in my company I haven't converted the mindset fully to F#, so I'm wondering, is there a good way to use this in C# somehow?


You can create an F# library which references the Rezoom.SQL.Provider NuGet package, defines your migrations and all your queries (`type MyQueryExample = SQL<"select whatever">`), and not really write any F# code. Because this project is a generative type provider, it makes real .NET types you can then reference from C# or VB.NET or whatever.

If your goal is just to run queries, either synchronously or as `Task<T>`s, that'll do just fine. If you want the batching/caching stuff I described above, you'll want to build `Plan`s, which currently requires an F# computation expression.

However, in C# 7 I think it's possible to write plans with `async/await` syntax. I just need to write the AsyncMethodBuilder to support it. I've done it for F# asyncs here: https://github.com/rspeele/FSharpAsyncMethodBuilder

This would let you get the full batching/caching/transactional benefits of Rezoom even when writing a project that's 90% C#.


Yes, server-side .NET GraphQL does seem to be a missing piece right now!




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: