[ANN] Sihl 0.1.0

Hi all,

I am happy to announce this milestone release of Sihl, a web framework for OCaml.

Github: https://github.com/oxidizing/sihl
opam: http://opam.ocaml.org/packages/sihl/

Sihl is really just a collection of services that can be plugged into each other and a tiny core that knows how to start them. The goal is to take care of infrastructure concerns so you can focus on the domain.

After many iterations, the API is in a shape where we dare to show it to you :slight_smile:
It is still under heavy development so expect breakage without a major version bump. However, we just finished migrating a project from Reason on NodeJS to OCaml on Sihl, so we use it in production.

We provide service implementations that were useful to us so far. In the future we want to provide many more to cover all kinds of needs. (PRs are always welcome!)

Any feedback is greatly appreciated, thanks! :slight_smile:

23 Likes

Here is an example of a tiny Sihl app:

module Service = struct
  module Random = Sihl.Utils.Random.Service
  module Log = Sihl.Log.Service
  module Config = Sihl.Config.Service
  module Db = Sihl.Data.Db.Service
  module MigrationRepo = Sihl.Data.Migration.Service.Repo.MariaDb
  module Cmd = Sihl.Cmd.Service
  module Migration = Sihl.Data.Migration.Service.Make (Cmd) (Db) (MigrationRepo)
  module WebServer = Sihl.Web.Server.Service.Make (Cmd)
  module Schedule = Sihl.Schedule.Service.Make (Log)
end

let services : (module Sihl.Core.Container.SERVICE) list =
  [ (module Service.WebServer) ]

let hello_page =
  Sihl.Web.Route.get "/hello/" (fun _ ->
      Sihl.Web.Res.(html |> set_body "Hello!") |> Lwt.return)

let routes = [ ("/page", [ hello_page ], []) ]

module App = Sihl.App.Make (Service)

let _ = App.(empty |> with_services services |> with_routes routes |> run)
2 Likes

Congratulations on the release! The API looks composable, feature-rich and well-documented. I also like the fact that it’s based on Opium.

Am I right in assuming that services can be used independently from the rest of the framework?

2 Likes

Thank you!

Yes you are right, all you need is a dependency on Sihl.Core which is about 120 lines of code. Almost every feature is implemented as service, you can pick which ones you want to use.

We provide a convenience abstraction called “app” that has dependencies on services that are typically used in web apps. Apart from that, there is no “god” service that knows everyone else.

3 Likes

hey @jerben that’s great news

I have been exploring native OCaml/ReasonML , but am leaning more towards Node due to familiarity of the ecosystem

Have you considered writing a blog post about the transition from Node to native?
Have you measured the performance impact/difference?
Have you encountered unexpected slow downs, and would you recommend going this road to others?

1 Like

Hey @mudrz, familiarity with Node was the reason we went with Reason on Node as well. The plan was to get started quickly and migrate to OCaml native later on.

Not yet, but thanks for the idea! Thinking about the answers I found that there is quite some stuff to cover.

We haven’t measured performance yet, neither of Sihl nor of the project using Sihl.

A small anecdote:
One of the main reasons for the switch to native was the quality of packages. We used one of the big database driver implementations of MariaDB on NPM. After switching to the wonderful caqti library, we discovered a bug in our code which lead us to rethink one part of our architecture that gave us a big speed up in database reads. Turns out that database package on NPM behaved weirdly while caqti just told us that the usage is wrong.

If you consider the time to reload the JS files with something like nodemon after compilation, the feedback loop with native is slightly shorter. We didn’t measure, but BuckleScript and the compiler of OCaml 4.08.1 felt about the same.

Yes, the we struggled with some things but mainly due to the fact that we extracted the infrastructure code that became Sihl and did the migration from Reason on Node to OCaml at the same time.

We had to implement our own SMTP over Lwt library for instance and are facing similar issues with multipart file uploads at the moment.

If you are familiar with Node and functional programming and you have to deliver quickly, I would probably recommend to start with OCaml/Reason on Node using BuckleScript, because the output JS is human readable and you know exactly what is running on Node.

But if you have a couple of innovation points extra to spend and want to be super early adopter, I would go with OCaml directly. The ecosystem with regards to web development is getting better every day.

We are working on a Sihl starter project that can be cloned and that showcases a complete Sihl app in OCaml. This will probably cover standard stuff like DB (transactions, pooling), user management, emailing, HTTP routes, server side templates and maybe a simple SPA and some JSON routes using js_of_ocaml.

4 Likes

Hi, do you have a ticket open for multipart form uploads? I’ve implemented it for ReWeb, so I can probably comment there.

Hey @yawaramin thanks a lot for your offer,

Not yet, we wanted to start here and see what is already there. But the readme doesn’t sound too promising:

It works for strings, but has some problems with files.

Did you have a look at that one?

This would be really nice since as a newcomer to OCaml I have troubles mapping in my mind simple concepts from other languages into it. Things like DI and etc.

Indeed, that’s the one I’m using and I haven’t noticed any issues with it. Maybe for extremely large files there may be issues. My implementation is here https://github.com/yawaramin/re-web/blob/319ff1406b6cba15fc3a0e8f356404594c355242/ReWeb/Filter.ml#L237

There is another implementation that I believe uses bigstrings: https://github.com/dinosaure/multipart_form

I haven’t looked deeply into it though as it is slightly lower level. Maybe I should revisit it.

2 Likes

I recently updated the library and I think it will be ready for a beta release. If you have some issues, I can spend my time to improve the library again. Note that this library is currently used into some of my unikernels - and I did not get an error :slight_smile: .

The goal is to be independent from the HTTP server and it fits well with HTTP/AF. The main improvement is to process a multipart/form-data as a stream and avoid any problems about memory consumption of such process.

5 Likes

@jerben I tried adding an esy.json and it works great! I’ve raised a PR too.

I was on Windows when I tried this - do you think the underlying libraries work on Windows?

Links
Branch - https://github.com/ManasJayanth/sihl-starter/tree/prometheansacrifice/esy

2 Likes

That sounds really good. As long as multiple files work it is fine for our use case, we have a hard limit of 10MB per file. Thanks!

I didn’t know that one, I will check it out. Looking forward to the release. I take back that we might face major issues regarding multi form uploads :slight_smile:

Perfect thanks, merged.

I don’t know what it means to be able to run on Windows, but we use stuff from unix modules in the scheduler service, in the sendgrid email service and the static file middleware. It would be trivial to replace those with portable solutions, I see no reason why we couldn’t have a nice DX for Windows users out-of-the-box.

We are looking into replacing CircleCI with Github Actions so maybe it is easy to run the test suite on a Windows machine there?

2 Likes

I know @anmonteiro was doing some work on multipart parsing as well recently, probably makes sense to unite the efforts and have one library for multipart used across the OCaml/Reason HTTP ecosystem :slight_smile:

1 Like