Christine and I, having settled into our new jobs, are rolling up our sleeves to dig in and start some hardcore re-factoring. The legacy applications we are dealing with run smoothly and quickly for the most part, but are teetering on the edge of being un-maintainable. They suffer from the standard trademarks of any legacy code which has seen many programmers over the years-- lack of code reuse, absent standards, poorly normalized data structures, and minimal separation of view, business, and data. Don't think I'm knocking it too hard though. It's probably not much different than a lot of the code bases out there. We believe it can be better though. Easier to maintain, understand, and extend. Enter our plan for world domination...
One of our primary concerns is separation of responsibilities. Our data ultimately lives in an AS 400 mainframe in a hierarchical set of DB2 files dating back to the 80's. It's amazing how much data this beast can handle. Our CF code however, deals with an intermediate SQL Server database. There are some large pieces of the SQL database we would like to re-factor but it is outside of our scope at this point. Therefore, we need a clear separation between our DAO and business objects to allow for those changes to happen in the future with minimal impact on our business objects.
Following the arrival of a large, brand new whiteboard, we donned a slew of buzz-inducing dry erase markers and began modeling our Entity Relation diagrams. This really is a liberating process. You start by jotting down all the concepts you need to deal with-- shopping carts, users, orders, products, discounts. Then you draw a box around each one and start filling in all the related properties you need to account for in each of theses areas-- Name, address, CD's, Books, prices, etc.
Our goal was to represent our business concepts accurately without worrying about the interface or data structure yet. As you work through these exercises, a model starts to form on the board as you begin drawing lines between your boxes. You can pick out inheritance (a CD or a book IS A product), and composition, (A shopping cart HAS A product and user). We grouped related objects together under services-- product service, user service... Upon the satisfactory completion of our first draft of these diagrams on the white board, Christine fired up a UML plug-in for Eclipse and made it all look pretty.
The way it stands now, here is the arsenal we have chosen to attack this code with. By no means am I saying this is the best combination, but it is what we have decided to go with for now.
ColdBoxColdBox is an event-driven framework that uses the MVC pattern. Event driven simply means that you have "events" that happen in your application, with handlers to take care of that event. ColdBox allows for both implicit and explicit event invocation. Implicit invocation is also referred to the "Hollywood principle", or "don't call us, we'll call you." This is because the framework taps the appropriate event when it is triggered. Explicit invocation would be when an event specifically calls another event directly. MVC stands for Model View Controller, and it enforces separation of your interface code (view), your business layer services, beans, and DAOs (model), and the glue that holds both of those together, deciding which one gets called next (controller). Among the benefits of this pattern are easier code reuse, the ability to change the interface (Ex. HTML to Flex), and loose coupling of your service layer to the framework. Christine and I have never used ColdBox, but were impressed by some really cool stuff like built in error handling with notifications, extra debugging info and pre-written plug-ins.
LightWireLightWire is a light-weight dependency injection (DI) framework (Think ColdSpring) from Peter Bell which coincidentally plays very nicely with ColdBox. I won't give a full background on DI, but basically it is an object factory design pattern, where you "ask" a factory to create instances of your objects for you. This allows your objects to have references to other required objects programmatically inserted into them. The simple examples always seem too basic to accurately demonstrate the usefulness of DI, but a common idiom, is the transient bean which requires a reference to its singleton DAO. You can configure your "factory" to ensure said DAO exists, and has a reference to it placed inside your bean upon the bean's creation. A desirable byproduct of your LightWire configuration file is that it acts as a single source of living documentation for all your object dependencies.
Iterating Business Object (IBO)This is another product of Peter Bell. Claimed to be pure genius by some, and a stop gap for CF's infamous object instantiation penalty by others, IBOs allow you to deal with multiple records in an encapsulated way without having to create collections of objects. Instead, a single instance of an IBO is created and loaded with a query object or struct to represent 1 or more records. A pointer is maintained to the "current" record and all other methods apply to that record. We are using the base IBO object and extending it with all our beans. One modification we have made is the addition of a per-record dirty flag to control whether the data in the object has changed since it was loaded to prevent unnecessary saves. Following the active record pattern, we have added read, update, insert, and delete methods in the base objects which handle the "dirty" stuff before passing control back off to the sub class for bean-specific implementation.
Data Access Object (DAO)We are retro-fitting a normalized, relational, (and ideal?), business model to a data structure which has significant differences. Due to this disconnect, Object Relational Mappers (ORM's) and code generators were thrown out early on. We need our DAO to be a separate layer that buffers our business objects from how we are actually storing the data currently. Our hope is to tighten down the database in the future, but until then we need to keep the rest of the application shielded from the way things are working under the covers. For the time being, our DAO will handle single-record operations, and our IBOs will iterate over their contents and ask the appropriate DAO to deal with them individually.
ServicesChristine and I talked about this one for a while. Thanks to Jason Dean for also lending some useful conversation. We are trying to stay away from the 5:1 curse and keep our beans as fat as possible. We came very close to eliminating our services all together and replacing them with our framework's event handlers. Tight coupling of our framework and business objects didn't concern us too much, but I didn't care for the possibility of view-specific code in our handlers that would also be acting as services. Christine argued we were going to have too many CFC files for all our services and insisted "You Ain't Gonna' Need It (YAGNI). I argued that we'd end up with as many or more event handlers to compensate and it would be harder to add back in later. So, we're keeping them-- for now. :)
Well, as usual, my "quick" post has turned into several pages. Please do comment on our choices if you made it this far.