Code Generation vs. Metaprogramming
Introduction
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 the upcoming
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.
Theoretically Speaking
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.
Metaprogramming: Benefits
Simplicity
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.
Realtime Changes
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.
Break Free
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
- Etc.
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.)
Beyond PHP
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.
In Conclusion
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. =)