« September 2009 »
S M T W T F S
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30


Tuesday, 29 September 2009
Rails through thick and thin
Topic: Tools

It seems as if everyone likes to blog about the fat model and skinny controller idea when they're just learning about Rails development. I'm just learning about Rails development, so here's my blog post on the subject.

The canonical model-view-controller architecture calls for the controller to be lightweight. There are good reasons for this, including flexibility to scale across architectural layers, loose coupling, ease of understanding the code, and more. As a Java developer, I typically follow this guideline when building webapps with MVC frameworks like Struts2. It's only natural for Rails developers to try and conform with this very logical design guideline.

When you generate Rails code, the out-of-the-box solution has very thin models with most of the logic included in the controllers; quite the opposite of generally-accepted good practice for an MVC app. There's a lot of advice out there for people who want to skinny-down the controllers, and there's nothing new about it. For instance, there's a nice write-up by Michael Koziarski dating from 2007. The "before" version of the create method in his ReportsController is a great example of what can happen when you jam too much logic into a controller. In an even earlier example (2006), Jamis Buck shows the value of pushing logic from the controller to the model. His example resulting in the find_recent method in the Person model class is excellent.

But there's another issue to consider. Rails models are based on the ActiveRecord pattern. As a result, Rails models are tightly coupled to the persistence mechanism. By default, the persistence mechanism is a relational database, although this can be overridden. Because of this coupling, Rails models really don't feel quite like a domain layer; at least, not to me. It "feels" as if we need another layer between the controller and the model. I've heard and read Rails developers resist this idea, sometimes strongly. At the same time, other Rails developers say that pushing too much business logic into the models amounts to substituting a new problem for the old.

Besides all that, it's hard to test Rails models without a dependency on the database due to the lack of separation of concerns. There are tools available to mitigate the problem, such as Thoughtbot's FactoryGirl, but you still end up with some tests touching a real database instance. Tools like FactoryGirl make it easier to define fixtures, but they don't really isolate you from the database. In a large app, tests that are dependent on a real database instance can affect build times, and long build times tend to have a domino effect on other good practices.

So, we want skinny controllers, and Rails models are neither a business layer nor a domain layer, properly speaking. What should we do? I think it's legitimate to treat the models as a thin persistence wrapper, and to add either helper classes or a distinct layer between the controllers and the models to function as a business layer, if you need one. You might not need a business layer, since most business applications are almost purely CRUD apps, and don't actually have much "business logic." Let the models focus on one concern — persistence management — and pull the other concern — the domain model as such — into a separate layer. It seems more loosely coupled and cleaner than the default architecture.

