I decided to try to migrate it to a Grails platform to get a sense for how difficult that might be. I am going to share the steps that I took to migrate my Tomcat/Spring3/Hibernate3/MySql5 application to Grails/MySql5.
Since I already had a database schema from the Java implementation I decide to leverage that DDL so that both applications could run against the same backend.
Since I knew that the user interface was a Flex interface, I installed the flex plugin.
grails install-plugin flex
This plugin allows one to expose a Grails service as a Flex BlazeDS service by simply adding: static expose = ['flex-remoting'] to the service.
The 3 methods that the Java service exposed were:
* getAllNotes
* saveOrUpdateNote
* deleteNode
Therefore, to maintain the same Flex UI I needed to expose the same API.
The service ultimately ended up looking like the following:
package com.redpointtech.services
import com.redpointtech.domain.Note
class RedpointNotesService {
boolean transactional = true
static expose = ['flex-remoting']
def getAllNotes() {
def allNotes = Note.findAll()
return allNotes
}
def saveOrUpdateNote(Note note) {
def noteid = note.id
println "NOTE ID TO SAVE: " + noteid
// until I can figure out how to get unsavedValue:0 to
// work I need to do my own unsaved value.
if( noteid == 0 ) {
note.id = null;
}
note.save()
}
def deleteNote(Long noteId) {
def note = Note.findById(nodeId)
note?.delete()
}
}
Take special note of the check for a zero noteid. I could not immediately figure out how to tell grails that the 'unsaved-value' is 0 instead of the default null, so I had to make that programmatic. I am still looking into this, but if you know the answer please leave a comment.
To my pleasant surprise, the service was very small and the only extra work to expose the service to Flex was to add the 'static expose = ['flex-remoting']'. Anyone that has exposed Flex services knows that you have to add a servlet to the web.xml, update the flex-remote.xml, add a Spring factory to get the service. While not hard, certainly more work that added one line to a service. Please note, that I did NOT update the web.xml at all.
Next I turned my attention to the Note domain object. I used the the grails create-domain command to create the Note domain object.
To keep this as minimal as possible to start I did not add any constraints - clearly you would normally.
The Note domain class just keeps track of the authors name, the text of the note and the date the note was created. There is no mapping to a User domain object because I purposefully wanted to keep this reference application very simple.
Below is the Grails domain object:
package com.redpointtech.domain
class Note{
//Long id
String author
String text
Date createDate
static constraints = {
}
static mapping = {
table 'notes'
version false
id unsavedValue: 0
text column: "note_text"
createDate column: "create_date"
}
}
There are a couple of aspects to note:
* I commented out the id property because by convention Grails gives every domain object an id property.
* I had to add a mapping section because I was not able to use the grails default values.
If you look up at the service class again, you will see methods on the Note class like 'findAll' but you do not see them here. This is because GORM, which is Grails' Object Relational Mapping which sits on top of hibernate, creates many of the standard methods for you and dynamically adds them at runtime. This dramatically decreases the number of DAO methods you need to write - and really all but eliminates the DAO layer all together.
At this point, I now I have a service and a domain object that represents the Notes. Because I am using a MySql database and not the supported out of the box HSQL DB, I have a little more configuration to do.
The grails-app/conf/DataSource.groovy file contains the datasource definitions for all of the environments.
Below is the version of DataSource.groovy that I used:
dataSource {
pooled = true
driverClassName = "org.hsqldb.jdbcDriver"
username = "sa"
password = ""
}
hibernate {
cache.use_second_level_cache=true
cache.use_query_cache=true
cache.provider_class='com.opensymphony.oscache.hibernate.OSCacheProvider'
}
// environment specific settings
environments {
development {
dataSource {
pooled = false
driverClassName = "com.mysql.jdbc.Driver"
dbCreate = "update" // one of 'create', 'create-drop','update'
url = "jdbc:mysql://localhost/test"
username = "root"
password = "root"
logSql="true"
dialect = org.hibernate.dialect.MySQL5InnoDBDialect
}
hibernate {
show_sql = true
hibernate.format_sql = true
}
}
hsqldevelopment {
dataSource {
dbCreate = "create-drop" // one of 'create', 'create-drop','update'
url = "jdbc:hsqldb:mem:devDB"
}
}
test {
dataSource {
dbCreate = "update"
url = "jdbc:hsqldb:mem:testDb"
}
}
production {
dataSource {
dbCreate = "update"
url = "jdbc:hsqldb:file:prodDb;shutdown=true"
}
}
}
You can see in the environments section, I altered the development datasource to use MySql instead of HSQL. This should be pretty standard datasource configuration for MySql.
The last thing I needed to do was drop the mysql JDBC jar into the lib directory - and it was complete.
In the end I went from absolute zero, to a working Grails application that gave me the same functionality as my Tomcat/Spring/Hibernate application in less than an hour. I have to say, I was very impressed with the speed of development in the grails environment.
My company is currently working on a full featured, consumer facing Flex/Grails application and Grails has still not let me down even in this more challenging real world environment.
Every time I use Grails, I am more convinced of its viability as a production ready framework. It probably helps that it is built upon production ready frames of Spring and Hibernate.
2 comments:
Very informative just what I need.
Happily the solution to "unsaved value" turned out to be very simple.
Ensure the id and version properties are typed as objects in Flex. For example:
public var id:Object;
public var version:Object;
Explanation
Grails (actually GORM) has a feature that allows you to save an object to database without having to worry about whether it should perform an insert or update.
To make this work, GORM needs a way to know whether the object has been saved yet. The default behavior works like this: if the id property is null, the object will be inserted, otherwise it will be updated.
A common pattern in a Grails/Flex database application is:
1. Flex requests an object graph from grails via a Grails service call.
2. Flex stores the object graph in memory and optionally updates the object graph.
3. Flex sends updated objects back to Grails for processing, also via a Grails service.
4. If Grails validations succeed, GORM persists the updated objects to the database.
Flex simply passes back the same id and version properties it received from Grails. This is important since GORM is in charge of managing these properties.
Now if you want to allow Flex to create one or more new objects and have Grails save them, GORM will need to know that these objects are new, so simply ensure the id and version properties of any new objects are null in Flex, and they will be set to null when they are passed to your Grails service, which will trigger inserts rather than updates.
One last detail: this solution assumes you are passing (deserialized) objects via AMF, rather than XML, JSON, etc.
Post a Comment