In this article we'll develop a back-end solution that can be used with any front-end (Web, mobiles...). In the next article we'll extend on this example and work on the front-end using AngularJS, JQuery and Bootstrap CSS.

The goal of the application is to be able to administer books. In particular, we'll develop the back-end solution for that application that should be able to:

Add new book

Update existing book

Delete an existing book

List all books

Display details of the selected book

Delete all books



Disclaimer: This article does not provide detailed tutorial nor instructions how to use frameworks listed below. It is a step by step guide how to develop an sample application with different frameworks and technologies combined. If the same steps as described below are followed, the end result will be a fully developed back-end application

All the data will be stored in transactional database and exposed to front-end through JSON REST API.

All the code will be done in the TDD fashion. If this is your first time working with TDD, tests that we will be writing might look like a waste or overkill. I urge you to give TDD a try. After a bit of practice, most people find TDD indispensable as a way to design their code as well as a safety net. On a unit level, we have all our code always tested and working as we expect. All our test classes will follow the same naming convention. They will be located in src/test/java directory in the same package as the implementation code. Class name will be sufixed with Test. For example, tests for the Book class should be in the BookTest class. Try to follow the tests. Pick the first one for the specified class, run it and observe that it fails, implement just enough to make the test pass, run all tests and observe that they all pass, refactor the code, repeat.

Make sure that Java and Gradle are installed before starting this tutorial. We will be using IntelliJ IDEA as our favorite IDE.

Completed code can be found in the TechnologyConversationsBooks GIT repository.

Following frameworks and libraries are selected for the back-end of this application:

Gradle: Gradle can automate the building, testing, publishing, deployment and more of software packages or other types of projects such as generated static websites, generated documentation or indeed anything else. It combines the power and flexibility of Ant with the dependency management and conventions of Maven into a more effective way to build.

JUnit: Simple framework to write repeatable tests.

Hamcrest: Provides a library of matcher objects.

JSONAssert: Allows easier creation of JSON tests.

HSQLDB: HSQLDB offers a small, fast multithreaded and transactional database engine with in-memory and disk-based tables and supports embedded and server modes.

Hibernate: Hibernate is idiomatic persistence for Java and relational databases.

Jersey: Jersey helps with development of RESTful Web Services in Java.

Grizzly: Grizzly helps developers to build scalable and robust servers using NIO as well as offering extended framework components: Web Framework (HTTP/S), WebSocket, Comet, and more.

Overview

We will start by creating a new Gradle Java project. Once we're all set, we'll create data persistence to the database. From there on we'll move into JSON REST API. Finally, since this is a public API that should be used by others, we'll package the application.

Project setup

Create a new project in IDEA by selecting Gradle as project type. Put following dependencies to the build.gradle file:

[Build.gradle]

... dependencies { testCompile group: 'junit', name: 'junit', version: '4.11' testCompile group: 'org.hamcrest', name: 'hamcrest-all', version: '1.3' testCompile group: 'org.skyscreamer', name: 'jsonassert', version: '1.2.3' compile group: 'org.hsqldb', name: 'hsqldb', version: '2.3.2' compile group: 'org.hibernate', name: 'hibernate-core', version: '4.3.4.Final' compile group: 'org.hibernate', name: 'hibernate-annotations', version: '3.5.6-Final' compile group: 'org.slf4j', name: 'slf4j-simple', version: '1.5.8' compile group: 'javassist', name: 'javassist', version: '3.12.1.GA' compile group: 'org.glassfish.jersey.core', name: 'jersey-client', version: '2.7' compile group: 'org.glassfish.jersey.media', name: 'jersey-media-json-jackson', version: '2.7' compile group: 'org.glassfish.jersey.containers', name: 'jersey-container-grizzly2-http', version: '2.7' compile group: 'org.jasypt', name: 'jasypt', version: '1.9.2' } ...

Whole file can be found in the build.gradle GIT repository.

Refresh dependencies by clicking the "Refresh All Gradle Projects" icon inside the "Gradle" tab. Once project is refreshed you'll see all the dependencies in the project External Libraries.

Create directories src/main/java and src/test/java. They will hold implementation code and tests. Both of those directories should have the package com.technologyconversations.books.

