18

I have a situation where I support what is functionally the same library in multiple languages. There are often constants that need to be shared between these (for example, json field name keys or error codes).

The way I currently do this is by having code defining the constants in each language.

The problem comes in maintenance. If I add a new error code, I need to update it in every library manually. While this is fine for a few it becomes tedious if I have say 5 sdks to update. It'd be nice to have a single source of truth for these too.

I've thought about some sort of config-like file but then it needs to be included in every deployed package which adds complexity to our build/release process.

Is there a better way to handle maintenance of constants which are shared between multiple languages?

enderland
  • 12,201
  • 4
  • 53
  • 64

4 Answers4

11

Although I think your current approach is probably the simplest and most straightforward, here are a couple alternative ideas:

  • Extract your constants (and perhaps models) to a different package that is cross-compiled to all your languages. You might be able to cross-compile the entire library, but that may come with a substantial amount of problems. Just cross-compiling constants should be simple enough that there won't be as many issues. You can publish your constants package and depend on it in your language-specific libraries. Haxe can probably do it. This approach is good because you'll still have compile-time checking (for compiled languages)
  • Store configuration in a central service. For example soap web services have the Web Service Description Language (WSDL) and REST services have Web Application Description Language (WADL) which describes the operations and messages of the service. There are also generic centralized configuration services like Spring Cloud Config
  • Config file. I know you've already suggested it, but I don't think it needs to complicate your build/release process very much. Put your config file in a separate build project (where you can version it). Publish the project to your all language specific package repositories (Maven, Nuget, NPM, etc.) then in your language-specific libraries you can depend on the package. This shouldn't be any more complex than having an additional project in your build pipeline.
  • As RubberDuck suggested, code generation is a good alternative to cross-compiling. You can generate constant definitions for each language using a common configuration.
Samuel
  • 9,237
3

Great question! I have the same exact issue; my constants are essentially: what languages are supported in my applications, and additional information about those languages as they pertain to functionality in the app.

Unfortunately, the best thing I've found (as you have) is to simply redefine constants for each language, as you are currently doing (I know, you definitely wanted to hear that).

Obviously it feels wrong because it's the opposite of DRY (WET??). However, constants should change so infrequently that the 5-10 minutes of redefining them for each language does not really bother me. At the end of the day, small issues with some 'elegant' solution like shared config or code generation could take hours or days to resolve, so what is really gained? Added complexity with the risk of something going wrong that could take additional effort to fix is not something that I want to deal with.

Furthermore, if your application has so many constants that redefining them per language when you add or change them takes a significant amount of time, you might just have a more significant code smell to deal with and, at that point, you may want to turn to something more complex.

So in short, redefining them for each language has been my best solution, and I have yet to think of anything more DRY that wouldn't have more risk factor than I want to deal with.

One thing to definitely do, though, is to ensure that your constants are well documented in a generalized (and language agnostic) manner (we have a company documentarion repo with specs, miscellaneous docs, 'drawing board' docs, etc. where we keep this document). Also make sure you have mechanism(s) in place to keep their definitions in sync. That's about as big a problem with the duplication approach as you'll have, except for a small amount of psychological distress from intentional code duplication. But in the end, your constant changes should be very deliberate and infrequent, so synchronicity issues should be essentially nil.


I should also mention that over the years, I've seen multi-language ports of various libraries (too tired to remember what they are at the moment) written by the same group that invariably have constants defined in the languages themselves. No shared config, no code generation (except for Google API client libraries... but come on, Google has the resources to afford such complexity). So I think we've hit a brick wall on this one. Maybe someone will eventually come up with a library to deal with this problem ;)

1

I wrote a Python tool called Reconstant to solve this exact problem. I've written about it on Code Review Stack Exchange here.

Glorfindel
  • 3,167
0

Hopefully, the core of you library is written in one language, and the other libraries use FFI (https://en.wikipedia.org/wiki/Foreign_function_interface) to call to the core library. This would give you the central location to provide an api to publish the constants and definitions from. This way everything is self contained within the library. I am only mentioning this as it doesn't seem to be included in Samuel's response.

I think it really depends on how capable your user base is. Are the capable enough to be able to handle passing around an other config file? Are they capable to setup a new service? For the vast majority of users that I have supported, I would want everything to be self contained - this way the users would not have to think about it.

Robert Baron
  • 1,132