Friday, October 8, 2010

Grails controllers and command objects

Hi there folks!

This is one of the days that come from time to time when one feels as if a lightning struck went down in extreme proximity :)

Well, I've been asked to implement a functionality, a very simple one. Take 2 parameters from the URL, verify if all of them are given, check if one is a valid identifier of an existing entry in the database and if so set a given property of that domain object to the value of the other parameter. Let's see how this might be done:
class CarController {
def index = { ... }

def setCarAvailability = {
def car = Car.get(params.id)
if (car && params.available) {
car.avaiable = params.available
car.save(flush: true)
redirect action: 'index'
} else {
flash.message = "Error: either no valid car id, available not given or saving failed"
redirect action: 'index'
}
}
}

Pretty easy, right?

Now somebody please tell me what's wrong with this picture?

At first, if this is a simple application with one or two controllers having one or two actions this seems completely acceptable, right? But what if there are 5 or 10 actions in 20 different controllers? The answer is simple: UNMAINTAINABLE RAVIOLI CODE!!!

To counteract this I've came across an idea of making command objects not just dumb value placeholders with validation capabilities but to actually implement the command as it should be (given its proud name).

So at the end of the day this is how I'd really code this:
class CarController {
def index = { ... }

def setCarAvailability = {
SetCarAvailabilityCommand command ->

if (command.validate()) {
command.execute()
redirect action: 'index'
} else {
flash.message =
command.errors.allErrors.collect { error ->
error -> error.field
}.join(", ")
redirect action: 'index'
}
}
}

class SetCarAvailabilityCommand {
Car car
Boolean available

static constraints = {
car nullable: false, validator: { it?.attached }
available blank: false, required: false
}

def execute() {
car.available = available
car.save(flush: true)
}
}
How about that, ha? Nice separation of concerns (actual entity that does the job separated from controller that should only care about controlling what should go next), automatic parameter validation, clean naming and if you put those two into the same file then even automatic reloading works!

The key here is that the parameter must be named "car.id" for this to work. This way the automatic binding will find the actual Car instance and put it into our command object automagically.
  <g:form action="setCarAvailability">
<g:textField name="car.id"/>
<g:checkBox name="available"/>
<br/><br/>
<g:submitButton name="action" />
</g:form>

You might have noticed the funny-looking validator for car field. This just checks that the Car instance came from the database. If one would be to specify an id that does not exists it'll fail.

Another neat thing is that all of the commands have similar interface so generally you can create a helper method that will get some parameters (where to redirect after failure for example) and have every single action be actually a one-liner! And remember: since this is a dynamic language with duck typing it does not have to be a java interface strictly speaking! Commands just have to talk and walk like commands :)

Oh, and if you'd like to delegate some code to service I rush to say that dependency injection works as expected with command objects so no worries :)

I would not be myself if a ready-to-use example would not be here :)

I wish you all some nice and well separated code!

4 comments:

Steve said...

Rockin cool!

Vishal Shiralkar said...

Very Nicely explained. After Reading this blog I came to the conclusion that the blogger has done enough home work before writing this page.
Thank You for your work.


Vishal

RoMuLo said...

Your idea is very nice, I make it this way, but I'm having a problem with this solution; when it's a "create action", my domain object into "MyCommand" always will be a new object. Because of this fact, it always return to me the error "the field 'MyDomain' can't be null".

So, how can I validate my "domain object" into "MyCommand", but accepting a "myObject.id == null"

(< input type='hidden' name='myDomain.id'/ >)
when its an "action create"?

Can you help with this?
tks!

In case, an "action create" like this one above:

"def create = { MyCommand cmd ->
if(cmd.validate())
cmd.myObject.save(flush: true)
else
.
.
}

MyCommand {
MyDomain myObject

static constraint {
myObject nullable: false, validate: { it.attached }
}
}"

RoMuLo said...

Your idea is very nice, I make it this way, but I'm having a problem with this solution; when it's a "create action", my domain object into "MyCommand" always will be a new object. Because of this fact, it always return to me the error "the field 'MyDomain' can't be null".

So, how can I validate my "domain object" into "MyCommand", but accepting a "myObject.id == null"

(< input type='hidden' name='myDomain.id'/ >)
when its an "action create"?

Can you help with this?
tks!

In case, an "action create" like this one above:

"def create = { MyCommand cmd ->
if(cmd.validate())
cmd.myObject.save(flush: true)
else
.
.
}

MyCommand {
MyDomain myObject

static constraint {
myObject nullable: false, validate: { it.attached }
}
}"