Bean

Our API should be able to provide following book information:

ID: Unique identifier

Image: The URL to the item cover-art

Title: The title of the item

Author: The author of the item

Price: The price of the item

Link: The link to the item details

Let's create the Book class that will act as Java bean object holding all this information. This bean will implement getters and setters for each data type. Following is the test for ID getter/setter followed with the implementation.

[BookTest.java]

... @Test public void idShouldHaveSetterAndGetter() { int expected = 123; Book book = new Book(); book.setId(expected); assertThat(book.getId(), is(equalTo(expected))); } ...

[Book.java]

... private int id; public int getId() { return id; } public void setId(int id) { this.id = id; } ...

Similar code should be repeated for the rest of getters and setters.

Further on, we should override equals and hashCode methods so that we can compare two instances of our Book bean (this will come in handy with tests we'll write later on).

[BookTest.java]

... @Test public void equalsShouldFailIfIdIsNotTheSame() { Book actual = new Book(111); actual.setImage("image"); actual.setTitle("title"); actual.setAuthor("author"); actual.setPrice(12.34); Book expected = new Book(222); expected.setImage("image"); expected.setTitle("title"); expected.setAuthor("author"); expected.setPrice(12.34); assertThat(actual, is(not(equalTo(expected)))); } ...

[Book.java]

... @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Book book = (Book) o; if (id != book.id) return false; return true; } ...

Similar code should be repeated for the rest of fields that should be included in equals method.

Finally, we should add persistence annotations to the Book class and all fields.

[Book.java]

