Some times we can forget that one of the most interesting uses of a language like Groovy is to make small scripts, beyond the need of playing with the rules and peculiarities of monster frameworks like Grails. This point is interesting not only for Groovy programmers, but also (specially) for Java programmers afraid of using Groovy in production code for being "dirty".
In this talk we'll see how Groovy is a language specially prepared for scripting, and very easy to handle for programmers with a Java background. We'll also explore some peculiarities of Groovy scripts in comparison to complete applications, and some interesting ideas which can be applied to scripts, based on my experience, and can be useful to anyone.
8. About Groovy
● Dynamic Language, prepared for scripting
● Runs in the JVM
o Lots of Java libraries
o Can use the code of our Java services
● Source code “almost” compatible with Java
o Main difference: == operator means equals()
o If you make your script in Java, it’ll WORK
● Other “cool” features, mainly inspired by Python
12. Mix your Production code
with direct accesses to the
DB, config, TServices...
13. Hello World (Java, but works also as
Groovy)
import utils.*;
World world = new World();
world.setId("mongo");
world.setName("Mongo");
System.out.println("Hello " +
world.getName() + "!");
package utils;
public class World {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
$GRSCRIPTS/helloworld.groovy $GRSCRIPTS/utils/World.groovy
14. Hello World (Groovy style)
def world = new World(id: "mongo", name: "Mongo")
println "Hello ${world.name}!"
class World {
String id
String name
}
$GRSCRIPTS/helloworld.groovy
$ cd $GRSCRIPTS
$ groovy helloworld.groovy
Hello Mongo!
$
15. Hello World (Groovy style)
def world = new World(id: "mongo", name: "Mongo")
println "Hello ${world.name}!"
class World {
String id
String name
}
$GRSCRIPTS/helloworld.groovy
$ cd $GRSCRIPTS
$ groovy helloworld.groovy
Hello Mongo!
$
16. Hello World (Groovy style)
def world = new World(id: "mongo", name: "Mongo")
println "Hello ${world.name}!"
class World {
String id
String name
}
$GRSCRIPTS/helloworld.groovy
$ cd $GRSCRIPTS
$ groovy helloworld.groovy
Hello Mongo!
$
17. Hello World (Groovy style)
def world = new World(id: "mongo", name: "Mongo")
println "Hello ${world.name}!"
class World {
String id
String name
}
$GRSCRIPTS/helloworld.groovy
$ cd $GRSCRIPTS
$ groovy helloworld.groovy
Hello Mongo!
$
45. “Clean” Services Clients
import utils.*
TServiceClient subscriptionService = new TServiceClient(
base: "http://xxxxx/SubscriptionService/")
def statusData = subscriptionService.getSubscriptionStatus([id: 80644970])
println "Result of the call: ${statusData}"
println "Subscription status: ${statusData.status}"
● Generic, equivalent to CURLs
● Using dynamic Groovy features
● Methods of the service are used transparently as if it
were a “real” client interface
48. Classes for named access to the
environment
import utils.*
import groovy.sql.*
def chargingService = TuentiEnv.jsc.prod.charging
def balanceData = chargingService.getBalance([id: 80644970], "es")
Sql chargingDb = TuentiEnv.jsc.prod.charging.sql
● Easy named access to:
o Databases
o Service clients
o For every environment
52. Services Console: easily test your
services
aviedma@dev6:~$ groovysh
Groovy Shell (2.2.2, JVM: 1.6.0_26)
Type ':help' or ':h' for help.
---------------------------------------------------------------------
groovy:000> utils.TuentiEnv.initConsole()
===> null
groovy:000> jsc.prod.charging.getBalance([id: 80644970], "es")
===> [amount:[amountInMillieuros:0], isAvailable:true]
groovy:000> pretty jsc.prod.subscription.getSubscriptionStatus([id:
80644970])
===> {
"isAvailable": true,
"lastUpdateTime": 1423501862,
"status": "active"
}
groovy:000>
53. Services Console: easily test your
services
aviedma@dev6:~$ groovysh
Groovy Shell (2.2.2, JVM: 1.6.0_26)
Type ':help' or ':h' for help.
---------------------------------------------------------------------
groovy:000> utils.TuentiEnv.initConsole()
===> null
groovy:000> jsc.prod.charging.getBalance([id: 80644970], "es")
===> [amount:[amountInMillieuros:0], isAvailable:true]
groovy:000> pretty jsc.prod.subscription.getSubscriptionStatus([id:
80644970])
===> {
"isAvailable": true,
"lastUpdateTime": 1423501862,
"status": "active"
}
groovy:000>
54. Services Console: easily test your
services
aviedma@dev6:~$ groovysh
Groovy Shell (2.2.2, JVM: 1.6.0_26)
Type ':help' or ':h' for help.
---------------------------------------------------------------------
groovy:000> utils.TuentiEnv.initConsole()
===> null
groovy:000> jsc.prod.charging.getBalance([id: 80644970], "es")
===> [amount:[amountInMillieuros:0], isAvailable:true]
groovy:000> pretty jsc.prod.subscription.getSubscriptionStatus([id:
80644970])
===> {
"isAvailable": true,
"lastUpdateTime": 1423501862,
"status": "active"
}
groovy:000>
55. Tracking scripts execution
import utils.*
TuScript.track("Say hello to the world”)
println "Hello world!"
(...)
● A single line at the beginning of the script
o Logs start-end time and duration of the run (even if it
fails or is killed)
o Without changes in the scripts, it can be used to
register the script execution for monitoring
56. Tracking scripts execution
class TuScript {
public static void track(String description) {
if (metadata != null) {
finishCurrentScript()
} else {
addShutdownHook { finishCurrentScript() }
captureSignal("INT")
captureSignal("TERM")
}
metadata = new ScriptMetadata(description: description)
logScriptStart()
}
public static void finishCurrentScript() {
if (metadata != null) {
logScriptEnd()
}
metadata = null
}
private static void captureSignal(String signal) {
def oldHandler = Signal.handle(new Signal(signal), [handle: { sig ->
logSignal(signal)
if (oldHandler) oldHandler.handle(sig)
}] as SignalHandler);
}
(...)
57. Tracking scripts execution
class TuScript {
public static void track(String description) {
if (metadata != null) {
finishCurrentScript()
} else {
addShutdownHook { finishCurrentScript() }
captureSignal("INT")
captureSignal("TERM")
}
metadata = new ScriptMetadata(description: description)
logScriptStart()
}
public static void finishCurrentScript() {
if (metadata != null) {
logScriptEnd()
}
metadata = null
}
private static void captureSignal(String signal) {
def oldHandler = Signal.handle(new Signal(signal), [handle: { sig ->
logSignal(signal)
if (oldHandler) oldHandler.handle(sig)
}] as SignalHandler);
}
(...)
58. Tracking scripts execution
class TuScript {
public static void track(String description) {
if (metadata != null) {
finishCurrentScript()
} else {
addShutdownHook { finishCurrentScript() }
captureSignal("INT")
captureSignal("TERM")
}
metadata = new ScriptMetadata(description: description)
logScriptStart()
}
public static void finishCurrentScript() {
if (metadata != null) {
logScriptEnd()
}
metadata = null
}
private static void captureSignal(String signal) {
def oldHandler = Signal.handle(new Signal(signal), [handle: { sig ->
logSignal(signal)
if (oldHandler) oldHandler.handle(sig)
}] as SignalHandler);
}
(...)
59. Split execution in steps
sql.eachRow("""
<loooooong SQL>
""") { row ->
processRow(row)
}
void processRow(row) {
println "${row.id}: ${row.name} ${row.surname}"
(... lots and lots of processing ...)
}
60. Split execution in steps
sql.eachRow("""
<loooooong SQL>
""") { row ->
processRow(row)
}
void processRow(row) {
println "${row.id}: ${row.name} ${row.surname}"
(... lots and lots of processing ...)
}
61. Split execution in steps
void step1(String file) {
Dump dump = new Dump(file)
dump.withWriter(fieldNames) { out ->
sql.eachRow("""
<loooooong SQL>
""") { row ->
out.insert(row)
}
}
}
void step2(String file) {
Dump dump = new Dump(file)
dump.eachRow { out ->
processRow(row)
}
}
void processRow(row) {
println "${row.id}: ${row.name} ${row.surname}"
(... lots and lots of processing ...)
}
62. Split execution in steps
void step1(String file) {
Dump dump = new Dump(file)
dump.withWriter(fieldNames) { out ->
sql.eachRow("""
<loooooong SQL>
""") { row ->
out.insert(row)
}
}
}
void step2(String file) {
Dump dump = new Dump(file)
dump.eachRow { out ->
processRow(row)
}
}
void processRow(row) {
println "${row.id}: ${row.name} ${row.surname}"
(... lots and lots of processing ...)
}
63. Split execution in steps
void step1(String file) {
Dump dump = new Dump(file)
dump.withWriter(fieldNames) { out ->
sql.eachRow("""
<loooooong SQL>
""") { row ->
out.insert(row)
}
}
}
void step2(String file) {
Dump dump = new Dump(file)
dump.eachRow { out ->
processRow(row)
}
}
void processRow(row) {
println "${row.id}: ${row.name} ${row.surname}"
(... lots and lots of processing ...)
}
Same processing code, no changes needed
64. Running Shell commands
def process = 'ls /home/andres'.execute()
def procOutput = new InputStreamReader(process.in)
procOutput.eachLine { line ->
println line
}
process.waitFor()
println "** Return code: ${process.exitValue()}"
65. Running Shell commands
def process = 'ls /home/andres'.execute()
def procOutput = new InputStreamReader(process.in)
procOutput.eachLine { line ->
println line
}
process.waitFor()
println "** Return code: ${process.exitValue()}"
66. Running Shell commands
def process = 'ls /home/andres'.execute()
def procOutput = new InputStreamReader(process.in)
procOutput.eachLine { line ->
println line
}
process.waitFor()
println "** Return code: ${process.exitValue()}"
def process = 'echo "Hola caracola"'.execute()
println process.text
def process = ['echo', 'Hola caracola'].execute()
67. Running Shell commands
def process = 'ls /home/andres'.execute()
def procOutput = new InputStreamReader(process.in)
procOutput.eachLine { line ->
println line
}
process.waitFor()
println "** Return code: ${process.exitValue()}"
def process = 'echo "Hola caracola"'.execute()
println process.text
def process = ['echo', 'Hola caracola'].execute()
def process = 'ls /home/andres/*'.execute()
def process = ['sh', '-c', 'ls /home/andres/*'].execute()
68. Running Shell commands
def process = 'ls /home/andres'.execute()
def procOutput = new InputStreamReader(process.in)
procOutput.eachLine { line ->
println line
}
process.waitFor()
println "** Return code: ${process.exitValue()}"
def process = 'echo "Hola caracola"'.execute()
println process.text
def process = ['echo', 'Hola caracola'].execute()
def process = 'ls /home/andres/*'.execute()
def process = ['sh', '-c', 'ls /home/andres/*'].execute()
def process = 'ls'.execute()
def process = 'ls'.execute([], new File('/home/andres'))