Avoiding Copies for Customizations

by Jeff Vroom

Question: Have you ever had to copy lots of code to customize it, then struggled long-term to maintain the copies?

I've seen this strategy bury more than one company. They copied and changed large amounts of code to quickly make changes for each new customer. When you don't have time to make a nice plugin, refactor using object oriented principles to share the code, you can generate technical debt quickly. Pretty soon, your small engineering team can no longer maintain feature demands for existing customers while making customized copies for new customers. If you follow this "slash and burn" style development, at some point you'll lose your competitive advantage and no amount of lock-in advantage can keep your customers long term. The more competitive the situation, the faster the product evolution, the more likely you'll lose in the long run.

More engineers take the simpler approach and use flags, or feature toggles in the code. Even if you engineer your feature toggles to allow them to be configured externally, without modifying the core application code, this approach runs into limits as you add more customers with disparate needs.

To support more customization without layers you'd use some mixture of plugins, object-oriented development, and inversion-of-control containers (or IOC).

Plugin Frameworks

Wordpress is a great example of a plugin framework. A plugin framework has built-in "hinge points": specific API contracts used you can follow to extend or modify the behavior of the UI from separate code. They are carefully designed to solve specific use cases and typically give your plugin control over specific regions of the screen, ability to add new fields, document types, etc. You control layout, styles, separately and so this pattern is great as long as your requirements are matched by the contracts supported by the plugin.

It takes a lot of work to design and maintain a great plugin framework. It's like a mini-application framework by itself with install, upgrade, compatibility constraints, uninstall, etc. Plugin-frameworks are so successful it's worth the effort for some application areas where there's a lot of custom requirements (e.g. content management and e-commerce). Still it is very common to run into the limits of what you can customize in a plugin, or have version problems upgrading. It takes a lot of coding on both sides to make it work.

Object Oriented

When you can't find a plugin platform that matches your needs, your next option is to use object oriented programming APIs with inheritance and essentially build your own plugins using code. Instead of copying code, you refactor so the customizations are separated in a subclass. But object oriented programming by itself is inadequate to make easily customizable systems. You need to use a factory pattern to separate the use of an instance from its construction.

Inversion of Control

In the Java world, the most common and powerful factory pattern is an IOC container such as Guice or Spring. They make it easy to configure the instance class used for any given purpose. Through the configuration (either annotations, properties or markup) the IOC container builds a graph of component instances, wiring everything up.

To customize with an IOC container, only the configuration is copied and updated for each new type of use. As long as you maintain the compatibility of the published APIs (which includes any APIs used in your configuration) you won't break applications that use your code as you go. When you get a new version of the configuration you copied, you need to merge in any changes which are now required by the contract.

That's a lot to think about on each code change so it's no wonder that rigorous O/O design with IOC component configuration does not work well when your major corporate goal is "time to market". It takes careful planning for customization hooks. You need to refactor as new requirements emerge in the planning process which involves a lot of code rewriting. Features move from code to configuration and back again. The developers of those components must take care with each change to maintain compatibility for old configuration files or upgrades are not seamless. It's in particular very difficult to seamlessly add new features - usually, the developer must modify their copy to add configuration required by the new feature.

StrataCode Layers

StrataCode provides a new option that lets you code rapidly - essentially like you do with Java, Spring, JPA, etc. today but less verbose, easier to read code. You use simple classes and objects for your application code. Just like Java classes, objects can be nested to form trees. StrataCode's code-generation will convert this all to a nice readable, debuggable Java file via incremental code-transformations.

For simplicity, you can configure fields by directly initializing them. At some point, you might split that initialization out into a separate layer or simply override those values in customized layers. This works for object instance or subclasses. To define a plugin interface, you just choose which types, properties and methods to expose. Layers complement the standard object-oriented design patterns that can be used with your code. When clients are given the ability to modify a type, they can redefine the behavior of that component - including inserting new children or even replacing a child component. Plugin developers see a static-typed set of APIs that can use, override and extend with much less work on your part.

There's less planning for future customizations during design and fewer design decisions about what's in code versus what's in configuration. All code and these configuration layers are traceable in the IDE for easy find and replace. After each code change, you can quickly validate all layers that use that code to detect structural integrity problems without running the code.

With layers, knowing you can make customizations later, you write code that's easier to read.

One of the reasons developers use an IOC framework is to allow configuration of arbitrary graphs of components. Sometimes, as you make references between objects, you need to create a cyclic or upstream reference. You can't express that with Java's field initialization. With StrataCode, you can add the @Component annotation to the class (or a sub-class). Lots of code will "just work" but sometimes you'll need to move code from the constructor into the init method if it depends on a field which cannot be initialized because of the cyclic reference.

Most IOC systems have some runtime overhead - parsing and applying the configuration. With StrataCode, you can use dynamic layers when you want configuration applied at runtime, and compiled layers when you want to avoid that overhead (or don't want to include the StrataCode dynamic runtime library in your application). This organization provides tunable runtime-efficiency and coding-time agility. You have less code which is easier to navigate.