The source code for this blog is available on GitHub.

Conner Charlebois

Why you Shouldn't Rely on Configuration in your Database

Cover Image for Why you Shouldn't Rely on Configuration in your Database
Conner Charlebois
Conner Charlebois

In the old days when deploying an application took hours instead of minutes, development teams would often favor configuration over customization. That is, they would prefer to change a setting in the runtime (the running application) in order to alter behavior than to need to make a change to code itself (a “design time” change). Such design decisions would invariably lead development teams to create apps that could do darn-near everything, but that were incredibly error-prone and difficult to maintain when, inevitably, the code itself did need to be changed.

In this article, I will put forth the case for customization over configuration, and why making explicit customizations in an application’s codebase can actually cause fewer headaches over the long term. There are three reasons for this: First, when apps rely on configuration data — especially when those configuration data are complex — the app’s behavior can become flaky when the configuration data isn’t set correctly. Second, when an app behaves according to its configuration and that configuration itself is flawed, misbehaviors in the system can be very hard to identify, and harder to remediate. Finally, relying on this sort of configuration data prevents you from being able to confidently deploy your application into a new environment. Unless the app populates this configuration data itself, then the behavior of the app could heavily depend on records in some configuration table that may or may not exist when a new version is deployed. For these reasons, I always encourage developers to steer away from endless configuration options, and steer more towards explicit and clear customization.

Configuration Data Makes the Deployment Pipeline Prone to Errors

When you build an application that relies on having configuration data in its database and then go to deploy your application to another environment, what happens? You quickly realize that you need to change/update those configuration records in order for your application to behave correctly! The most common places I see this in Mendix apps are the use of the Model Reflection, Excel Import & Export, and Email modules. All of these modules come equipped with pages for a system admin to configure their behavior in the runtime. However, how many times have you deployed an app to a new environment, and then later realized that it’s not working as expected because it doesn’t have the correct email template configured, or the excel importer template has a different set of columns than the one you were working on locally? My guess is, probably more than once. While you can mitigate these issues by using tools like the Excel Importer Template Manager, you’ll still need to rely on a manual step of migrating these templates when you promote a build from environment to environment. Whenever your build and deployment pipeline requires human interaction, you open the door to human error.

So how can we do this better? The solution is hiding in plain sight. While all the aforementioned modules include pages that can be included in your application for admins to do the configuration, you can choose not to include them. All of the provided pages just give a UI into normal old Mendix objects which are just normal old records in normal old tables in your database. So, instead of relying on the user, why not automate the process?

As you may now be thinking, a better solution is to configure a set of startup microflows that create or update these configuration records when the application starts up. By explicitly defining the configuration details in the startup microflow, you can prevent runtime errors down the line, since the configuration is only edited in one place, by the logic you define.

Data-Driven Behaviors can be Difficult to Fix

Perhaps the nastiest outcome of having an app with data-driven logic is when the app is not behaving correctly, but the system is showing no errors, and there’s no outward indication of anything going wrong. Such is often the case when applications rely on configuration data. Unless your app has a vast array of error handlers and validation checks, it’s highly likely that someone could — intentionally or otherwise — screw up some configuration data and cause the system to not work. Finding the cause of these issues can sometimes be difficult, since — as far as the system knows — it’s behaving correctly. Because your misbehavior is caused by configuration data, it’s unlikely that reverting a commit will solve your issue. If the issue is in your data, your data need to be fixed.

So, how can we avoid this headache and ensure that our system correctly handles a configuration error? You guessed it — don’t rely on the data! When logic is explicitly defined in the model (I call this “model-driven”) it’s easy to trace and troubleshoot and figure out exactly where logic is breaking down. You can leverage the built-in consistency checker in Studio Pro to make sure that you have full coverage and confidence in your app working the way that it should. When you deploy your application and find that it’s not working as expected, you can say with confidence that its due to a modeling error and can quickly revert to a previous revision in order to restore working behavior.

Reliance on Configuration Data Makes Deployments Flaky

As a developer, you want to have confidence that, when you deploy your application, it should behave the same way regardless of the environment it’s deployed to. However, when your app depends on configuration data existing in the database, you can never achieve this confidence. You will always need to confirm that the configuration data exist (and exist correctly) in an environment before you can be confident in your code. One example of this that I’ve seen is when an application makes use of an excel import template and where this template data is different in the prod environment than in the non-prod environments. New versions of the code can be released, but the template is still the old version. When a user goes to upload a file, columns can be missed or, worse, the application could display a generic error.

How can you regain confidence in your deployments? By dropping your configuration dependencies, of course! By creating & committing any necessary configuration records in a startup microflow (and raising errors if the operations fail) you can be sure that, once your app starts up, it’s fully ready to roll in whatever environment it’s deployed to.

Customization is Cleaner

I hope it’s clear now why you should favor customization over configuration. First, never send a human to do a computer’s job. Setting configuration data is remedial and needs to be done exactly the same way all the time, so let the computer do it. Second, save your configuration as code, and rejoice in the fact that you can revert your application to a previous configuration version just like you could revert to a previous code version. Finally, reduce dependency on runtime data and gain the confidence that your app can run in any environment it’s deployed to. Using these tips, I know you can go forth and build even more robust and resilient applications!