On a regular basis I have the distinct "pleasure" of dealing with a supposedly customizable system like a CRM, a CMS, an ERP, and the like. One thing I have learned since I started working with these systems is that customizing them can often be a sure path down a road of madness. All too often, your customizations make the system "upgrade proof" or nearly so. While this is usually not completely avoidable in most cases, there are some things you can do to ensure that your changes cause minimal pain when the base system needs to be updated.
The biggest pain point is the mingling of your code and the system's code. This happens in a variety of ways, so let's take a look at them:
- Modifying system files and editing built-in functionality directly. This is the highest possible crime that you can commit, like stealing nuclear secrets and selling them to a foreign power.
- Copying system files and editing the built-in functionality. This is almost as bad as editing the files directly, because you still have the same problem: upgrades will require figuring out which changes you made and which changes the upgrade makes, and porting your changes to the new files. You've only gained a small measure of separation of your work from the system's code with this one.
- Placing your custom-created files within the existing structure. Unless the system gives you a directory specifically for your changes, reusing the existing directory structure is a great way to get messed up when the vendor reorganizes it in the next release.
Instead, the best way to handle this is to create your own classes that either inherit the default classes or are partial classes, depending upon the language and how the original code is written. This will allow you to override functionality on a very discrete, limited basis and, if the underlying code ever changes, the potential for harm will be minimal. Try to add functions rather than change them whenever possible to avoid conflicts in the future; this will also allow you to quickly and easily find references to your code. Always use the vendor-specified directory for custom code, and if the vendor does not specify one, create one that is 100% separate from the existing directory structure.
The second big pain point in the way you make your changes is in storing your settings. Most of these systems have a way of storing settings in a database and use a basic configuration to bootstrap and get the database connection up (such as web.config in ASP.NET). There is no best way to store your own settings, but we can look at the choices.
You can also use web.config (or the equivalent in your system) for settings, but the tradeoff for the simplicity and built-in tooling is that your settings will be managed outside of the system's administration system. It will also cause you additional pain when deploying to another system (or graduating from development to staging to production) since you will need to merge the settings with the existing settings file. Alternatively, you can figure out how to work with the system's database, but be aware that not every system exposes this cleanly. If possible, look at the system's codebase and see how it accesses settings, and if it uses a class or library for that, try to make use of it yourself. The big question mark with this approach is that you might not have a clean way to inject your settings into the system or to add on to the administration console. If you can answer both of these concerns with a reasonable solution, this is usually the better approach in my opinion. But you will need to play it by ear.
The last major area of problems has to do with change management. There will almost always be times when, despite your best efforts, you will need to break one of these rules. For example, I was working with Drupal, and I discovered that the email sending routines were broken on Windows servers due to the newline character differences and a very strict mail server. I had three choices:
- I could write a custom mail routine, and change every reference in the system to the new one.
- I could edit the exiting routine to include the change.
- I could write my own routine and edit the existing one to simply call my version.
The first choice would be the worst. Even though I would be leaving the core functionality alone, it would involve editing a lot of other pieces of code. In this case, I ended up going with the second option, simply because my change was so small. I marked the code clearly with comments, and I have a very prominent note in my project documentation to port the patch if the bug exists in future versions of Drupal.
No matter what, you need to find a way to make sure that these kinds of things are documented. It helps if you can find a way to force yourself (or whomever comes after you) to read the documentation, such as dropping a read-only file with an obvious filename in an area that will be overwritten or deleted with any upgrade. Make sure that any changes you make to the code are clearly labeled with the following information:
- When the change was made
- What version the application was at when the change was made
- Why you made the change
- A brief overview of the change's impact on the system
This information can be in the code itself as a comment or in the check-in notes. If you do it in the check-in notes, it is critical that each check-in represents only one change.
While working with these kinds of systems can often be a hornet's nest, hopefully you can mitigate as much risk as possible with these suggestions.
J.JaDisclosure of Justin's industry affiliations: Justin James has a contract with Spiceworks to write product buying guides; he has a contract with OpenAmplify, which is owned by Hapax, to write a series of blogs, tutorials, and articles; and he has a contract with OutSystems to write articles, sample code, etc.
Justin James is the Lead Architect for Conigent.