The case for code generation
Code generation has a mixed reputation. Nonetheless, the Open Lowcode framework is based on this technology. This post explains its usage, reasons for this choice, and how the biggest drawbacks are mitigated.
Open Lowcode asks the user to define its extended data-model (object model): data objects and the properties attached to them. Then, the framework generates the data objects, and the standard actions and pages for the object. All specific content (pages and actions defined by users) have to implement an abstract class that is also generated. Open Lowcode working in java, all the code generated is java.
After experimenting several years with the technology, it proves very convenient, even if there are also a few traps.
Generated code is, probably, marginally faster than performing complex things at runtime, such as using massively reflection1. It is certainly massively faster than a scripting language, while hopefully being as convenient to develop in, as it limits the “boiler-plate code” of strongly typed and compiled languages, especially java. Better performance means, for the same service to user, cheaper hardware costs, especially in cloud, and also, less environment impact.
However, where the approach really shines is for incremental updates to the development and issue analysis. Code generation is the first test that a feature works. If the generated code does not compile, it is visible right away, especially if you type strongly 2 everything that can. This is true when new features are added to the framework, but also when the object model is changed. The discrepancies in the specific content written by users are immediately visible. In a surprisingly high number of situations, if it compiles, it works.
When you encounter runtime errors, the generated code gives a very precise stack-trace (report of where the failure happens). This makes the analysis several orders of magnitude faster than going through a generic stack-trace of all-purpose methods. This is especially true as the complexity of the object model is limited, and as a result, all the generated code is human-readable, contrary, for example, to some automatically generated web pages.
Automatic code generation also allows the APIs, and the manipulation of data objects to be as easy as possible. This is key for Open Lowcode, as a design objective is to allow moderately experienced developers to write specific actions for the business logic.
One advantage specific to java is the possibility to mitigate the limitations of generics, an advanced feature to define types. This feature works OK in most cases, but not all. This complexity is also, as much as possible, hidden behind the generated code. Some people will argue that this is a symptom of java limitations, and, I think I agree. Nonetheless, Open Lowcode tries to use widespread and decade-scale stable technology, and chose java for this reason, without finding an obvious alternative.
There are some drawbacks, or non-ideal aspects to this choice. Development is a two steps process, where you regularly define the model, generate and compile the framework classes, then, potentially, create or update your specific development, compile the whole application and deploy it. This is for sure not as smooth as a wisiwig development. However, the benefits make this small inconvenience worth it, and may even be beneficial, as it separates different phases (conception, development, and run-time).
Also, a medium-sized application will generate hundreds of classes. This may not look beautiful, but is not a very big problem in practice.
The biggest drawback I found to this approach is becoming somehow lazy because of the flexibility of generated code. It is easy to generate long and dirty code that will work OK, whereas it would have been possible to define generic helper functions that would have made the code easier to understand. This is certainly something to look after. As it is quite easy to refactor automatic code (you only have to do it once), it looks like this drawback can easily be mitigated, even after the first entry of service of a feature.
- Reflection is a java API where you can “script” access to objects, completely bypassing the strong-typing. Open Lowcode tries to limit reflection to the strict minimum (I think we have one place in the application where we use it.
- Strong typing means defining as precisely as possible the type of an object, and avoiding generic structures. For example, in java, if you want to process “invoices”, you create an invoice class instead of using a generic “data object” class where you put variables attributes on it.
Leave a Reply
Want to join the discussion?Feel free to contribute!