Monday, July 7, 2008

MVC: How to write controllers

I've been working on many projects (web and desktop applications), and I must say that the way MVC is implemented varies a lot. That's probably a good thing, since every project is different. I believe, however, that there are some common patterns that could improve most of the MVC applications.

1. Keep it simple


Don't create additional tiers unless they are needed. Every class should clearly belong to either Controller, Model or a View.


2. Thin controller, fat model


This is probably the most useful lesson I learnt from the Rails world. I'm not going to elaborate on that here, as it was covered in many other places. Just move all your logic to models. It's that simple.


3. RESTful design

If you work on a web application, and still don't know what REST is, then go and read about it. It's easy and it will simplify your design a lot. Basically, it makes you think about your app as a set of resources. Every action in your controllers does one of the following with your resources: new, create, edit, update, show, destroy, index(list). At first, it can feel as a limitation, but it is the kind of limitation that actually helps you.

Many people argue that not everything can be RESTified. I agree that there are places where REST is not needed, however it's always a good exercise to find a RESTful solution.


4. Communication between controllers and models

As always, when you delegate some work further, you need to control the result somehow. The same happens when you move all the logic from controllers to models.

There are three ways (that I'm aware of) of implementing the communication between a controller and a model.

4a. Return codes

You use return codes when your model methods return not the data, but the result of an operation.

An example here is ActiveRecord::Base.save method which returns true when the save was successful, false otherwise.

order = Order.new(params[:order])
if order.save
flash[:message] = "The order was created!"
redirect_to order
else
flash[:errors] = "Something was wrong"
redirect_to new_order_path
end

In my opinion it's a good solution for simple situations. However, I don't like the "if" condition here and also it can go messy with more complex examples.

4b. Ask the object for its state


credit_card = CreditCard.new(params[:credit_card])
if credit_card.valid?
if order.capture_payment(credit_card)
flash[:message] = "success"
else
flash[:message] = "payment failure"
end
else
flash[:message] = "credit card validation error"
end
redirect_to order
This is slightly better than a), because the return code knowledge is hidden in method calls, but we still see the problem of many "if" statements.

4c. Don't ask, tell. Use custom exceptions

That's my favorite, because it eliminates all the "if"s from my controller code. I know it's just a syntactical change, but I prefer to see something like this in my controller, rather than the example above:

begin
order.pay(credit_card)
flash[:message] = "success"
rescue CreditCardNotValid
flash[:message] = "credit card validation error"
rescue PaymentFailed
flash[:message] = "payment failure"
end

Here we use custom exceptions to define a "protocol" for the communication between a model and a controller. Obviously we need to define the exceptions somewhere. I like to add them at the top of the associated model class:

class CreditCardNotValid < Exception
class PaymentFailed < Exception

class Order < ActiveRecord::Base
def pay(credit_card)
...
end
end


So, what are your favorite patterns for writing controllers?