Play!> framework design pattern #3 : Avoid Anemic Domain Model
In my last post about data integrity, I noticed that with the MVC model of Play, you tend to put many logic in the controller, even a part of the domain model, thus creating an Anemic Domain Model as described by Martin Fowler.
And it’s what I made in my Codebaord Project to test Play! Framework. In my model, I have a class Domain
that is only a database wrapper (with only annotations), and a controller Domains
that has code that should be in the model (see old controller source with this wrong choice).
So, I started replace domain code in the domain classes.
Now the controller class is lighter :
public class Domains extends Admin \{
public static void list() \{
List<Domain> domains = Domain.find("order by position asc").fetch();
render(domains);
}
public static void create() \{
Domain domain = new Domain();
render("@edit", domain);
}
public static void edit(Long id) \{
Domain domain = Domain.findById(id);
render(domain);
}
public static void setDefault(Long id) \{
Domain domain = Domain.findById(id);
domain.setDefault();
Map summary = new HashMap();
summary.put(0, 1);
summary.put(1, Messages.get("domain_as_default"));
summary.put(2, id);
renderJSON(summary);
}
public static void save(@Valid Domain domain) \{
if (Validation.hasErrors()) \{
params.flash(); // add http parameters to the flash scope
validation.keep(); // keep the errors for the next request
render("@edit", domain);
}
domain.save();
list();
}
public static void delete(Long id) \{
Domain domain_to_delete = Domain.findById(id);
Map summary = new HashMap();
try \{
domain_to_delete.delete();
summary.put(0, 1);
summary.put(1, Messages.get("domain_deleted"));
summary.put(2, domain_to_delete.id);
} catch (UnsupportedOperationException e) \{
summary.put(0, 0);
summary.put(1, Messages.get(e.getMessage()));
summary.put(2, domain_to_delete.id);
}
renderJSON(summary);
}
public static void move(Long id) \{
String to = params.get("move_to");
Domain domain_to_move = Domain.findById(id);
domain_to_move.move(to);
Map summary = new HashMap();
summary.put(0, Messages.get("domain_moved"));
summary.put(1, domain_to_move.id);
summary.put(2, domain_to_move.position);
renderJSON(summary);
}
}
and the model class is no more "empty" :
@Entity
public class Domain extends Model \{
public String name;
public long position;
public boolean isPublic;
public boolean isDefault;
public String toString() \{
return name;
}
public Domain delete() \{
if (! isDefault) \{
super.delete();
} else \{
throw new UnsupportedOperationException("dont_delete_default");
}
List<Domain> domains = Domain.find("position > ?", position).fetch();
for (Iterator iterator = domains.iterator(); iterator.hasNext(); ) \{
Domain domain = (Domain) iterator.next();
domain.position = domain.position - 1;
domain.save();
}
return this;
}
public Domain setDefault() \{
Domain defaultDomain = Domain.find("isDefault = ?", true).first();
if (defaultDomain != null) \{
defaultDomain.isDefault = false;
defaultDomain.save();
}
isDefault = true;
save();
return this;
}
public void move(String to) \{
if (to.equals("highest")) \{
List<Domain> domains = Domain.find("position < ?", position).fetch();
for (Iterator iterator = domains.iterator(); iterator.hasNext();) \{
Domain domain = (Domain) iterator.next();
domain.position = domain.position +1;
domain.save();
}
position = 1;
} else if (to.equals("higher")) \{
Domain domain_to_swap = Domain.find("position = ?", position - 1).first();
domain_to_swap.position = position;
position = position - 1;
domain_to_swap.save();
} else if (to.equals("lower")) \{
Domain domain_to_swap = Domain.find("position = ?", position + 1).first();
domain_to_swap.position = position;
position = position + 1;
domain_to_swap.save();
} else if (to.equals("lowest")) \{
List<Domain> domains = Domain.find("position > ?", position).fetch();
for (Iterator iterator = domains.iterator(); iterator.hasNext();) \{
Domain domain = (Domain) iterator.next();
domain.position = domain.position - 1;
domain.save();
}
position = Domain.count();
}
save();
}
}
[/code]