At Montoux, we version our actuarial applications with Maven’s versioning scheme. This was a natural choice for our original monolithic Clojure application, but as we began to redesign our client infrastructure and shift some of the functionality from back-end Clojure to front-end Javascript, we discovered an unexpected problem: there aren’t any libraries for Maven versioning in Javascript!
We need this to be able to show actuaries a sorted list of which applications are available to them in the browser. Until the re-design of our client infrastructure, we sorted actuarial applications using a function from the goog.string library called numerateCompare. This is a comparator for decimal numbers:
cljs.user=> (goog.string/numerateCompare "1.0" "1.1")
-1
As expected, it returns -1 to indicate that the first argument is less than the second argument. This worked fine for six years of front-end versioning until we hit double-digit versions. We noticed during testing that version 1.10 was listed as older than version 1.9:
cljs.user=> (goog.string/numerateCompare "1.9" "1.10")
1
This is because “1.10” is interpreted the same as the decimal number 1.1, which is smaller than 1.9. There are other edge-cases as well. For example, certain phrases like “alpha” or “beta” indicate a pre-release in Maven’s versioning scheme, and with numerateCompare, a version like “1.0” would be erroneously ordered before “1.0-alpha”:
cljs.user=> (goog.string/numerateCompare "1.0" "1.0-alpha")
-1
Until now, we’d never run into these cases, but having got all the mileage we could out of numerateCompare, it was time to begin using a real comparator on the front-end that respected the Maven versioning semantics we used on the back-end. We thought it would be a straightforward matter: surely someone had written this library before, and all we had to do was find and import it. But it turns out nobody had. To solve this obscure conundrum, we wrote mvncmp, a Javascript implementation of the algorithm used by Maven to compare two version strings.
Java and Javascript are two different beasts. In porting this code from one to the other, we decided that fidelity was our most important priority. Version comparison was not a performance bottleneck, so it wasn’t necessary to spend time needlessly optimizing the end result. What was more important was being able to determine that it did the exact same thing as the original. Maven versioning has all sorts of obscure edge cases, and if a bug were ever found in our Javascript implementation that made it inconsistent with the original Java implementation, reconciling the two would be extremely painful. We wrote mvncmp to be as similar to the original code as possible. This doesn’t mean pretty, idiomatic Javascript, but it does mean we can be reasonably confident—with minimal effort—that it does the exact same thing. To wit:
cljs.user=> (goog.string/numerateCompare "1.9" "1.10")
1
cljs.user=> (mvncmp/compare "1.9" "1.10")
-1
So ends our little adventure. Our front-end no longer thinks 1.9 is bigger than 1.10, and a bug was solved before it reached our customers.
You can see the source code for mvncmp on Github and download it from npmjs.