Enterprise Grails - Best Practices
After my presentation Adopting Grails at GR8Conf in Copenhagen, people approached me regarding the best practices part. Let’s recap and spend more words than available on the slides (http://www.slideshare.net/gr8conf/gr8conf-2011-adopting-grails). If I’m referring to ‘you’ in the text below, I assume that ‘you’, the reader, is the one that introduces Grails to a team
Clearly articulate the benefits of the framework
Ideally you have a meeting with your core team to discuss the benefits and come to a consensus. From the discussion create a pros/cons table which includes alternatives to Grails (e.g. doing all by yourself or another framework). You need this at least for your stakeholders. Do not rely on hype. Have one or more prototypes or previously created Grails apps available.
Create a local community
Once the team starts working with Grails, you will find people that like it and others that don’t. That’s ok. Find the people that think like you (the ones who like Grails) and start creating a community. Exchange tips and tricks and share it with the rest of the team.
Use the external community and help others using it
Sooner or later you will find yourself in a situation, where the manual or book does not help. This is the time to reach out for the mailing lists and/or IRC channels. Teach your team to work with these tools. It’s good to find a solution by yourself, but not for too long. Otherwise it becomes frustrating; and most of the problems have been seen before.
Provide tools
One of the very first things to do is to provide the team with a good IDE. In case you are not using IntelliJ or Spring-Tool-Suite (STS/Eclipse) now is the time to do so. I found this topic (IDE) the hardest one in the beginning of our Grails adoption.
The next is to install test coverage and static analysis tools like Codenarc.This raises the trust level. Also teach your team to use Grails-plugins.Often your own development can be replaced by simply installing a plugin. Or at least check out the plugin code and learn from it, if you cannot use it out of the box.
Find situations/cases in which Groovy/Grails really make a difference
In order to raise the awareness, expose special features of your development environment to other teams. E.g grails run-app vs. classical web-app redeploy cycle with heavy app servers is a huge time saver. In the long run this makes some teams really jealous.
Stay close to the Grails sweet-spot as long as possible
Grails is really flexible and this is one of its strengths. But the more you move away from the sweet-spot, the more special are your problems compared to the rest of the community. Also upgrading Grails to newer versions is more difficult if your project contains non-standard parts, like POJO based domain models or handcrafted hibernate mappings.
Be available
If you are the person who introduced Grails provide help when necessary. Helping the team should be of high priority. The will ask you many questions and if they don’t get answers they will find a solution to their problem somehow. This could be old-school Java code because of lacking Groovy experience or strange controller constructs.
Don’t let the team swim alone. At least for a while they need you.
Offer/Initiate code reviews/pair-programming
In order to bring the team up to speed with their Groovy/Grails skills offer yourself for code reviews and/or pair programming. Do not wait for them to ask you for reviews or programming sessions. Stay in the driver seat and push forward.
Maintain a catalog of design principles and guidelines
Grails gives you a frame to create your apps, but still enough freedom to stay flexible. But in your team you want to constrain lets say the number of ways to create XML response data or how you handle errors. Provide your team with architecture and coding guidelines. Make clear how things have to be done to avoid chaos. For example, give advice on
- Types, when to use types and when to use def
- Inheritance vs. composition
- controller and services responsibilities
- XML generation strategies
- Java vs. Groovy coding
- etc.
Further ideas
Finally, the following ideas might help you to run your Grails adoption even smoother:
- Use the create-* scripts to introduce own templates for controllers and services
- Provide own scripts for lab-specific tools
If you have more ideas, let me know, since Grails adoption is an ongoing process and I’m happy to know how it worked for you.
Playing with GPars: Managing the upper limit of queues
I was invited speaker at GR8Conf-Europe in Copenhagen. As such I had the pleasure to watch all the other speakers on great Groovy, Griffon and Grails technology. Especially the presentation on GPars from Václav Pech raised my attention. I started right away to play around and created a little example based on the Dataflow concept. My first attempt of a simple dataflow processing chain ended in an OutOfMemory error. So I started a discussion with Václav, who gave the necessary advice and fixes to my code. In this episode I will wrap up what has been discussed (the original mailing list entry is here). My original example looked like this
import groovyx.gpars.dataflow.DataFlowBroadcast
import groovyx.gpars.dataflow.DataFlowQueue
import static groovyx.gpars.dataflow.DataFlow.operator
import static groovyx.gpars.dataflow.DataFlow.task
def b = new DataFlowQueue()
def a0 = new DataFlowBroadcast()
operator([inputs:[a0.createReadChannel()], outputs:[b], maxForks:2]) { val ->
bindOutput "$val[*]: ${val*val}"
}
operator([inputs:[a0.createReadChannel()], outputs:[b], maxForks:2]) { val ->
bindOutput "$val[+]: ${val+val}"
}
task {
int i = 1
while (true) {
println "${i++}: $b.val"
}
}
for (i in 1..1000000) {
a0 << i
}
The code is creating an integer sequence, which is passed through two operators which create the double value (val+val) and the square (val*val). The DataFlowBroadcast ensures that both operators get the same value. Finally the result of both operations is pushed to printing task via a DataFlowQueue. As I said, this code failed due to a higher rate of production than the rate of consumption, leading to an overflow in one of the queues.Now Václav suggested two changes. The first simply checks for the size of all queues against a MAX value and if that is reached, the corresponding thread is paused via Thread.yield(). Here is the code
import groovyx.gpars.dataflow.DataFlowQueue
import static groovyx.gpars.dataflow.DataFlow.operator
import static groovyx.gpars.dataflow.DataFlow.splitter
import static groovyx.gpars.dataflow.DataFlow.task
def b = new DataFlowQueue()
def a0 = new DataFlowQueue()
def a1 = new DataFlowQueue()
def a2 = new DataFlowQueue()
MAX=100
splitter(a0, [a1, a2])
operator([inputs: [a1], outputs: [b], maxForks: 4]) { val ->
bindOutput "$val[*]: ${val * val}"
}
operator([inputs: [a2], outputs: [b], maxForks: 4]) { val ->
bindOutput "$val[+]: ${val + val}"
}
task {
int i = 1
long t0 = System.currentTimeMillis()
int count = 0
while (true) {
b.val
println "${i++}: $b.val"
}
}
int i=0
while (i<50000) {
a0 << i++
while ([a0.length(), a1.length(), a2.length()].max()>MAX) Thread.yield()
}
Also the broadcast queue is replaced by a splitter and two DataFlowQueues, because DataFlowBroadcast has no length() method, which is necessary for the upper bound check.
He also suggested a second alternative, which implements a communication between consumer and producer than an a global check on queue length. In that example, a special token is passed around as a confirmation message to signal the next chunk of delivery. This solution allows that producer and consumer are further away from each other. The downside is, that each operator has to handle this special token in its processing engine. Also note that now the length() method is no longer required and we can go back to DataFlowBroadcast.
import groovyx.gpars.dataflow.DataFlowBroadcast
import groovyx.gpars.dataflow.DataFlowQueue
import static groovyx.gpars.dataflow.DataFlow.operator
import static groovyx.gpars.dataflow.DataFlow.task
def b = new DataFlowQueue()
def c = new DataFlowQueue()
def a0 = new DataFlowBroadcast()
operator([inputs: [a0.createReadChannel()], outputs: [b], maxForks: 2]) { val ->
if (val == -1) bindOutput val
else bindOutput "$val[*]: ${val * val}"
}
operator([inputs: [a0.createReadChannel()], outputs: [b], maxForks: 2]) { val ->
if (val == -1) bindOutput val
else bindOutput "$val[+]: ${val + val}"
}
task {
int i = 1
long t0 = System.currentTimeMillis()
int count = 0
while (true) {
def value = b.val
if (value == -1) c << value
else {
println "${i++}: $value "
}
}
}
int MAX = 100
int n = 0
int i=0
while (i<50000) {
a0 << -1
MAX.times {
a0 << i++
}
c.val
c.val
}