The domain object model
The model has a central position in a Play application. It’s the domain-specific representation of the information on which the application operates.
Martin Fowler defines it as:
Responsible for representing concepts of the business, information about the business situation, and business rules. State that reflects the business situation is controlled and used here, even though the technical details of storing it are delegated to the infrastructure. This layer is the heart of business software.
A common Java anti-pattern is to keep the model as a set of simple Java Beans and put the application logic back into a “service” layer which operates on the model objects.
Martin fowler again has named this anti-pattern Anemic object model:
The basic symptom of an Anemic Domain Model is that at first blush it looks like the real thing. There are objects, many named after the nouns in the domain space, and these objects are connected with the rich relationships and structure that true domain models have. The catch comes when you look at the behavior, and you realize that there is hardly any behavior on these objects, making them little more than bags of getters and setters. Indeed often these models come with design rules that say that you are not to put any domain logic in the domain objects. Instead there are a set of service objects which capture all the domain logic. These services live on top of the domain model and use the domain model for data.
The fundamental horror of this anti-pattern is that it’s so contrary to the basic idea of object-oriented design, which is to combine data and process together. The anemic domain model is really just a procedural style design, exactly the kind of thing that object bigots like me (and Eric) have been fighting since our early days in Smalltalk. What’s worse, many people think that anemic objects are real objects, and thus completely miss the point of what object-oriented design is all about.
Properties simulation
If you take a look at Play sample applications, you will often see that classes declare public variables. Now if you’re a Java developer with any experience at all, warning sirens are probably clanging like mad at the sight of a public variable. In Java (as in other object-oriented languages), best practice says to make all fields private and provide accessors and mutators. This is to promote encapsulation, a concept critical to object oriented design.
Java has no truly built-in property definition system. It uses a convention named Java Beans: a property on a Java object is defined by a couple of getXxx/setXxx methods. If the property is read-only there is only a getter.
Although the system works well, it’s very tedious to write. For each property you have to declare a private variable and write two methods. Thus, most of time the getter and setter implementation is always the same:
private String name;
public String getName() {
return name;
}
public void setName(String value) {
name = value;
}
The Model portion of the Play framework automatically generates this pattern while keeping your code clean. Effectively, all public variables become instance properties. The convention is that any public, non-static, non-final field of a class is seen as a property.
For example, when you define a class like this:
public class Product {
public String name;
public Integer price;
}
The loaded class will be:
public class Product {
public String name;
public Integer price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
}
Then when you want to access a property you can just write:
product.name = "My product";
product.price = 58;
Which is translated at load time to:
product.setName("My product");
product.setPrice(58);
Warning!
You can’t directly use getter and setter methods to access properties if you rely on automatic generation. These methods are generated at runtime. So if you reference them in code you write, the compiler won’t find the methods and will generate an error.
Of course you can define the getter and setter methods yourself. If a method exists Play will use the existing accessors.
So to protect the Product class’ price property value, you can write:
public class Product {
public String name;
public Integer price;
public void setPrice(Integer price) {
if (price < 0) {
throw new IllegalArgumentException("Price can’t be negative!");
}
this.price = price;
}
}
Then if you try to set a property to a negative value an exception will be thrown:
product.price = -10: // Oops! IllegalArgumentException
Play will always use the defined getter or setter if it exists. Look at this code:
@Entity
public class Data extends Model {
@Required
public String value;
public Integer anotherValue;
public Integer getAnotherValue() {
if(anotherValue == null) {
return 0;
}
return anotherValue;
}
public void setAnotherValue(Integer value) {
if(value == null) {
this.anotherValue = null;
} else {
this.anotherValue = value * 2;
}
}
public String toString() {
return value + " - " + anotherValue;
}
}
From another class, you can try these assertions:
Data data = new Data();
data.anotherValue = null;
assert data.anotherValue == 0;
data.anotherValue = 4
assert data.anotherValue == 8;
Yes it works. And because the enhanced class follows the Java Beans convention, when you use your object with a library that expects a JavaBean it will work perfectly!
Set-up a database to persist your model objects
Most of the time you will need to save the model object data permanently. The most natural way is to save this data into a database.
During development, you can quickly set up an embedded database either in-memory or save it in a sub-directory within your application using the db configuration.
The Play distribution includes JDBC drivers for H2 and MySQL in the $PLAY_HOME/framework/lib/
directory. If you are using a PostgreSQL or Oracle database, for example, then you should add the JDBC driver library there, or in your application’s lib/
directory.
To connect to any JDBC compliant database. Just add the driver library to and define the JDBC properties db.url, db.driver, db.user and db.pass:
db.url=jdbc:mysql://localhost/test
db.driver=com.mysql.jdbc.Driver
db.user=root
db.pass=
You can also configure a JPA dialect with jpa.dialect.
From your code, you can then obtain a java.sql.Connection
from the play.db.DB
and use it in the standard way.
Connection conn = DB.getConnection();
conn.createStatement().execute("select * from products");
Persist your object model with Hibernate
You can use Hibernate (through JPA) to persist your Java objects in the Database automatically.
When you define JPA entities by adding @javax.persistence.Entity
annotations to any Java object, Play will automatically start a JPA entity manager.
@Entity
public class Product {
public String name;
public Integer price;
}
Warning!
A common mistake is to use the Hibernate @Entity
annotation instead of the JPA one. Remember that Play uses Hibernate through the JPA API.
You can then obtain the EntityManager from the play.db.jpa.JPA
object:
EntityManager em = JPA.em();
em.persist(product);
em.createQuery("from Product where price > 50").getResultList();
Play provides a nice support class to help you to deal with JPA. Just extend play.db.jpa.Model
.
@Entity
public class Product extends Model {
public String name;
public Integer price;
}
And then manipulate the Product
object using simple methods on the Product
instances:
Product.find("price > ?", 50).fetch();
Product product = Product.findById(2L);
product.save();
product.delete();
Support for multiple databases
You can configure Play to use multiple (separate) databases.
The default database is configured by configuration parameters in conf/application.conf
whose keys start with ‘db.’ (e.g: db.url
). To configure an additional database, add an underscore and a suffix to the ‘db’ part of the key, like this:
db_other.url=jdbc:mysql://localhost/test
db_other.driver=com.mysql.jdbc.Driver
db_other.user=root
db_other.pass=
This database configuration is now called ‘other’ in Play. For example, configure JPA for this ‘other’ configuration like this:
db_other.jpa.dialect=<dialect>
You can then access this configuration from your application’s Java code like this:
Connection conn = DB.getDBConfig("other").getConnection()
DB.getDBConfig(configName)
returns an Object with all the methods you usually find as statics methods in the play.db.DB
class.
Keep the model stateless
Play is designed to operate in a ‘share nothing’ architecture. The idea is to keep the application completely stateless. By doing this you will allow your application to run on as many server nodes as needed at the same time.
What are the common traps you should avoid to keep the model stateless? Do not store any object on the Java heap for multiple requests
When you want to keep data across multiple requests you have several choices:
- If data is small and simple enough, store it into the flash or the session scope. However these scopes are limited to about 4 KB each, and allow only String data.
- Save the data permanently into persistent storage (like a database). For example if you need to create an object with a “wizard” that spans multiple requests:
- Initialize and persist the object into the database at the first request.
- Save the newly-created object’s ID into the flash scope.
- During successive requests, retrieve the object from the database using the object ID, update it, and save it again.
- Save the data temporarily into a transient storage (such as the Cache). For example if you need to create an object with a “wizard” that spans multiple requests:
- Initialize the object and save it into the Cache at the first request.
- Save the newly-created object’s key into the flash scope
- During successive requests, retrieve the object from the cache (with the correct key), update it, and save it into the cache again.
- At the end of the last request in the chain, save the object permanently (into the database for example)
The Cache is not a reliable storage but if you put an object in the cache you should be able to retrieve it. Depending on your requirements, the Cache can be a very good choice and a good replacement for the Java Servlet session.
Continuing the discussion
Now we’ll check how to persist the model using JPA persistence.