Most of the time, the hand-coded methods people add to Rails models are really just handling a find with special conditions (as in Jamis' find_recent example), or saving an object with a change of state, like from "active" to "inactive." It seems to me this isn't necessarily part of the model's responsibility. It feels more like a choice being made by client code. The default model code already handles find conditions passed in from the client, and already knows how to save whatever values the client passes to it. Custom methods that ostensibly make the intent of the code clearer may not actually do so. They may obfuscate the intent of the code by separating the conditions for a persistence request from the context that makes those conditions obvious.

Going with generated code for models, plus a few declarations to invoke built-in functionality like has_many and validates_presence_of, also alleviates the problem of isolating unit tests or specs for model classes. A rule of thumb for unit testing is that we should not test generated code or the plumbing/framework; we should test hand-written code. When you treat Rails models as a thin persistence wrapper, you end up with literally no hand-written code in the model classes. The problem of unit tests that have dependencies on an external database simply disappears, because you don't have unit tests for the model classes — they consist entirely of generated code. Classes in your true domain layer can be isolated for testing just by mocking the models. In effect, you let the model classes adhere to the Single Responsibility Principle simply by omitting domain logic from them, leaving them with the single responsibility to wrap the persistence mechanism. Now the single reason to change is that you need to change the peristence mechanism from a relational database to something else.

I wanted to think through the question of skinny vs. fat to see whether there might be implications for scaling a Rails app. This is what I came up with, FWIW.

The app you get by default from the generators is designed for small-scale, low-volume CRUD apps that live entirely on a single server. Whether you have the bulk of your business logic in a controller or in a model doesn't make all that much difference. A messy method in a model is just as hard to read as a messy method in a controller, and since the whole mess lives on the same server, it's a wash. It's like squeezing a balloon that is half-filled with air. When you squeeze one end, the air bulges into the opposite end. Think of one end as the controller and the other end as the model, and you can see the effect of moving code between the controller and the model. It really comes down to a question of personal preference.

Architectural niceties start to make a difference when you go beyond the level of small-scale, low-volume CRUD apps that live entirely on a single server. How you need to scale depends on the circumstances, and most forms of scaling will not affect the application code at all because they are handled by assets external to the application, like routers or containers. In those cases, it still will not matter whether most of your logic resides in controllers, in models, or in an additional layer between the two.

Let's say we begin with a small-scale, low-volume CRUD app that lives entirely on a single server. Over time, the user base for our app grows. We need to be able to handle a higher workload than we did originally. This can be done by running the Rails app with a facility like Passenger for Apache. Passenger is smart about caching query results and keeping objects instantiated in memory between requests. It enables a Rails app to handle a higher transaction volume in a way that is completely transparent to the application code. In this case, your personal preferences regarding controllers and models neither help nor hinder scalability; you are free to do as you please.

Maybe the need for scaling is a question of availability rather than transaction volume, or in addition to transaction volume. In this case, we can set up a router in front of the application that sends requests to alternate copies of the application running on different servers. The servers may be clustered and may support hot failover, or other features to support high availability. This can be done in conjunction with a facility like Passenger, too. The different instances of the app can access the same back-end data store or different ones, as appropriate. There is no effect on the application code, so your personal preferences are still completely valid.

One of the ways we scale Java webapps is by deploying different architectural layers independently. That way, we can deploy multiple controller instances or multiple business layer instances and so on as needs change. The advent of virtual servers makes this approach all the more feasible and responsive to dynamically-changing workload demands.

You wouldn't normally separate Rails views, controllers, and models physically. If you needed to do so, you could use routes.rb to route different requests to different instances of the business layer or model layer. I can't actually think of a use case for it, but in any case it still doesn't affect the application source code and wouldn't push you toward thin or thick controllers, particularly. The use cases that drive us to separate components in Java webapps can be supported by deploying multiple copies of the whole Rails webapp.

Another scaling solution is to route requests to different servers based on geographical region or company code or some other piece of information that comes in on the request message. You could create a thin controller layer that does nothing other than interpret the relevant code and pass the request through to another controller residing on a different server. When you have different transaction volumes or different quality-of-service levels for different regions or companies (or whatever), this may be a reasonable way to split the traffic across multiple servers. The base application code still doesn't have to change; all you need to do is create a new set of controllers, or a single controller, to pass the requests through to the appropriate instance of the app.

You could conceivably achieve the same goal by defining special routes that included the necessary routing code, rather than introducing another controller layer. In that case, clients would have to include the routing code in the URLs they used to invoke the Rails app. This may or may not be desirable, depending on circumstances. In any case, it has no effect on the application source code, and so your personal preference for skinny or fat controllers is unaffected.

It's possible that the need for scaling has to do with the volume of data transferred to and from the database. We can accommodate that by changing the database adapter and using a different DBMS product; perhaps moving from MySQL to Oracle or UDB. That's transparent to the application code, as well, unless you've nailed your feet to the floor by using stored procedures.

Even if you've nailed your feet to the floor, you can pull them loose again by doing away with the built-in database access in the models and having them call a service layer instead; something like an enterprise service bus, perhaps. Your database resources (or whatever the real source of data happens to be) can be isolated behind the service layer where the details are transparent to your application code. In this case, your personal preference may affect the difficulty of the change.

This is a fairly typical situation in enterprise IT organizations, including those whose feet are not nailed to the floor. Business apps need to pull data from a variety of back-end sources, and the sources may change without notice provided the interface remains stable. If you're using a paper-thin model that acts as nothing more than a persistence wrapper, it's a trivial modification to call the service layer instead of a local database adapter, and your existing unit tests provide a safety net for the changes. If you have a fat model design, you may have more work to do. Realistically, this sort of architectural issue would probably be known at design time, so it's really an academic exercise. When the day comes that there are a lot of legacy enterprise Rails apps around, it may become more common situation.

Personally, I like the skinny model approach because it simplifies testing. I don't mind seeing a bit of business logic in the controllers, and since Rails is a tool for CRUD webapps, there won't be extensive business logic in most cases anyway. If the controllers start to look too fat, it's easy enough to move some of the logic into helper classes. If the helper classes call the models, then you can think of them as a sort of "business layer," although that's really a matter of code organization and readability and not a matter of architecture, since the Rails app will be deployed all in one chunk. Another way to say it (although in a post this long the last thing we need is another way to say it) is that for a Rails app MVC is a design pattern rather than an architectural pattern.

My conclusion about this matter is that it doesn't make a difference one way or another. Structure your Rails app in the way that makes the most sense to you. If you need to scale it later on, it will make little or no difference whether you've written thick or thin controllers. Of course, this conclusion doesn't automatically apply to other languages or frameworks, or to applications other than webapps.

I reserve the right to change my mind without notice as I continue to learn about Rails development.


Posted by Dave Nicolette at 3:44 PM EDT
Post Comment | View Comments (2) | Permalink

Sunday, 11 October 2009 - 2:14 PM EDT

Name: "Ryan Hoegg"

You might try using Modules to put your business logic in your "model" layer from the controller's perspective, without adding code to the actual model classes.  You could then unit test the logic in these modules without using the generated persistence code.

Wednesday, 21 October 2009 - 10:00 PM EDT

Name: "Dave Nicolette"
Home Page: http://www.davenicolette.net/agile

Hi Ryan,

Thanks for the suggestion. I'll give it a try. I'm still getting my feet wet with Ruby and Rails, and I appreciate the advice.

Cheers,

Dave

View Latest Entries