@Entity @Table(name = "Book") @XmlRootElement public class Book { ... @Id @Column(name = "id") private int id; ...

Final version of tests can be found in the BookTest.java and implementation in the Book.java.

Persistance

We will use Hibernate to store and retrieve data from HSQLDB. For the sake of fast execution of tests we'll use in-memory database. Production version of the application should change the mode to file or even switch to a different DB.

First step is to create a new directory src/main/resources and place the hibernate.cfg.xml inside. This is the Hibernate configuration file. Please take a look at the final version of hibernate.cfg.xml. The file is pretty straight-forward and easy to understand. The key entry is the mapping that should point to the Book class created earlier. Another setup file is the HibernateUtil.java. Tests can be seen from HibernateUtilTest.java. With both in place, we can start working on our Book DAO (Data Access Object) that will provide a bridge between the Book class and the HSQL database.

We'll implement following methods:

deleteAllBooks: Removes all books from the database.

getAllBooks: Gets all books from the database.

getBook: Retrieves the specified book.

saveOrUpdateBook: If the book already exists, it gets updated. Otherwise, new book is inserted to the database.

deleteBook: Deletes the specified book.

We'll start by creating @Before and @After methods that will be executed before and after each test. Their purpose is to setup initial set of data (one book) and delete all data after each test. Both will be required by tests further on.

[BookDaoTest.java]

... @Before public void beforeBookDaoTest() { bookDao.saveOrUpdateBook(book); } @After public void afterBookDaoTest() { bookDao.deleteAllBooks(); } ...

We'll have to implement those two methods. Later on we'll refactor them and write more extensive tests. At the moment it is important that all books can be deleted and that we can insert new books.

[BookDao.java]

... public void deleteAllBooks() { Session session = HibernateUtil.getSessionFactory().openSession(); session.createQuery("delete from Book").executeUpdate(); session.close(); } public void saveOrUpdateBook(Book book) { Session session = HibernateUtil.getSessionFactory().openSession(); session.saveOrUpdate(book); session.flush(); session.close(); } ...

By doing this implementation without tests we are breaking the TDD rule to write tests first. However, alternative would be to use JDBC directly without Hibernate for those first tests. We will get back to those methods later on. Now that we have our setup and cleaning in place, we can start working on our tests and implementation. We'll start with tests for the getAllBooks method.

[BookDaoTest.java]

... @Test public void getAllBooksShouldReturnAllBooks() { assertThat(bookDao.getAllBooks().size(), is(equalTo(1))); } ...

[BookDao.java]

... public List<Book> getAllBooks() { Session session = HibernateUtil.getSessionFactory().openSession(); @SuppressWarnings("unchecked") List<Book> books = session.createQuery("from Book").list(); session.close(); return books; } ...

Next we'll want to save bandwidth by retrieving only the necessary columns (ID and title). Also, in order to provide pagination capabilities we should be able to return books starting from specified offset and to limit the number of results. Tests that verify those requirements could be:

[BookDaoTest.java]

... @Test public void getAllBooksShouldReturnsBooksWithId() { Book actual = bookDao.getAllBooks().get(0); assertThat(actual.getId(), is(equalTo(id))); } @Test public void getAllBooksShouldReturnsBooksWithTitle() { Book actual = bookDao.getAllBooks().get(0); assertThat(actual.getTitle(), is(equalTo(title))); } @Test public void getAllBooksShouldReturnsBooksWithoutAuthor() { Book actual = bookDao.getAllBooks().get(0); assertThat(actual.getAuthor(), is(nullValue())); } @Test public void getAllBooksShouldReturnBooksStartingFromSpecifiedFirstResult() { int size = 10; int firstResult = 5; int expected = size - firstResult; bookDao.deleteAllBooks(); insertBooks(size); assertThat(bookDao.getAllBooks(firstResult, 0).size(), is(equalTo(expected))); } @Test public void getAllBooksShouldReturnBooksWithSpecifiedMaxResult() { int size = 20; int maxResult = 10; bookDao.deleteAllBooks(); insertBooks(size); assertThat(bookDao.getAllBooks(0, maxResult).size(), is(equalTo(maxResult))); } @Test public void getAllBooksShouldReturnBooksWithSpecifiedMaxResultStartingFromSpecifiedFirstResult() { int size = 20; int firstResult = 5; int maxResult = 10; bookDao.deleteAllBooks(); insertBooks(size); List<Book> actual = bookDao.getAllBooks(firstResult, maxResult); assertThat(actual.size(), is(equalTo(maxResult))); assertThat(actual.get(0).getId(), is(equalTo(firstResult + 1))); } ...

If we do the implementation of each test one by one, final solution might look something like:

[BookDao.java]

... public List<Book> getAllBooks() { return getAllBooks(0, 0); } public List<Book> getAllBooks(int firstResult, int maxResult) { List<Book> books = new ArrayList<>(); Session session = HibernateUtil.getSessionFactory().openSession(); Query query = session.createQuery("select id, title from Book"); query.setFirstResult(firstResult); query.setMaxResults(maxResult); @SuppressWarnings("unchecked") List allUsers = query.list(); for (Iterator it = allUsers.iterator(); it.hasNext(); ) { Object[] bookObject = (Object[]) it.next(); Book book = new Book((Integer) bookObject[0]); book.setTitle((String) bookObject[1]); books.add(book); } session.close(); return books; } ...

Please consult BookDaoTest.java for the complete set of tests and BookDao.java for the final implementation.

Now that we have the persistence done, we can move to the JSON REST API.

Server

We will be using Grizzly as the server serving JSON REST requests. For that purpose we'll create Server.java. It will be used by our tests to start and stop the server. Also, it will contain the main method that can be used to start the server manually.

[Server.java]

... public HttpServer startServer() { final ResourceConfig rc = new ResourceConfig().packages("com.technologyconversations.books"); HttpServer server = GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_API_URI), rc); return server; } ...

Complete source code can be found in the Server.java.

JSON REST API

Our back-end solution will expose API through JSON REST services. This way we are removing any coupling between back-end and front-end. Later on we can create Web site, Android, iOS or desktop applications that consume those services.

As with the BookDaoTest, we'll start with setup and cleanup methods. This time we'll introduce 2 new annotations: @BeforeClass and @AfterClass. Unlike @Before and @After that are executed before and after each test, @BeforeClass and @AfterClass annotations are executed before and after the class itself. In other words, @BeforeClass is executed before the first test and @AfterClass is executed after the last test in that class. In this case we want to start the server before the first test and stop it at the end.

[BookApiTest.java]

@BeforeClass public static void beforeBookApiTestClass() { server = new Server().startServer(); } @AfterClass public static void afterUserResourceTestClass() { server.shutdown(); }

