Sharing code with an OCaml native backend


#1

We recently switched our frontend to bucklescript from Elm. The result is a lot of types and code that is similar but not quite the same as our backend, which is in OCaml.

If you’re in a similar situation, what have you done to avoid duplicating code between your OCaml backend and bucklescript frontend? I’m esp interested in how you laid out your code to be shared between their frontend and backend (directory structure, build scripts and compiler incantations), how you dealt with types that were almost but not quite the same, anything to do with PPXes), etc. Thanks!


#2

My web-app server is Rails, but it needs to execute Reason code (pure, stateless) for certain requests. This code is wrapped in an HTTP endpoint with bs-express and runs on Node on the same box. The Rails server talks to it locally.

The web UI is part of the Rails codebase, and it is written in ReasonReact. There is also a web-worker that is almost identical to the Node server. But this one runs on the browser.

All three of them - Node, webworker, and ReasonReact UI - relies on a common Reason codebase.

It is structured like this:

rails/
   bsconfig.json
   app/javascript
     web-ui/
     common-core/
   node/
     bsconfig.json

The Node source tree is within the Rails directory. It is a thin folder with just an HTTP entrypoint. All other code is in common-core. Its bsconfig.json looks like this:

 "sources": [
    ...
    {
      "dir": "../app/javascript/common-core",
      "subdirs": true
    }
  ]

This required a non-standard Rails deployment: a single git repo that serves two applications. But no other hacks.

Modules in common-core serialize and deserialize data with bs-json. This is possible since all Reason code runs on a Javascript runtime - either on Node or browser. This means I don’t have to worry about keeping two schemas in sync, which would’ve been the case if I had opted for OCaml and atdgen instead of Node. But @Khady recently wrote a BuckleScript backend for atdgen (https://github.com/ahrefs/bs-atdgen-codec-runtime) so this might no longer be a concern.

I would’ve liked to switch to native OCaml instead of Node on the backend, but it is unclear how I can use the same unmodified code and standard library on both OCaml and Javascript runtimes. This setup has worked well so far - it is really nice to be able to use the exact same types and code everywhere and not duplicate anything.


#3

I’m not really the author of this code. Just took care of moving it from Ahrefs’ private repo to github. But yes, it solves the problem.

At Ahrefs we are using it to avoid code and type duplication between frontend and backend. We have a big set of atd files that are used to define the messages that each backend endpoint accept and return. It works very well. We should probably write a blog post about it at some point.


#4

Do you know when an official version is going to be released? I see a few issues on GitHub that are discouraging me from using it in production.


#5

do you mean atdgen or bs-atdgen-codec-runtime? Both of them are totally usable in the current state and got release already. If anything is missing or must be fixed don’t hesitate to open issues.


#6

I didn’t realize that atgden released the BS backend. As for issues, I just saw a couple of possibly concerning ones in the atd project.


#7

Author of bsb-native here. I’d use bsb-native’s ability to specify which folder builds to what target (JS or native) and then use the compile time flag in a shared module to include the web module when #if backend = “js” etc. Here’s an example: https://github.com/Schmavery/reprocessing/blob/master/src/Reprocessing_Hotreload.ml
And the bsconfig: https://github.com/Schmavery/reprocessing/blob/master/bsconfig.json#L12


#8

I’m a little confused as to the difference/relationship between esy and bsb-native. Could you clarify? Our backend uses dune and OCaml 4.07 - can we (should we?) use bsb-native with that?


#9

esy is a package manager. dune and bsb-native are build systems. I strongly recommend using dune.