Qcodo's code generator appears to be very similar to the many “ActiveRecord”-based metaprogramming frameworks that have caught on a lot of buzz over the past year. But there are some core, fundamental differences between Code Generation and Metaprogramming, and this article will take an in-depth look at both of them, weighing the benefits and costs of each.
Over the past year or so, there has been a lot of buzz and excitement over a new wave of frameworks that focus on the “rapid” in rapid application development for web-based database-driven applications.
A lot of the catalyst for this buzz was generated by Ruby on Rails, which brought to the mainstream the idea of having your framework create and handle a database persistence layer, or object relational mapping. It's all based on Martin Fowler's ActiveRecord pattern, which he describes as:
An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data.
Ruby on Rails and other ActiveRecord-inspired frameworks (including Cake, Hibernate, NHibernate, and even Zend PHP Framework) all focus on creating your object relational map “automagically”, thus providing you with an entire class library, complete with database access, relationship management, and attribute handling, which map directly to the tables in your database. Having this alleviates the need for the developer to focus on the database, as s/he can now spend time focusing on extending these classes with custom business rules, etc.
Obviously, having this is a great starting point for any application you hope to prototype rapidly. And while each individual framework has other functionality and features that provide even more tools to aid the developer, almost everyone would agree that the power of these frameworks largely come from this core concept of automatically building your object relational map.
And at the 50,000-foot level, Qcodo's Code Generator looks very similar; given a data model, Qcodo generates your entire class library, mapping objects to entities.
However, the similarities end there.
Metaprogramming vs. Code Generation
All of the above-mentioned frameworks focus on creating a class library using metaprogramming techniques. In short, the functionality and properties of your object relational map is given to you through the use of runtime analysis (including database-structure analysis and class reflection). While some of these frameworks ease off on the performance hit of the runtime analysis through caching techniques, the principle is the more or less the same: given your data model with various tables, you create an empty class which extends a base/generic Object Relational Mapper class.
(Because of Ruby on Rails, most people refer to this as the “ActiveRecord” class, which can sometimes be confused with Fowler's ActiveRecord design pattern. In reality, Qcodo is one of the few frameworks that actually implements Fowler's original ActiveRecord design pattern as originally described. Unfortunately it makes it a little confusing that some of the other frameworks have taken Rails' lead in obfuscating the term “ActiveRecord” by implementing a framework class using the same name to implement a metaprogramming-based mechanism to perform ActiveRecord-like functionality.).
By very nature of the fact that your empty class is a subclass of this generic ActiveRecord class, the framework kicks in using database analysis and reflection to provide you the “Create, Restore, Update and Delete” (or CRUD) functionality for that database table. Note that these methods never need to be explicitly defined anywhere. You simply call on them in your new class, and the deep framework functionality within the ActiveRecord class performs the task for you.
Code Generation, which is what Qcodo does, takes an entirely different approach. The analysis is done at time of Code Generation, and produces an explicit set of class library code. So when your application runs through these classes, there is no runtime reflection or database analysis that is performed to provide the CRUD functionality, because all your CRUD functionality, relationship managers and attributes are already explicitly hard coded in your class libraries.
Michael Rettig wrote an article with Fowler in Javaworld where he describes the differences of reflection (which metaprogramming is based on) and code generation:
The number one limitation to using runtime reflection is “Do not make a simple problem complex.” With reflection, this is unavoidable. Coupling reflection with recursion is one serious headache; reviewing the code is a nightmare; and determining exactly what the code is doing is an intricate process. The only way to truly determine the code's behavior is to step through it, as it would behave at runtime, with sample data. However, to do this for every possible data combination is nearly impossible. Unit testing code helps the situation, but still cannot quell the fears of a developer who sees a possibility for failure. Fortunately, there is an alternative.
He of course goes on to describe the alternative as being code generation. The key point is that because code generation is explicit, there is a known variable with what you are dealing with. This helps the developer in many ways:
- It allows the developer to understand what the data access code is actually doing, and it offers the ability to easily step through the code that is explicitly accessing a known data entity.
- A developer can see the actual queries being performed against the database in the code.
- Errors will be reported in a way that makes sense to the developer, in the actual code for a specific class, as opposed to within a genericized data-access object somewhere in the framework.
While this is very interesting to note at a theoretical level, let's break down the differences between the two techniques with respect to the specific frameworks.
The biggest benefit of metaprogramming is that from a code-base perspective, it keeps things really simple. Because no code is generated, your classes remain incredibly small. For the most part, the only code that exists in your classes is custom methods to handle specific business rules and other specific one-off customizations.
And in fact, code generation can sometimes be quite daunting for people simply because the generated code increases the size of the codebase, and thus, it would at least feel like the system would be a bit more unwieldy to manage.
Hopefully, the discussion of Qcodo's templates and unlimited code regeneration below will help these people understand that at the core, you aren't required to manage the entire class library that was code generated. You simply need to manage any customizations that are done within the custom class files. And incidentally, this is what you would have to do anyway if you were using something like ActiveRecord.
Because the analysis is done at runtime, changes to your data model can be reflected in your application at realtime. There is no need to “regenerate” your code as you would with a code generation approach.
But of course, this runtime analysis does incur a cost...
Code Generation: Benefits
No “Runtime Analysis” Performance Hit
Since all your analysis has been done at time of code generation, the runtime execution of a Qcodo web application does not have to incur the cost of any analysis. The obvious cost against this is that when you make any changes or additions to your data model, you are required to re-code generate. However, given that code generation is usually done by a simple web-page hit or a command-line call, this step is trivial.
Now, the concept of “re-code generation” is a scary term to many developers, especially given other code generation modules that exist in the marketplace (specifically those in IDEs and other frameworks). The main concern is that if you are required to re-code generate, you run the risk of losing modifications to already code generated code.
Qcodo alleviates this by creating a generated class and a custom class for each table that is to be mapped to a class object. Your specific business rules and other customizations would be manually coded in the custom class, while you would leave the generated class untouched. This means that a developer can customize and add as much functionality to all the classes as needed, but then whenever changes are made to the data model, the developer can perform re-code generation without worrying that the custom code would be overwritten.
Although it's not something that we would ever recommend since we actually like Qcodo, you do have the ability to break free from the framework at any time. =)
Because the code has been explicitly generated for you, there is no dependency on an ActiveRecord-type object. If you want to strip away the dependency on the closest analog to ActiveRecord in Qcodo, which is the code generator, then fine. Simply toss the code generator out the window, and you still have your working application.
The same absolutely cannot be said with metaprogramming-based frameworks. Once you create your class library as extensions to the metaprogramming-assisted classes, you are forever dependent on them for the functionality of your web application.
Adding Core Functionality
Because Qcodo's code generation is based in templates (which itself is just PHP code), it's pretty straightforward to extend core code generated functionality to all your objects. For example, if you need to implement application-wide database archiving, this can be done by just modifying a few lines in your templates and then simply performing re-code generation.
(And in fact, something like this actually happened on a previous project where we were in beta testing, about two weeks before soft launch, and the client suddenly came up with the requirement for database archiving for their entire system. It took less than 45 minutes to have it fully implemented.)
Unfortunately, to do this using metaprogramming techniques, you would need to understand the internals of the base object (e.g. “ActiveRecord”) in order to be able to add this kind of functionality.
And the other types of application-wide functionality that a project may require can be quite varied:
- Optimistic or pessimistic locking
- Row- and field-level permissions
- Application-level database journaling
- Web service wrappers (SOAP, REST)
- XML transforms
- Object caching
Beyond the Object Relational Model
Because at the core, the code generator simply generates off of a list of templates, Qcodo is able to go beyond generating just your class library for the object relational model. It also generates an entire library of Form Drafts, which are very simple web forms that give you the web front-end to the CRUD functionality to your database.
Now, many of the metaprogramming-based frameworks do provide a similar kind of functionality to you via “scaffolding” (which, interestingly enough, is becoming more and more code-generation based).
However, we've stayed away from calling the Qcodo functionality “scaffolding” as well, because the Form Drafts are actually significantly more robust in functionality given the fact that the forms, themselves, are based on the Qforms architecture and is completely object-oriented, meaning that it shares the same benefit of being able to have customizations made to the page (including full scale visual design), but then being able to re-code generate at will and not having to have to worry about those customizations being overwritten.
And, focusing back on the template-based code generation specifically, it is just as easy to add application-wide functionality to your presentation layer; it's just a matter of editing the PHP templates.
(Another specific case in point: Form Drafts can essentially give us a very quick and dirty back-end administration web application for this Qcodo.com website. But we obviously needed to add some sort of security. It took literally 15 seconds to implement that security and to re-code generate all our forms, and suddenly, we had an entire back-end administration tool for this website.)
One of the most interesting benefits of PHP is the fact that it truly is a “glue” language -- it is so easy to incorporate objects in other languages, including Java, C#/VB.NET, C, and C++.
In a similar fashion, Qcodo can be used to generate non-PHP code. Obviously, this would require a bit more work, as you would need to create templates in another language. And probably for 99% of the users out there, there is absolutely no need for this.
But one very real benefit of Qcodo is, supposed you have an incredibly large scale application where every microsecond of performance needs to be optimized. One great advantage could be to recode the templates so that Qcodo would output the object relational model as a PHP Extension in C++. Now you can have the binary code reside in-memory and in-process with PHP, and skip the need to have to include the PHP class file, parse the PHP code, etc.
Unfortunately, this simply cannot be done with metaprogramming at all.
Obviously, as I've said before this is a benefit that most people will never need. But it actually brings up another point: because of the open-ended template-based nature of the Qcodo code generator, it really allows you as the developer to think “outside the box” when it comes to utilizing the framework to do what you want it to do. There are no limits. You have the ability to do infinite customizations.
Hopefully, this article is able to at least scratch the surface in showing the differences between code generation and metaprogramming. Now, because of our bias with Qcodo, we would obviously like to think that one is better than the other. =)
But in reality, both have their benefits and both have their costs. As always is the case in good software engineering, developers should use the right tool for the job.
Given its by-design simplicity in user code and its ability to provide ORM functionality changes in realtime, metaprogramming is great when it comes to rapid development of prototypes.
Conversely, given the fact that actual code is easily generated through the use of modifiable templates, and given the fact code can be regenerated and customized ad nosium, code generation allows not only rapid prototyping but also the ability to scale out your code base in functionality as your prototype matures into a full-scale application.
Is that a biased statement? Probably. But is it a true statement? Well, why don't you see for yourself and the let us know what you think in “Forums”:/forums/. =)