The Waves Architecture

Waves follows a few simple design principals, which are probably worth noting up-front.

  1. Keep code compact and extensible. As of this writing, Waves remains under a thousand lines of code, and just about every aspect of its behavior, including it’s dispatcher can be overridden or extended.
  2. Leverage the expressive power of Ruby. This means, among other things, using pure unadulterated Ruby for everything from configuration to URI mappings.
  3. Provide defaults by convention, but make them easy to change. If you like the way Waves works, out of the box, then great, you’re done. But if you don’t, there should be a reasonably straightforward way to customize it.
  4. Loosely couple major design components, like the ORM and the controller, or even the controllers and views. This makes it much easier to accommodate new innovations later on.
  5. Use the best available solution. Waves originally had its own HTTP processing layer, but then Rack came out, and was obviously better. So now Waves runs on Rack.
  6. Be thread-safe. Threads right now are passe, but they shouldn’t be. Ruby 1.9 supports native threading and Web applications are course-grained enough that threading doesn’t generally pose too many problems associated with parallel processing. Ultimately, threading may yet emerge as the fastest, most scalable way to run Ruby Web apps, as it has on most other language platforms.

The Waves Stack

Waves is runs on a state-of-the-art application stack, although remarkably it is not really tied to any single solution, except possibly Rack. That makes sense: Waves is a framework for building Web apps, and Rack is a framework for frameworks like Waves. Other than that, it depends on what application you’re building. That said, Waves does tend to favor some libraries over others, especially at this young stage of its development.

Web Server

Rack provides the HTTP processing engine. This provides Waves with a easy-to-configure processing pipeline, via Rack’s middleware, plus support for most major Ruby Web servers, via Rack’s handlers.

At the moment, Waves is actually hard-coded to support Mongrel as the Web server, although it is a trivial change to make the handler configurable. The main reason I haven’t done this is to make it a bit easier to provide support and because I’m relying on Mongrel’s MIME type interface at the moment, although it would be easy to “borrow” that code for use with Waves.

Data Storage

Sequel is the preferred ORM at the moment because it supports migrations and because it supports the use of plain old Ruby for defining queries, but it would only take minor changes to support DataMapper or other ORMs. Again, the need to be able to support these different configurations has led me to initially constrain them.

I am looking at Ambition as well, since it provides a nice Enumerable-based abstraction that is essentially ORM independent. Thus, it can be sort of like Rack – a layer that framework providers can use to enable support for virtually any database. However, that game is still early on, as are most of the ORM frameworks.

As an illustration of how flexible Waves really is, I have one Waves app running using soon-to-be-released Gem called Filebase, a file-oriented “database” that provides a lightweight alternative to using an ORM if you don’t really need one. I use it for CMS-type applications, where most of the data maps well to a file structure. Weighing in at 30 LOC, it is great for keeping the memory requirements down.

Template Engines

At the moment, Waves supports only two templating engines: Markaby, which given that it is actually Ruby code was a no-brainer for inclusion, and Erubis, which is useful for all those times when the structure of Markaby is a bit too much, or your not generating HTML.

I plan on adding XML Builder support for sure, but beyond that, I’ll let the developers tell me. I would imagine there will be some demand for HAML, especially for user-defined template scenarios. (Although, I’d like to get Sandbox working with Markaby towards the same end.)

Class / Module Loader

This might not seem to be a natural category for a framework application stack, but it is a surprisingly crucial area. Most, if not all the frameworks I’ve seen have sort of weak support in this area. This is unfortunate because Ruby itself provides great capabilities in this area.

Waves uses a Gem I wrote (for Waves) called Autocode, which does true code reloading. Rather than simply call load on various files, it actually first calls remove_const on all the elements that are intended to be reloaded. This completely wipes any constants or associated method definitions from memory, provided there are no references to them hanging around.

Autocode also has the ability to “autocreate” classes and modules from exemplars, which allows Waves to generate models, views, and controllers as needed, even when there is no source file associated with them. This not only saves some typing, but allows you to, in effect, add new MVC components to your application simply by running a migration.

Waves will shortly also support LiveConsole, which will allow hot-patching of production apps. The way this works is pretty simple, actually. Since your application is contained in a reloadable module (it’s reloadable, even if you don’t ever reload it), and since LiveConsole gives you a console into the running server process, you can simply call, say, Blog.reload from the console to update the running code, without ever bringing down the server. You have to do this within a server mutex, but since the code is truly reloaded, it is a pretty safe operation.

This kind of hot-patching feature used to be one of those things that distinguished enterprise software from comparable open-source projects, but no more. You can get hot patching for free with Waves.

Testing And Debugging

This is an area that has not been addressed with Waves. You can use whatever testing approach you prefer. Waves doesn’t favor any approach – it doesn’t even generate a tests directory for you at this point when you generate a new application. This will be addressed in future releases, along with adding ruby-debug support (I think it already works with Waves, it’s just that I haven’t tried it out).