IBM releases Elm-powered app


#1

Hi fellow Elmers,

IBM has just released Decision Composer, a free and limited offering of our Operational Decision Management (ODM - ex ILOG JRules) product.

A good portion of the front end is written in Elm, so I thought I’d share the link here, and post some feedback.

The app is live here (requires registration) :

https://decision-composer.ibm.com

It basically allows business users to model and implement complex Decisions via graphical tools and human-readable artifacts instead of code. Decisions are then compiled to executable artifacts, and deployed to runtimes for consumption in other applications/services.
It is based on the DMN standard, spiced up with our own BRMS expertise.

The app is a SPA that (in short) displays a diagram, and allows to edit various artifacts in the diagram nodes, like textual rules (with syntax highlighting, code completion etc) and Decision Tables (a la XL).

Most of the app is built over existing technology and components from the ODM product. The server relies on our Java APIs for many things (parsing, validation, compilation, etc), and the front is an Elm SPA that embeds some editors written in Dojo.

In the above screenshot, for example, the Diagram is a Dojo component, with its own dom tree (and svg), and the rest of the page is Elm. Same goes for rules and tables. We use parent-child for “pages” of the app as well as for some components that are reused across the app.

What we’ve learned

  • Functional programming ! The Elm language is a good introduction to FP, especially for people with an OO background like us. Only problem :

    • Tooling isn’t mature. IDE plugins, time travel “debugger” : they cannot compare yet with our usual Dev XP for TS, Java, etc.
  • TEA is pretty simple to understand, people get quickly used to it. You don’t wonder where things are or should be, everything has its well-defined place. There’s one way to do things, not 50. Still :

    • It requires a lot of boilerplate
    • It can be hard to scale / componentize (e.g. when to do parent-child ? how to reuse “components” in several apps ? …
    • Designing clean modules is not necessarily easy, and requires experience. We do have a few ugly Models here and there that need refactoring !
  • Elm is really bullet proof, it’s not fake advertisement. We have had no runtime errors, as promised. Refactoring is easy, with the compiler telling you just about everything. And yes, really, when it compiles, it works ! Apart from the quite slow 0.18 compiler (fixed in 0.19), it’s really amazing.

  • Elm can interop with “graphical” JS pretty easily. We have embedded pretty big components (like the diagram or the decision tables editor) into the Elm virtual DOM, and we interact with those through ports. It works, but :

    • Ports == lots of boilerplate and “loose” typing : we pass Values because we need to encode/decode ourselves… codegen to the rescue ?
    • Careful with the virtual DOM when dealing with native JS. Don’t touch vdom nodes that contain your “native” dom, make sure you properly destroy things, etc. All this requires work and attention, and bugs can be pretty hard to track down. Custom elements would probably have helped a lot here.
    • Sometimes you’d like to do synchronous calls, but you can’t. We wrote native modules for that. I know, booooo !
  • The build (elm make) is really simple. Compare this to your average WebPack “config”. And the UMD module can be consumed easily too : we include it into a dojo AMD layer build.

  • It doesn’t require to patch your code because there’s a new release every month. We migrated from 0.17 to 0.18 pretty easily. The 0.19 migration is a bit bigger, especially because we have a few native modules, but still, it’s nothing compared to the number of updates we would have had with React+Redux+TS.

It was quite a bet when we started with Elm (0.17). Many co-workers didn’t understand. Everyone was about React back then. Even Elm’s syntax was subject to religious debates. “What ? No JSX ??? Square brackets ??”…
Also having a big player like facebook on one side, and a weird student on the other, that didn’t help (with all my respect to you, sir Czaplicki, you are doing a fantastic job) !!

Eventually, as usual, it took some coding to convince : as soon as we started shipping things, most of the skepticism about Elm stopped.

Today, we, the developers of the app, think it’s been a good choice. The “framework” is simple and yet solid. It never really got into our way, instead it helped a lot to have this common architecture/design for everybody. We basically followed The Elm Architecture, and it worked. Of course, the app isn’t perfect, and there are many things we could do better, but it’s not because of the framework, it’s because of us, who didn’t model things correctly and stuff like that.

It took a little time to get used to Elm, especially for OO-raised people like me and most of my team. But it eventually paid off. Once you’ve understood the basic principles, you realize that coding in the browser can actually be productive, safe, and even fun.

I hope this post will help people who have problems convincing their management of using Elm, just because it’s not mainstream. Yes, you can use it for pretty complex apps, it does scale. And yes, even big companies like IBM use it !


#2

Thank you for sharing! This is very interesting feedback!

I have a bunch of questions, particularly about the rough spots you experienced. Knowing the particulars of your expectations and needs will be really valuable to figuring out how to make improvements strategically. I will message you soon to try to find a time to talk through things so I can make detailed notes.

Anyway, thanks again for sharing all this!


#3

Congrats on the release!

How big is the Elm part of the app?


#4

cloc gives approx 45.000 lines in 170 files of Elm, including the tests.


#5

How are you doing synchronous calls to Elm in 0.19?


#6

As far as I know, you can’t.

It was an error on our part to go for Native stuff in the first place. For 0.19, I guess that we’ll migrate those native modules either to Elm code, or to ports.


#7

I’ve done it by wrapping the Elm ports in a queue with a locking mechanism. That’s JS stuff I mean. Not on the Elm side.


#8

This is awesome! Curious, how long does it take to compile in 0.19?


#9

We have not yet migrated to 0.19, so I can’t say. I’m curious to see that as well !


#10

Can you share what needs do those synchronous calls fill?


#11

You might find my elm-typescript-interop library useful. It does exactly this, codegens a TypeScript declaration file based on the ports in your app. I discussed it in more depth at this year’s Elm Conf.

Great to hear your experience report, thanks for sharing that with the community!


#12

To be fair, most of our native modules could (should) have been either written in pure Elm, or implemented as ports. We have stuff like input validation (reusing some regexes etc), which we wanted to reuse back in a time when everybody wasn’t comfortable with Elm.

There’s one case that, as far as I know, cannot really be covered by ports though.

We use dojo’s i18n system in order to have localized messages in the app. Our dojo components already heavily rely on that mechanism, so we didn’t want to have several different i18n “libs” and message files in the app. Having everything in our “standard” dojo format is also mandatory for people who translate the message bundles.

So what we do is :
1/ generate a “strong typed” .elm file from the dojo message keys (we parse and interpret dojo’s .js message files), so that, in Elm code, we can write stuff like “text <| I18n.messages.my.key arg1 arg2”. That way, the Elm code doesn’t even compile if you try to use invalid message keys (which is pretty cool : with dojo you’ll discover the problem at runtime).
2/ the generated I18n.elm file has to invoke dojo’s i18n stuff somehow, and this is where it needs to be sync, so that we can just call those funcs from our views.

I don’t know how we are going to do this without native modules yet.


#13

Do the translations need to be supplied at runtime? Do they need to change at runtime without a page reload?


#14

Yes, dojo loads the appropriate message bundle(s) at runtime, so that you don’t fetch all the translations, but only the one that you need for the user’s locale.
It is based on the AMD loader : you basically require ‘my/messages’ with a loader plugin (dojo/i18n), and then access the messages on the object :

define(["dojo/i18n!my/messages"], function(messages) {
    ...
    var msg = lang.replace(messages.my.key, arg1, arg2);
    ... 
}

Our native module just wraps this messages object, and we generate a “façade” over it so that keys are “typed” :

I18n.messages.my.key arg1 arg2 

instead of

I18n.message "my.key" [ arg1, arg2 ]

I think reloading in case of locale change would be a acceptable, because that’s not something you do frequently.


#15

:thinking: That sounds like you could pass the translations to Elm as flags or through a port. It would need to be stored in the model either way. The I18n functions would need another argument with the translation strings.

With a flat model (which is often a good idea for other reasons anyway) that could work really nice, I think.


#16

Interesting !

I’m not sure that using from the ports module as a definition of the types would be sufficient for us. Most of our ports use Value because we have custom encoders/decoders. We do not use the “default” marshalling/unmarshalling system that ports provide because we use union types and stuff like this in the values we pass around.

I was thinking about some kind of IDL that would allow to describe the types in a language agnostic fashion, and generate the elm ports and the ts.d (or Scala.js façades, or whatever) from this IDL…


#17

Yes, I’ve been thinking about ways to support serializing/deserializing Custom Types in elm-typescript-interop. I tried to gather some feedback from the community in this thread, but it’s a pretty challenging problem to find an elegant solution to. I’d love to hear creative ideas if anyone has thoughts on how to make this a nice experience. It’s not challenging technically, but making the process simple and intuitive is quite tricky!


#18

@vankeisb awesome to hear that even IBM is using Elm for more than just POCs :slight_smile:
About your native code around translations. Maybe https://package.elm-lang.org/packages/ChristophP/elm-i18next/latest/ is for you. It supports also uses runtime translations and supports elm 0.18 (version <= 2) and 0.19 (version 3) .