Sunday, September 21, 2014

Gradle : Next-gen build tool introduction

I've been recently reading about maven 3 and from a page to another I read a bit about gradle, and got interested in it, so I want to try it now and see how it feels and works in practice

so to get started you need to download gradle from here: http://www.gradle.org/downloads
then add the path to gradle bin directory to the PATH system variable and go to CMD/shell then run:

> gradle

if it worked and you got build successful let's move to setup our project structure.

a word about gradle first, is that it promises to keep the great conventions Maven provided but with the flexibility ANT  had since Maven can give you a hard time if you need to customize something.
it's a convention over configuration, yet flexible.

so back to the track, what I want to do in this project is to setup a full spring-mvc rest API server, with multiple tiers (projects) that contains DAOs, Business Entities, Services, and REST tier.

o---[ REST ] -- uses --> [Services] -- uses --> [DAOs] --- > (DB)
           |                                 |                              |
    [                          Business Entitiess                          ]

this is a simple enterprise architecture to provide loosely coupled  tiers, to simulate a real project structure to test what gradle has to offer here.

In maven I would go and create a parent project then add these projects as modules, but when I need to build a project I'll have to build every thing which takes a lot of time.

ok so in gradle we have 3 options to structure our build files [build.gradle]
1) Individual : each sub project has it's own build file
2) Unified : one build file in the master project to build all sub projects
3) Hybrid: a build file in the master project that contains common build configurations and build files in each sub project that are specific for each build file

the Hybrid in my opinion gives you the best of the two worlds so i'll use it here, the other two approaches will implicitly be implemented.

A. Setting up the project structure 

Now the directory structure of our project, i'll go to file system and create the following JSON tree:

master-proj {
build.gradle,
settings.gradle,
dao : { src :{ main : { java : {} , resources : {} } , test :{}} ,  build.gradle },
be : { source :{ code : { java : {} , res : {} }  ,  test :{} }, build.gradle },
bl : { src :{ main : { java : {} , resources : {} } , test :{}} ,  build.gradle },
rest :{ src :{ main : { java : {} , resources : {} } , test :{}} ,  build.gradle }
}

note: i'll be using git also in this tutorial to keep a reference for the code.

if you notice the 'be' src structure is different to test how easy it is to changed default project layout (src/main/java and src/main/resources) that follows maven conventions.

after creating this structure the content of build.gradle in the master project for now will only contain:
allProjects {
 apply plugin: 'java'
}
this tells gradle that all sub projects will use the java plugin without explicitly saying that in the sub projects build files

the contents of settings.gradle will be :
include 'dao', 'be', 'bl', 'rest' 

these are the names of the directories that contain the sub projects

navigate in cmd to master-proj directory and and run :
 > gradle build

what you will get is build directories inside each project like this:


Now let's go to be/build.gradle and add the following segment :

apply plugin: 'java'
sourceSets {
    main {
        java {
            srcDir 'source/code/java'
        }
        resources {
            srcDir 'source/code/res'
        }
    }
}
This will tell gradle java plugin where to find our source code, i'll leave test directory for later.

