A framework for statically typed functional web development in OCaml and Reason.
Explore the docs ยป
View Example Project
ยท
Report Bug
ยท
Request Feature
You want to skip the "blah blah" and go straight to the code? Get started here.
Let's start by clarifying what Sihl is not:
Sihl will not help you generate models, controllers and views quickly. It doesn't use convention over configuration and instead tries to be as explicit as necessary. We think the speedup of generating MVC and auto-wiring pales in comparison to the long-term maintanability concerns
Sihl encourages you to build things in a service-oriented way, but it's not a microservice framework that deals with problems of distributed systems. Use your favorite FaaS/PaaS/container orchestrator/micro-service toolkit to deal with that and Sihl for the business logic.
Now that we have that out of the way, let's have a look what Sihl is.
Sihl is a high-level application framework that can be used for web development. It provides a set of composable building blocks and recipes that allow you to develop (web) apps quickly but sustainably. Statically typed functional programming with OCaml makes web development fun and safe.
Things like database migrations, HTTP routing, user management, sessions, logging, emailing, job queues and schedules are just a few of all the topics Sihl takes care of.
Yes, because all other frameworks didn't grow here.
On a more serious note, originally we wanted to collect a set of services, libraries, best practices and architecture to quickly and sustainably spin-off our tools and product. An evaluation of languages and tools lead us to build the 5th iteration of what became Sihl with OCaml. We believe OCaml is a phenomenal host, even though its house of web development is small at the moment.
Sihl is built on OCaml because OCaml ...
But the final and most important reason is the module system, which gives Sihl its modularity and strong compile-time guarantees about a project setup. Sihl uses OCaml modules for statically typed dependency injection. If your app compiles, the dependencies are wired up correctly. You can not use what's not there.
Learn more about it in the concepts.
Following are the steps to quickly get started with a minimal running web server.
To initialize opam:
opam initTo install dune (the build system):
opam install duneopam install sihlLet's a simple Sihl app, that is a simple web app with a HTTP route.
We are using https://github.com/ocaml/dune to build the project. Create a dune file that specifies an executable depending on Sihl.
dune:
(executable
(name app)
(libraries
sihl
)
)A Sihl app requires at least two things: A minimal set of services (also called kernel services) for the app to run and the actual app definition.
Create the services file to statically set up the services and their dependencies that you are going to use in your project.
service.ml:
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)The app configuration file glues all the components together. In this example there is not much to glue except for the services we are going to use and two routes.
We want a simple web service without any database (and thus no migrations), so let's just include Service.WebServer.
app.ml:
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 hello_api =
Sihl.Web.Route.get "/hello" (fun _ ->
Sihl.Web.Res.(json |> set_body {|{"msg":"Hello!"}|}) |> Lwt.return)
let routes =
[ ("/page", [ hello_page ], []); ("/api", [ hello_api ], []) ]
module App = Sihl.App.Make (Service)
let _ =
App.(
empty
|> with_services services
|> with_routes routes
|> run
)You can build (and watch) this project with
dune build -wRun the executable to get a list of all available commands:
./_build/default/app.exeYou should see a start CLI command. This comes from Service.WebServer which is the only service we registered. Run the command with
./_build/default/app.exe startand visit http://localhost:3000/site/hello/ or http://localhost:3000/api/hello/.
Find a simple starter project here similar to our small example.
In essence, Sihl is just a tiny core (about 100 lines) that deals with loading services and their dependencies. Every feature is built using services.
A service is a unit that provides some functionality. Most of the time, a service is just a collection of functions that belong together. This would be the equivalent of a class with just static methods in object-oriented programming. However, some services can be started and stopped which gives them a lifecycle. Sihl makes sure that services with lifecycles are started and stopped in the right order.
Sihl provides service interfaces and some service implementations. As an example, Sihl provides default implementation of the user service for user management with support for MariaDB and PostgreSQL.
When you create a Sihl app, you usually start out with your service setup in a file service.ml. There, you list all services that you are going to use in the project. We can compose large services out of simple and small services using parameterized modules. This service composition is statically checked and you can use it throughout your project.
Sihl has to be made aware of the services you are going to use. That is why the second step of setting of services is done in the app description file.
A Sihl app is described in a app.ml. Here you glue services from service.ml, your own code and various other components together. It is the main entry point to your application.
Let's have a look at the folder structure of an example project called pizza-shop.
.
โโโ service
โ โโโ dune
โ โโโ service.ml
โโโ app
โ โโโ dune
โ โโโ app.ml
โโโ components
โ โโโ pizza-delivery
โ โ โโโ model.ml
โ โ โโโ service.ml
โ โ โโโ repo.ml
โ โโโ pizza-order-taking
โ โ โโโ model.ml
โ โ โโโ service.ml
โ โ โโโ repo.ml
โ โ โโโ cmd.ml
โโโ web
โ โโโ routes.ml
โ โโโ middlewares.ml
โโโ cli
โ โโโ cmd.mlThere is an emphasis on the separation of the business logic from web stuff. Your app is just a set of services, models and repositories and it is not concerned with HTTP, databases, CLIs or other infrastructure topics.
[TODO goe through folders]
See the open issues for a list of proposed features (and known issues).
[TODO]
[TODO]
[TODO]
[TODO]
[TODO]
[TODO]
[TODO]
[TODO]
[TODO]
[TODO]
[TODO]
[TODO]
[TODO]
[TODO]
[TODO]
[TODO]
[TODO long-term plan]
Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated. If you have any questions just contact us.
git checkout -b feature/amazing-feature)git commit -m 'Add some amazing feature)git push origin feature/amazing-feature)Copyright (c) 2020 Oxidizing Systems
Distributed under the MIT License. See LICENSE for more information.
Oxidizing Systems - @oxidizingsys - hello@oxidizing.io
Project Link: https://github.com/oxidizing/sihl
Sihl would not be possible without amazing projects like following: