Topstone Software Consulting

Spring and DynamoDB

Introduction

This Topstone Software publication discusses a demonstration application that uses the Spring framework and the Amazon Web Services (AWS) database DynamoDB.

The Book Search demonstration application is a full stack web application that allows the user to build and search a database of information about books. This application demonstrates how the Spring framework (in this case, Spring Boot and Spring MVC) can be used to build an application that uses AWS DynamoDB to store and search information. The Book Search application uses Java Server Page (JSP) server side web page rendering to build the web pages and display database search results.

The source code for this application is available on GitHub under the Apache 2 software license. The source code for the Book Search application is an important resource for understanding this publication.

Background

For readers who are new to Java web development frameworks, the Topstone Software publication A Simple Spring Boot Model View Controller (MVC) Example shows how a very simple web application can be built with Spring MVC. This publication also briefly discusses the Spring Tool Suite, an Integrated Development Environment (IDE) that was also used to develop the Book Search application.

Application Architecture

Although the Book Search application is not intended to be a production quality application, it shows how a real "full stack" Spring application can be constructed.

Model, View, Controller and Service

The model-view-controller (MVC) approach to building user interface applications dates back to the late 1970s.

The model consists of the data structures that contain the application data. In this case, there is only one data structure, the BookInfo object. The BookInfo object is the data structure that is persisted into the DynamoDB database.

The controller consists of a set of classes and functions that respond to web page interactions. In the Book Search application there are three controllers:

  1. The Index controller responds to the HTTP GET request for the main application index page. The index web page contains a set of forms for book information searches.
  2. The BookSearch controller responds to the HTTP PUT operations for the book information search forms. The BookSearch controller contains has one method for each search form. The result of the search will be rendered on the server side in the view page.
  3. The AddBook controller responds to the HTTP PUT operation that posts the book information form to the controller. If there are no errors, the book form data is saved in the DynamoDB database.

To avoid having the controller logic become too complex, code that is not involved directly with managing web page interaction is placed in Service objects. In the Book Search application the BookTable service supports reading and writing to the database of book information. The BookTableService abstracts the details about the database. In this case the database is DynamoDB. The database could be changed without changing the BookTableService interface. The DynamoDB Service object supports the BookTable service.

The Spring Framework

One of the objectives in software development should be to write as little software as possible. This goal can be achieved by leveraging software frameworks like Spring and the massive software resources of the Java ecosystem.

In the past Topstone Software has used the Grails framework to develop full stack applications Web that leverated the Java ecosystem. For example, Topstone Software developed the nderground social network using the Grails framework.

Although Topstone Software still provides Grails application consulting, all new web application development uses the Spring framework. The reasons that Topstone Software has chosen the Spring framework include:

  • The Spring framework includes a plethora of resources that can be leveraged for rapid application development. These include Spring Boot and Spring MVC, which have been used to develop the Book Search application.
  • There are a variety of documentation resources available to help the developer understand Spring based applications.
  • The Spring framework is the core open source product from Pivotal so developers can count on continued support and a development team that is sensitive to the needs of the Spring community.
  • Spring is the most popular Java web application development framework and its popularity continues to grow.
Book Search project structure (from the Spring Tool Suite)

The Index Page Controller

The simplest controller in the Book Search application is the controller for the application index page (e.g., the page at the relative URL "/"). As with all Spring controllers, this controller class is marked with the controller attribute @Controller

@Controller
public class IndexController extends BookControllerBase {

    @GetMapping("/")
    public String index(Model model) {
        return "index";
    }
}

The @GetMapping annotation tells the Spring framework that the index function handles HTTP GET requests to the relative URL "/". The @GetMapping annotation is shorthand for

@RequestMapping(value="/", method=RequestMethod.GET)

The controllers in the Book Search application contain functions that return a String: the relative URL for the associated web page that will be returned to the browser in response to the HTML request.

The main web page of the Book Search application contains a list of forms that allow the user to search the book information. A screen shot is shown below:

The simplest "form" on the application index page is just a button that issues an HTTP POST to the getAllBooks function in the Book Search Controller. This function handles HTTP POST operations to the relative URL /list-all-books. The HTML for this button form is shown below:

<form action="list-all-books" method="post">
    <button type="submit" class="btn btn-primary">List all books</button>
</form>

The getAllBooks function that handles the button form HTTP POST operation in the Book Search controller is shown below:

    @RequestMapping(value = "/list-all-books", method = RequestMethod.POST)
    public String getAllBooks( RedirectAttributes redirect ) {
        List<BookInfo> bookInfoList = getBookTableService().getBooks();
        if (bookInfoList != null && bookInfoList.size() > 0) {
            redirect.addFlashAttribute(BOOK_LIST, bookInfoList);
        }
        return "redirect:/";
    }

The code in the getAllBooks() function calls the BookTableService function getBooks() which returns all of the book information in the database as a List object that contains the BookInfo entries.

The List object is returned as a flash attribute (flash attributes have a "life" of one page display - when the page is refreshed or redisplayed, the flash attribute will be cleared). Standard page attributes are limited to String values. Flash attributes may be consist of any object type (such as the List of BookInfo objects).

Adding information about a book to the database

The "Add a book" tab on the main page brings the user to the "Enter Book Information" form. The form for this page is handled by the AddBookController class. The saveBook() function in this class saves the information received from the form in the database. The code for the saveBook() function is shown below:

    @RequestMapping( value="/save-book", method = RequestMethod.POST)
    public String saveBook(@Valid BookInfo bookForm,  Errors errors, RedirectAttributes redirectAttributes) {
        if (! errors.hasErrors()) {
            if (bookForm != null) {
                getBookTableService().writeToBookTable(bookForm);
                redirectAttributes.addFlashAttribute("book_saved", "Saved the information for " + bookForm.getTitle());
            } else {
                logger.info("bookForm argument is null");
            }
        } else {
            redirectAttributes.addFlashAttribute("errors", errors);
        }
        return "redirect:addbook";
    }

As in the previous examples, the saveBook function is annotated with a @RequestMapping annotation.

The saveBook function shows how the Spring framework can save coding effort. The field data from the form on the web page is packaged by the Spring framework into the BookInfo object that is one of the arguments to the saveBook function (note that the form input field names correspond to String members in the BookInfo class).

The function declaration also contains the @Valid annotation. This tells the Spring framework that it should perform validation on the BookInfo data. If there are problems with the input data, information about the errors will be contained in the Errors object.

Spring framework validation checking uses annotations associated with the BookInfo object. These annotations are shown below.

public class BookInfo {
    @NotBlank(message="A book title is required")
    private String title;
    @NotBlank(message="An author name is required")
    private String author;
    @NotBlank(message="Please include a genre for the book")
    private String genre;
    private String publisher;
    @Digits(integer=4, fraction=0, message="Please enter a year")
    private String year;
    @Pattern(regexp="^\\d{0,8}(\\.\\d{1,4})?$", message="Please enter a price i.e., 16, 16.00, 15.95")
    private String price;

If a form field (BookInfo element) fails validation, the error can be displayed in the HTML. For example, the JSP logic for an error in the book title field is shown below:

<div class="form-group row">
      <label for="title" class="col-sm-4 col-form-label">Title</label>
       <div class="col-sm-8">
	    <input type="text" id="title" name="title" placeholder="Title">
	    <c:if test="${errors.hasFieldErrors('title')}">
	       <div class="error">
                  ${errors.getFieldError("title").getDefaultMessage()}
	  </div>
     </c:if>
   </div> <!-- col-sm-8 -->
</div>

DynamoDB

DynamoDB is an Amazon Web Services "noSQL" database. DynamoDB has a number of attractive features. These include:

  • DynamoDB can be easily scaled as the database workload increases.
  • DynamoDB has excellent performance
  • DynamoDB is a managed AWS resource and requires zero system administration.
  • DynamoDB has a flexible database schema. New, non-index, attributes can be added to a DynamoDB table without having to rebuild the database
  • The user only pays for database reads, writes and storage. There are no 24/7 server costs.
  • DynamoDB has a "free tier" that is available to all AWS customers (not just new customers)

For the Book Search application, with relatively little data and low data traffic, DynamoDB usage is free, since it falls within the free tier. In contrast, a relational database like AWS RDS/Postgres runs on a server 24-hours a day, seven days a week. This results in monthly AWS charges.

Indexing the DynamoDB Database

DynamoDB supports index query operations and table scans. Most of the BookTableService search operations use indexed DynamoDB queries. The exception is the findBookByTitle() operation which will the scan the entire book information database for a book title substring.

An example of the book table information is shown below:

DynamoDB tables must have a hash index. The hash index is a value that is unique across all table values.

For the book table, the hash key is composed of the book title. Since books by different authors may have the same title, the title hash index is combined with a "range key", consisting of the author name. For this application the book title, author pair is assumed to be a unique value. A query on title, author pair will return only one book.

In order to allow the database to be searched by author, a DynamoDB "global secondary index" is also used. A query on this index may return more than one book.

The other DynamoDB attributes (e.g., publisher, date) are not used to return values in a DynamoDB query. These values can be searched in a DynamoDB scan operation that will read all of the items in the database. Scan operations can be expensive for large databases, since the user is charged for the amount of data read.

DynamoDB Code

The DynamoDB book table is created by the CreateBookTable class. The code that builds the table constructs the hash and range key index from the title and author attributes. A global secondary index is created from the author attribute.

The code that constructs the DynamoDB queries and the book title scan is contained in the BookTableService class.

A note on testing

Software developers are encouraged to follow the practice of "test driven development" where unit test code is written for all software components. In developing software that makes use of DynamoDB, I have found that test driven development is a necessity. Amazon's DynamoDB documentation could be better and it is easy to write DynamoDB code that does not function properly. Unit tests are often the only way to know in advance that the DynamoDB code works.

The database functions in the BookTableSearch object have versions that take a table name argument. This allows the unit test code to use DynamoDB tables that are created and deleted by the test code.

Book Search Demonstration Application on GitHub

Topstone software has published the Java Book Search demonstration application on GitHub (see Spring and DynamoDB Book Search application). This software is available under the Apache 2 open source license.

The Java and HTML code was on this page has been "pretty printed" for HTML display using http://hilite.me/

References

  • Spring in Action, Fifth Edition by Craig Walls, Manning Publications, 2018 (available through the Manning Early Access Program)
Ian Kaplan, Topstone Software Consulting, June 2018