add the following class in  be/source/code/java folder (don't forget to add the directories for the package) :

package com.blabadi.gradle.be;
public class Person {
}
and in the be/source/code/res folder add an empty file call it : be.properties
 navigate to master-proj/be and run :

>gradle build
 go to master-proj\be\build, you will find more directories : (classes, dependency-cahce, resources)
open the compiler jar in lib to see it's contents:



so now we have something we can work with as start point.

now we want to import this to eclipse but we need to add eclipse config files ( .classpath, .project, .settings) gradle has a plugin for that and we can include it like the java plugin under our master project build file :

allprojects {
  apply plugin: 'java'
  apply plugin: 'eclipse' 
}

now while you are in in master-proj dir , run : 

gradle eclipse

now all your projects can be imported to eclipse to work with easier.

let's go ahead and do that:




it also figured out about our custom source directory in project 'be'



B. Dependency management

in our work we usually need the following types of dependencies:

1- 3rd party dependencies in runtime (spring jars)
2- 3rd party dependencies in compile time only ( javax.servlets )
3- 3rd party testing dependencies (junit)
4- our own frameworks jars / generated web services client jars
5- projects dependencies

some of these are on maven repositories and others aren't, gradle allows us to do everything from importing a jar from file system like ant or use central maven repo or use our own maven repo if we have one, to test this let's plan to add the following dependencies:

for DAO project it will depend on :
- spring for Dependency injection
- hibernate for persistence
- business entities project

for BL :
- spring for Dependency injection
- our own utility library [local jar]
- business entities project
- Junit4 for testing

for REST :
- spring for Dependency injection & spring web
- BL project
- BE project

BE project won't have any dependencies for now.

easiest way to get spring dependencies is to use maven central repo, and since spring core is common between more than one project let's configure this in one place.

In the master project build file i'll add this :

allprojects {
  apply plugin: 'java'
  apply plugin: 'eclipse'

  repositories {    mavenCentral()  } 
}


[':bl', ':dao', ':rest'].each { 
pn -> project(pn) {  dependencies 
      { compile 'org.springframework:spring-context:4.1.0.RELEASE'  }  
   }
}

this tells gradle to user maven central repo to download the spring context jar (and it's dependencies)
and for each of the three projects add the spring-context jar as a compile time dependency

now run gradle eclipse in the master project, it will download the spring jars then update the class path for the three projects to reference the spring-context jar from the local

for the REST project it needs spring-webmvc.jar to expose our rest api's so in rest/build.gradle
add this :

dependencies {
compile 'org.springframework:spring-webmvc:4.1.0.RELEASE'
}

and run gradle eclipse to update classpath file

for the dao we will need hibernate jars so add this to its build.gradle:

dependencies {
compile 'org.hibernate:hibernate-core:4.3.6.Final'
}

now let's convert REST project to a WAR instead of JAR, to do that add the following plugin at the top of its build file :

apply plugin: 'war'

and add the following dependency :

dependencies {
compile 'org.springframework:spring-webmvc:4.1.0.RELEASE'
compile project(':bl')}

this is how we add a dependency  between the projects

now also add the following to complete the dependency between our projects :
in master-project/build.gradle :

[':bl', ':dao', ':rest'].each { pn ->
project(pn) {
dependencies {
compile 'org.springframework:spring-context:4.1.0.RELEASE'
compile project(':be') }
}
}

and in the bl/build.gradle:

dependencies {
compile project(':dao')
}
to add a local jar (not 3rd party and not in a Maven repo) we can do the following:
assume we want our library in the root project in folder called libs, for example I have put xpp3.jar in that folder and I want to reference it inside my projects

1- add flat directory repository :
repositories {
mavenCentral( )
flatDir {
    dirs "$rootDir/libs"
    }
}
$rootDir is a DSL variable that points to the root project base directory.

and in the dependencies add :

compile 'xpp3:xpp3:1.1.4'

it will now be resolved to the libs directory without the need to complex configuration like maven.

another way to do this is to use the files( ) method without declaring a flatDir repository:

compile files('../libs/commons-fileupload-1.3.1.jar')  //[assuming we are in a subproject depedency ]


see the master-proj build.gradle :




ok now the final step is prepare our application for deployment, I have took some time to add the required spring configurations and wire everything together I added few dependencies for junit in the master project build.gradle:

[':bl', ':dao', ':rest'].each { pn ->
project(pn) {
dependencies {
compile 'org.springframework:spring-context:4.1.0.RELEASE'
compile project(':be')
testCompile group: 'junit', name: 'junit', version: '4.+'
testCompile 'org.springframework:spring-test:4.1.0.RELEASE'
}
}
}
notice the scope of junit is for test compile only so it won't be packaged with the final war.

to deploy the project run :

gradle build and you will get a war from the rest project, drop that in your server and you're good to go

visit the final project setup here on git hub:
https://github.com/blabadi/hello-world/tree/dev/test-gradle/master-proj


Summary :
Gradle is simple, flexible and really powerful, the only thing took time is boilerplate required for the initial structure which I did manually on purpose to understand the very basics of gradle.

Gradle yet has more interesting features, and what I only covered is mainly the java plugin and maven in gradle, the possibilities are endless, since you are using groovy scripting for the build domain (DSL) which provides easy to change conventions.


refs :
1- http://www.gradle.org/docs/current/userguide/
2- http://spring.io/guides/gs/gradle/

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.

Istio —simple fast way to start

istio archeticture (source istio.io) I would like to share with you a sample repo to start and help you continue your jou...