Let's start with the request that should return the list of all books.The URL should be /api/v1/items. Response should be JSON list of books. Functionalities should reflect those stated in the DAO with offset and maximum number of books to retrieve as optional parameters. Response should be using format [{id: BOOK_ID, link: LINK_TO_DETAILS, title: BOOK_TITLE}, {...}].

[BookApiTest.java]

... @Test public void getV1ItemsShouldReturnTypeApplicationJson() { assertThat(itemsTarget.request().get().getMediaType().toString(), is("application/json")); } @Test public void getV1ItemsShouldReturnListOfBooks() throws Exception { int size = 3; insertBooks(size); String json = itemsTarget.request().get(String.class); List<Book> actual = objectMapper.readValue(json, objectMapper.getTypeFactory().constructCollectionType(List.class, Book.class)); assertThat(actual.size(), is(size)); } @Test public void getV1ItemsShouldStartFromSpecifiedFirstResult() throws Exception { insertBooks(10); String json = requestItems(5, 0); List<Book> actual = objectMapper.readValue(json, objectMapper.getTypeFactory().constructCollectionType(List.class, Book.class)); assertThat(actual.size(), is(5)); } @Test public void getV1ItemsShouldReturnSpecifiedMaximumNumberOfBooks() throws Exception { insertBooks(10); String json = requestItems(0, 7); List<Book> actual = objectMapper.readValue(json, objectMapper.getTypeFactory().constructCollectionType(List.class, Book.class)); assertThat(actual.size(), is(7)); } @Test public void getV1ItemsShouldReturnSpecifiedMaximumNumberOfBooksAndSkipToTheSpecifiedFirstResult() throws Exception { insertBooks(20); String json = requestItems(5, 7); List<Book> actual = objectMapper.readValue(json, objectMapper.getTypeFactory().constructCollectionType(List.class, Book.class)); assertThat(actual.size(), is(7)); assertThat(actual.get(0).getId(), is(equalTo(6))); } @Test public void getV1ItemsShouldReturnTheCorrectJson() throws Exception { insertBooks(1); String json = itemsTarget.request().get(String.class); JSONAssert.assertEquals("[{id: 1}]", json, false); JSONAssert.assertEquals("[{link: "/api/v1/items/1"}]", json, false); JSONAssert.assertEquals("[{title: "" + title + ""}]", json, false); } ...

The implementation after all the tests from above looks like:

[BookApi.java]

... @GET @JSONP(queryParam = "callback") public String getAllBooks(@QueryParam("offset") int offset, @QueryParam("count") int count, @QueryParam("callback") String callback) throws Exception { ObjectMapper mapper = new ObjectMapper(); mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_DEFAULT); List<Book> books = BookDao.getInstance().getAllBooks(offset, count); for (Book book : books) { book.setLink(ITEMS_URL + "/" + book.getId()); } return mapper.writeValueAsString(books); } ...

For the rest of requests, please consult BookApiTest.java and BookApi.java.

Package

Now that we have the code and the tests for our back-end application done, it is time to package our application. Gradle makes this task very simple. All we have to do is add war and application plugins.

[Build.gradle]

... apply plugin: 'war' apply plugin: 'application' ...

Full version can be found in build.gradle.

To create zipped distribution with the compiled JAR, dependencies and startup scripts:

$ gradle distZip

The result is a ZIP file with startup scripts inside the bin directory. When run, it will run the Grizzly server and start serving JSON requests.

To create WAR file suitable for deployment to any Web server:

$ gradle war

What Next?

Right now we have our back-end application with permanent storage and capable of serving JSON requests. Now we're ready to start working on the front-end. Next article Application development: front-end solution with JavaScript continues where we stopped. We'll work with HTML5, AngularJS, JQuery, Bootstrap CSS and plain old JavaScript.

Test-Driven Java Development

Test-Driven Java Development book wrote by Alex Garcia and me has been published by Packt Publishing. It was a long, demanding, but very rewarding journey that resulted in a very comprehensive hands-on material for all Java developers interested in learning or improving their TDD skills.

If you liked this article I am sure that you'll find this book very useful. It contains extensive tutorials, guidelines and exercises for all Java developers eager to learn how to successfully apply TDD practices.

You can download a sample or purchase your own copy directly from Packt or Amazon.