© Balaji Varanasi and Maxim Bartkov 2022
B. Varanasi, M. BartkovSpring RESThttps://doi.org/10.1007/978-1-4842-7477-4_4

4. Beginning QuickPoll Application

Balaji Varanasi1   and Maxim Bartkov2
(1)
Salt Lake City, UT, USA
(2)
Kharkov, Ukraine
 
In this chapter we will discuss the following:
  • Analyzing the requirements for QuickPoll

  • Identifying QuickPoll resources

  • Designing representations

  • Implementing QuickPoll

Up to this point, we have looked at the fundamentals of REST and reviewed our technology choice of implementation—Spring MVC. Now it’s time to develop a more complex application. In this chapter, we will introduce you to the beginnings of an application that we will be working on throughout this book. We will call it QuickPoll. We will go through the process of analyzing the requirements, identifying resources, designing their representation, and, finally, providing an implementation to a subset of features. In the upcoming chapters, we will continue our design and implementation by adding new features, documentation, security, and versioning.

Introducing QuickPoll

Polls have become a popular option for soliciting views and opinions from the community on many websites these days. There are a couple of variations between online polls, but a poll typically has a question and a list of answers, as shown in Figure 4-1.
../images/332520_2_En_4_Chapter/332520_2_En_4_Fig1_HTML.jpg
Figure 4-1

Web poll example

Participants vote and communicate their opinion by selecting one or more responses. Many polls also allow participants to view the poll results, as shown in Figure 4-2.
../images/332520_2_En_4_Chapter/332520_2_En_4_Fig2_HTML.jpg
Figure 4-2

Web poll results

Imagine being part of QuickPoll Inc., a budding Software as a Service (or SaaS) provider that allows users to create, manipulate, and vote on polls. We plan to launch our services to a small audience, but we intend to become a global enterprise. In addition to the Web, QuickPoll would also like to target native iOS and Android platforms. To achieve these lofty goals, we have chosen to implement our application using REST principles and web technologies.

We begin the development process by analyzing and understanding requirements. Our QuickPoll application has the following requirements:
  • Users interact with QuickPoll services to create new polls.

  • Each poll contains a set of options that are provided during poll creation.

  • Options inside a poll can be updated at a later point.

  • To keep things simple, QuickPoll restricts voting on a single option.

  • Participants can cast any number of votes.

  • Results of a poll can be viewed by anyone.

We have started with a simple set of requirements for QuickPoll. As with any other application, these requirements will evolve and change. We will address those changes in the upcoming chapters.

Designing QuickPoll

As discussed in Chapter 1, designing a RESTful application typically involves the following steps:
  1. 1.

    Resource identification

     
  2. 2.

    Resource representation

     
  3. 3.

    Endpoint identification

     
  4. 4.

    Verb/action identification

     

Resource Identification

We begin the resource identification process by analyzing requirements and extracting nouns. At a high level, the QuickPoll application has users that create and interact with polls. From the previous statement, you can identify User and Poll as nouns and classify them as resources. Similarly, users can vote on polls and view the voting results, making Vote another resource. This resource modeling process is similar to database modeling in that it is used to identify entities or object-oriented design that identifies domain objects.

It is important to remember that all nouns identified need not be exposed as resources. For example, a poll contains several options, making Option another candidate for resource. Making Poll Option a resource would require a client to make two GET requests. The first request will obtain a Poll representation; the second request will obtain an associated Options representation. However, this approach makes the API chatty and might overload servers. An alternative approach is to include the options inside a Poll representation, thereby hiding Option as a resource. This would make Poll a coarse-grained resource, but clients would get poll-related data in one call. Additionally, the second approach can enforce business rules such as requiring at least two options for a poll to be created.

This noun approach allows us to identify collection resources. Now, consider the scenario in which you want to retrieve all of the votes for a given poll. To handle this, you need a “votes” collection resource. You can perform a GET request and obtain the entire collection. Similarly, we need a “polls” collection resource, which allows us to query groups of polls and create new ones.

Finally , we need to address the scenario in which we count all of the votes for a poll and return the computed results to the client. This involves looping through all the votes for a poll, grouping those votes based on options, and then counting them. Such processing operations are typically implemented using a “controller” resource, which we introduced in Chapter 1. In this case, we model a ComputeResult resource, which performs this counting operation. Table 4-1 shows the identified resources and their collection resource counterparts.
Table 4-1

Resources for QuickPoll Application

Resource

Description

User

Singleton User Resource

Users

Collection User Resource

Poll

Singleton Poll Resource

Polls

Collection Poll Resource

Vote

Singleton Vote Resource

Votes

Collection Vote Resource

ComputeResult

Count Processing Resource

Resource Representation

The next step in the REST API design process is defining resource representations and representation formats. REST APIs typically support multiple formats such as HTML, JSON, and XML. The choice of the format largely depends on the API audience. For example, a REST service that is internal to the company might only support JSON format, whereas a public REST API might speak XML and JSON formats. In this chapter and in the rest of the book, JSON will be the preferred format for our operations.

JSON Format

The JavaScript Object Notation, or JSON, is a lightweight format for exchanging information. Information in JSON is organized around two structures: objects and arrays.

A JSON object is a collection of name/value pairs. Each name/value pair consists of a field name in double quotes followed by a colon (:), followed by a field value. JSON supports several types of values such as Boolean (true or false), number (int or float), String, null, arrays, and object. Examples of name/value pairs include
"country" : "US"
"age" : 31
 "isRequired" : true
"email" : null
JSON objects are surrounded by curly braces ({}), and each name/value pair is separated using a comma (,). Here is an example of a person JSON object:
{ "firstName": "John", "lastName": "Doe", "age" : 26, "active" : true }
The other JSON structure, an array, is an ordered collection of values. Each array is surrounded by square brackets ([ ]), with values separated by a comma. Here is an example of an array of locations:
[ "Salt Lake City", "New York", "Las Vegas", "Dallas"]
JSON arrays can also contain objects as their values:
[
         { "firstName": "Jojn", "lastName": "Doe", "age": 26, "active": true },
         { "firstName": "Jane", "lastName": "Doe", "age": 22, "active": true },
         { "firstName": "Jonnie", "lastName": "Doe", "age": 30, "active": false }
]
Resources are made up of set of attributes that can be identified using a process similar to object-oriented design. A Poll resource, for example, has a question attribute, containing a Poll question, and an id attribute, which uniquely identifies the Poll. It also contains a set of options; each option is made up of a value and an id. Listing 4-1 shows a representation of a Poll with sample data.
{
        "id": 2,
        "question": "How will win SuperBowl this year?",
"options": [{"id": 45, "value": "New England Patriots"}, {"id": 49,
"value": "Seattle Seahawks"}, {"id": 51, "value": "Green Bay Packers"},
{"id": 54, "value": "Denver Broncos"}]
}
Listing 4-1

Poll Representation

Note

We are intentionally excluding a user from Poll representation in this chapter. In Chapter 8, we will discuss user representation along with its associations to Poll and Vote resources.

The representation of a Poll collection resource contains a collection of individual polls. Listing 4-2 gives the representation of a Polls collection resource with dummy data.
[
              {
           "id": 5,
           "question": "q1",
           "options": [
{"id": 6, "value": "X"}, {"id": 9, "value": "Y"},
{"id": 10, "value": "Z"}]
         },
         {
        "id": 2,
        "question": "q10",
        "options": [{"id": 15, "value": "Yes"}, {"id": 16, "value": "No"}]
        }
         .......
]
Listing 4-2

List of Polls Representation

The Vote resource contains the option for which the vote was cast and a unique identifier. Listing 4-3 shows the Vote resource representation with dummy data.
{
        "id": 245,
        "option": {"id": 45, "value": "New England Patriots"}
}
Listing 4-3

Vote Representation

Listing 4-4 gives the Votes collection resource representation with dummy data.
[
            {
            "id": 245,
            "option": {"id": 5, "value": "X"}
         },
         {
            "id": 110,
             "option": {"id": 7, "value": "Y"}
         },
        ............
Listing 4-4

List of Votes Representation

The ComputeResult resource representation should include the total number of votes and Poll options along with the vote count associated with each option. Listing 4-5 shows this representation with sample data. We use the totalVotes attribute to hold the cast votes and the results attribute to hold the option id and the associated votes.
{
     totalVotes: 100,
     "results" : [
                { "id" : 1, "count" : 10 },
                { "id" : 2, "count" : 8 },
                { "id" : 3, "count" : 6 },
                { "id" : 4, "count" : 4 }
              ]
}
Listing 4-5

ComputeResult Representation

Now that we have defined our resource representation, we will move on to identifying endpoints for those resources.

Endpoint Identification

REST resources are identified using URI endpoints. Well-designed REST APIs should have endpoints that are understandable, intuitive, and easy to use. Remember that we build REST APIs for our consumers to use. Hence, the names and the hierarchy that we choose for our endpoints should be unambiguous to consumers.

We design the endpoints for our service using best practices and conventions widely used in the industry. The first convention is to use a base URI for our REST service. The base URI provides an entry point for accessing the REST API. Public REST API providers typically use a subdomain such as http://api.domain.com or http://dev.domain.com as their base URI. Popular examples include GitHub’s https://api.github.com and Twitter’s https://api.twitter.com. By creating a separate subdomain, you prevent any possible name collisions with webpages. It also allows you to enforce security policies that are different from the regular website. To keep things simple, we will be using http://localhost:8080 as our base URI in this book.

The second convention is to name resource endpoints using plural nouns. In our QuickPoll application, this would result in an endpoint http://localhost:8080/polls for accessing the Poll collection resource. Individual Poll resources will be accessed using a URI such as http://localhost:8080/polls/1234 and http://localhost:8080/polls/3456. We can generalize access to individual Poll resources using the URI template http://localhost:8080/polls/{pollId}. Similarly, the endpoints http://localhost:8080/users and http://localhost:8080/users/{userId} are used for accessing collection and individual User resources.

The third convention advises using a URI hierarchy to represent resources that are related to each other. In our QuickPoll application, each Vote resource is related to a Poll resource. Because we typically cast votes for a Poll, a hierarchical endpoint http://localhost:8080/polls/{pollId}/votes is recommended for obtaining or manipulating all the votes associated with a given Poll. In the same way, the endpoint http://localhost:8080/polls/{pollId}/votes/{voteId} would return an individual vote that was cast for the Poll.

Finally, the endpoint http://localhost:8080/computeresult can be used to access the ComputeResult resource. For this resource to function properly and count the votes, a poll id is required. Because the ComputeResult works with Vote, Poll, and Option resources, we can’t use the third approach for designing a URI that is hierarchal in nature. For use cases like these that require data to perform computation, the fourth convention recommends using a query parameter. For example, a client can invoke the endpoint http://localhost:8080/computeresult?pollId=1234 to count all of the votes for the Poll with id 1234. Query parameters are an excellent vehicle for providing additional information to a resource.

In this section, we have identified the endpoints for the resources in our QuickPoll application. The next step is to identify the actions that are allowed on these resources, along with the expected responses.

Action Identification

HTTP verbs allow clients to interact and access resources using their endpoints. In our QuickPoll application, the clients must be able to perform one or more CRUD operations on resources such as Poll and Vote. Analyzing the use cases from the “Introducing QuickPoll” section, Table 4-2 shows the operations allowed on Poll/Polls collection resources along with the success and error responses. Notice that on the Poll collection resource, we allow GET and POST operations but deny PUT and Delete operations. A POST on the collection resource allows the client to create new polls. Similarly, we allow GET, PUT, and Delete operations on a given Poll resource but deny POST operation. The service returns a 404 status code for any GET, PUT, and DELETE operation on a Poll resource that doesn’t exist. Similarly, any server errors would result in a status code of 500 sent to the client.
Table 4-2

Allowed Operations on a Poll Resource

HTTP

method

Resource

endpoint

Input

Success response

Error response

Description

GET

/polls

Body: empty

Status: 200

Body: poll list

Status: 500

Retrieves all available polls

POST

/polls

Body: new poll data

Status: 201

Body: newly created poll id

Status: 500

Creates a new poll

PUT

/polls

N/A

N/A

Status: 400

Forbidden action

Delete

/polls

N/A

N/A

Status: 400

Forbidden action

GET

/polls/{pollId}

Body: empty

Status: 200

Body: poll data

Status: 404 or 500

Retrieves an existing poll

POST

/polls/{pollId}

N/A

N/A

Status: 400

Forbidden

PUT

/polls/{pollId}

Body: poll data with updates

Status: 200

Body: empty

Status: 404 or 500

Updates an existing poll

Delete

/polls/{pollId}

Body: empty

Status: 200

Status: 404 or 500

Deletes an existing poll

In the same fashion, Table 4-3 shows the operations allowed on Vote/Votes collection resources.
Table 4-3

Allowed Operations on Vote Resource

HTTP

method

Resource

endpoint

Input

Success response

Error response

Description

GET

/polls/{pollId}/votes

Body: empty

Status: 200

Body: votes list

Status: 500

Retrieves all available votes for a given poll

POST

/polls/{pollId}/votes

Body: new vote

Status: 201

Body: newly created vote id

Status: 500

Creates a new vote

PUT

/polls/{pollId}/votes

N/A

N/A

Status: 400

Forbidden action

Delete

/polls/{pollId}/votes

N/A

N/A

Status: 400

Forbidden action

GET

/polls/{pollId}/votes/{voteId}

Body: empty

Status: 200

Body: vote data

Status: 404 or 500

Retrieves an existing vote

POST

/polls/{pollId}/votes/{voteId}

N/A

N/A

Status: 400

Forbidden

PUT

/polls/{pollId}/votes/{voteId}

N/A

N/A

Status: 400

Forbidden as a casted vote can’t be updated according to our requirements

Delete

/polls/{pollId}/votes/{voteId}

N/A

N/A

Status: 400

Forbidden as a casted vote can’t be deleted according to our requirements

Finally, Table 4-4 shows the operations allowed on the ComputeResult resource.
Table 4-4

Allowed Operations on ComputeResult Resource

HTTP

method

Resource

endpoint

Input

Success response

Error response

Description

GET

/computeresult

Body: empty

Param: pollId

Status: 200

Body: vote count

Status: 500

Returns the vote count for the given poll

This concludes the design for the QuickPoll REST service. Before we start our implementation, we will review QuickPoll’s high-level architecture.

QuickPoll Architecture

The QuickPoll application will be made of a web or REST API layer and a repository layer with a domain layer (layer between Web API and repository) crosscutting those two, as depicted in Figure 4-3. A layered approach provides a clear separation of concerns, making applications easy to build and maintain. Each layer interacts with the following layer using a well-defined contract. As long as the contract is maintained, it is possible to swap out underlying implementations without any impact on the overall system.
../images/332520_2_En_4_Chapter/332520_2_En_4_Fig3_HTML.png
Figure 4-3

QuickPoll architecture

The Web API layer is responsible for receiving client requests, validating user input, interacting with a service or a repository layer, and generating a response. Using HTTP protocol, resource representations are exchanged between clients and the Web API layer. This layer contains controllers/handlers and is typically very lightweight as it delegates most of the work to layers beneath it.

The domain layer is considered to be the “heart” of an application. Domain objects in this layer contain business rules and business data. These objects are modeled after the nouns in the system. For example, a Poll object in our QuickPoll application would be considered a domain object.

The repository or data access layer is responsible for interacting with a datastore such as a database or LDAP or a legacy system. It typically provides CRUD operations for storing and retrieving objects from/to a datastore.

Note

Observant readers will notice that the QuickPoll architecture is missing a service layer. Service layer typically sits between the API/presentation layer and repository layer. It contains coarse-grained API with methods that fulfill one or more use cases. It is also responsible for managing transactions and other crosscutting concerns such as security.

Because we are not dealing with any complex use cases for QuickPoll application in this book, we will not be introducing service layers into our architecture.

Implementing QuickPoll

We begin QuickPoll implementation by generating a Spring Boot project using STS. Follow the steps discussed in the “Generating a Project Using STS” section of Chapter 3, and create a project named quick-poll. Figures 4-4 and 4-5 give the configuration information used during project generation. Notice that we have selected the “JPA” and “Web” options.
../images/332520_2_En_4_Chapter/332520_2_En_4_Fig4_HTML.png
Figure 4-4

QuickPoll spring starter project

../images/332520_2_En_4_Chapter/332520_2_En_4_Fig5_HTML.png
Figure 4-5

QuickPoll Spring starter project dependencies

Alternatively, you can import the QuickPoll project into your STS IDE from the downloaded source code for this book. The downloaded source code contains a number of folders named ChapterX, in which X represents the corresponding chapter number. Each ChapterX folder further contains two subfolders: a starter folder and a final folder. The starter folder houses a QuickPoll project that you can use to follow along with the solution described in this chapter.

Even though each chapter builds on the previous chapter’s work, the starter project allows you to skip around in the book. For example, if you are interested in learning about security, you can simply load the QuickPoll application under the Chapter8starter folder and follow the solution as described in Chapter 8.

As the name suggests, the final folder contains the completed solution/code for each chapter. To minimize code in the chapter text, I have omitted getters/setters methods, imports, and package declarations in some of the code listings. Please refer to the QuickPoll code under the final folder for complete code listings.

By default, Spring Boot applications run on port 8080. So, if you intend to run two versions of QuickPoll, simply use the command line option -Dserver.port:
mvn spring-boot:run -Dserver.port=8181
Note

Java Persistence API, or JPA, is a standard-based API for accessing, storing, and managing data between Java objects and relational database. Like JDBC, JPA is purely a specification, and many commercial and open-source products such as Hibernate and TopLink provide JPA implementations. A formal overview of JPA is beyond the scope of this book. Please refer to Pro JPA 2 (http://www.apress.com/9781430219569/) to learn more.

Domain Implementation

The domain objects typically act as a backbone for any application. So, the next step in our implementation process is to create domain objects. Figure 4-5 shows a UML Class diagram representing the three domain objects in our QuickPoll application and their relationships.
../images/332520_2_En_4_Chapter/332520_2_En_4_Fig6_HTML.png
Figure 4-6

QuickPoll domain objects

Inside the quick-poll project, create a com.apress.domain subpackage under the /src/main/java folder, and create Java classes corresponding to the domain objects that we identified. Listing 4-6 gives the implementation of the Option class. As you can see, the Option class has two fields: id, to hold the identity; and value, corresponding to the option value. Additionally, you will see that we have annotated this class with JPA annotations such as @Entity and @Id. This allows instances of the Option class to be easily persisted and retrieved using JPA technology.
package com.apress.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Option {
    @Id
    @GeneratedValue
    @Column(name="OPTION_ID")
    private Long id;
    @Column(name="OPTION_VALUE")
    private String value;
    // Getters and Setters omitted for brevity
}
Listing 4-6

Option Class

Next, we create a Poll class , as shown in Listing 4-7, along with corresponding JPA annotations. The Poll class has a question field to store the poll question. The @OneToMany annotation, as the name suggests, indicates that a Poll instance can contain zero or more Option instances. The CascadeType.All indicates that any database operations such as persist, remove, or merge on a Poll instance needs to be propagated to all related Option instances. For example, when a Poll instance gets deleted, all of the related Option instances will be deleted from the database.
package com.apress.domain;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy ;
@Entity
public class Poll {
        @Id
        @GeneratedValue
        @Column(name="POLL_ID")
        private Long id;
        @Column(name="QUESTION")
        private String question;
        @OneToMany(cascade=CascadeType.ALL)
        @JoinColumn(name="POLL_ID")
        @OrderBy
        private Set<Option> options;
        // Getters and Setters omitted for brevity
}
Listing 4-7

Poll Class

Finally, we create the Vote class , as shown in Listing 4-8. The @ManyToOne annotation indicates that an Option instance can have zero or more Vote instances associated with it.
package com.apress.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
@Entity
public class Vote {
        @Id
        @GeneratedValue
        @Column(name="VOTE_ID")
        private Long id;
        @ManyToOne
        @JoinColumn(name="OPTION_ID")
        private Option option;
        // Getters and Setters omitted for brevity
}
Listing 4-8

Vote Class

Repository Implementation

Repositories, or data access objects (DAO), provide an abstraction for interacting with datastores. Repositories traditionally include an interface that provides a set of finder methods such as findById, findAll for retrieving data, and methods to persist and delete data. Repositories also include a class that implements this interface using datastore-specific technologies. For example, a repository dealing with a database uses technology such as JDBC or JPA, and a repository dealing with LDAP would use JNDI. It is also customary to have one repository per domain object.

Although this has been a popular approach, there is a lot of boilerplate code that gets duplicated in each repository implementation. Developers attempt to abstract common functionality into a generic interface and generic implementation (http://www.ibm.com/developerworks/library/j-genericdao/). However, they are still required to create a pair of repository interfaces and classes for each domain object. Often these interfaces and classes are empty and just result in more maintenance.

The Spring Data project aims at addressing this problem by completely eliminating the need to write any repository implementations. With Spring Data, all you need is a repository interface to automatically generate its implementation at runtime. The only requirement is that application repository interfaces should extend one of the many available Spring Data marker interfaces. Because we will be persisting our QuickPoll domain objects into a relational database using JPA, we will be using Spring Data JPA subproject’s org.springframework.data.repository.CrudRepository marker interface. As you can see from Listing 4-9, the CrudRepository interface takes the type of domain object that it manipulates and the type of domain object’s identifier field as its generic parameters T and ID.
public interface CrudRepository<T, ID> extends Repository<T, ID> {
        <S extends T> S save(S var1);
        <S extends T> Iterable<S> saveAll(Iterable<S> var1);
        Optional<T> findById(ID var1);
        Iterable<T> findAll();
        Iterable<T> findAllById(Iterable<ID> var1);
        void deleteById(ID var1);
        void delete(T var1);
        void deleteAllById(Iterable<? extends ID> var1);
        void deleteAll(Iterable<? extends T> var1);
        void deleteAll();
        // Utility Methods
        long count();
        boolean existsById(ID var1);
}
Listing 4-9

CrudRepository API

We begin our repository implementation by creating a com.apress.repository package under the srcmainjava folder. Then, we create an OptionRepository interface as shown in Listing 4-10. As discussed earlier, the OptionRepository extends Spring Data’s CrudRepository and thereby inherits all of its CRUD methods. Because the OptionRepository works with the Option domain object, it passes Option and Long as generic parameter values.
package com.apress.repository;
import org.springframework.data.repository.CrudRepository;
import com.apress.domain.Option;
public interface OptionRepository extends CrudRepository<Option, Long> {
}
Listing 4-10

OptionRepository Interface

Taking the same approach, we then create PollRepository and VoteRepository interfaces, as shown in Listings 4-11 and 4-12.
public interface PollRepository extends CrudRepository<Poll, Long> {
}
Listing 4-11

PollRepository Interface

public interface VoteRepository extends CrudRepository<Vote, Long> {
}
Listing 4-12

OptionRepository Interface

Embedded Database

In the previous section, we created repositories, but we need a relational database for persisting data. The relational database market is full of options ranging from commercial databases such as Oracle and SQL Server to open-source databases such as MySQL and PostgreSQL. To speed up our QuickPoll application development, we will be using HSQLDB, a popular in-memory database. In-memory, aka embedded, databases don’t require any additional installations and can simply run as a JVM process. Their quick startup and shutdown capabilities make them ideal candidates for prototyping and integration testing. At the same time, they don’t usually provide a persistent storage and the application needs to seed the database every time it bootstraps.

Spring Boot provides excellent support for HSQLDB-, H2-, and Derby-embedded databases. The only requirement is to include a build dependency in the pom.xml file. Spring Boot takes care of starting the database during deployment and stopping it during application shutdown. There is no need to provide any database connection URLs or username and password. Listing 4-13 shows the dependency information that needs to be added to QuickPoll’s pom.xml file.
<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <scope>runtime</scope>
</dependency>
Listing 4-13

HSQLDB POM.XML Dependency

API Implementation

In this section, we will create Spring MVC controllers and implement our REST API endpoints. We begin by creating the com.apress.controller package under srcmainjava to house all of the controllers.

PollController Implementation

The PollController provides all of the necessary endpoints to access and manipulate the Poll and Polls resources. Listing 4-14 shows a bare-bones PollController class .
package com.apress.controller;
import javax.inject.Inject;
import org.springframework.web.bind.annotation.RestController;
import com.apress.repository.PollRepository;
@RestController
public class PollController {
        @Inject
        private PollRepository pollRepository;
}
Listing 4-14

PollController Class

The PollController class is annotated with a @RestController annotation . The @RestController is a convenient yet meaningful annotation and has the same effect as adding both @Controller and @ResponseBody annotations. Because we need to read and store Poll instances, we use the @Inject annotation to inject an instance of PollRepository into our controller. The javax.inject.Inject annotation introduced as part of Java EE 6 provides a standard mechanism for declaring dependencies. We use this annotation in favor of Spring’s proprietary @Autowired annotation to be more compliant. In order to use the @Inject annotation, we need to add the dependency shown in Listing 4-15 to the pom.xml file.
<dependency>
        <groupId>javax.inject</groupId>
        <artifactId>javax.inject</artifactId>
        <version>1</version>
</dependency>
Listing 4-15

Inject Dependency in POM File

A GET request on the /polls endpoint provides a collection of all of the polls available in the QuickPolls application. Listing 4-16 shows the necessary code for implementing this functionality. The shortcut annotation declares the URI and the allowed HTTP method. The getAllPolls method used ResponseEntity as its return type, indicating that the return value is the complete HTTP response. ResponseEntity gives you full control over the HTTP response, including the response body and response headers. The method implementation begins with reading all of the polls using the PollRepository. We then create an instance of ResponseEntity and pass in Poll data and the HttpStatus.OK status value. The Poll data becomes part of the response body and OK (code 200) becomes the response status code.
@GetMapping("/polls")
public ResponseEntity<Iterable<Poll>> getAllPolls() {
        Iterable<Poll> allPolls = pollRepository.findAll();
        return new ResponseEntity<>(pollRepository.findAll(), HttpStatus.OK);
}
Listing 4-16

GET Verb Implementation for /polls

Let’s quickly test our implementation by running the QuickPoll application. In a command line, navigate to the quick-poll project directory and run the following command:
mvn spring-boot:run
Launch the Postman app in your Chrome browser and enter the URL http://localhost:8080/polls, as shown in Figure 4-7, and hit Send. Because we don’t have any polls created yet, this command would result in an empty collection.
../images/332520_2_En_4_Chapter/332520_2_En_4_Fig7_HTML.png
Figure 4-7

Get All Polls request

Note

The downloaded source code contains an exported Postman collection with requests that can be used to run tests in this chapter. Simply import this collection into your Postman application and start using it.

The next stop for us is to implement capability to add new polls to the PollController. We accomplish this by implementing the POST verb functionality, as shown in Listing 4-17. The createPoll method takes a parameter of the type Poll. The @RequestBody annotation tells Spring that the entire request body needs to be converted to an instance of Poll. Spring uses the incoming Content-Type header to identify a proper message converter and delegates the actual conversion to it. Spring Boot comes with message converters that support JSON and XML resource representations. Inside the method, we simply delegate the Poll persistence to PollRepository’s save method. We then create a new ResponseEntity with status CREATED (201) and return it.
@PostMapping("/polls")
public ResponseEntity<?> createPoll(@RequestBody Poll poll) {
        poll = pollRepository.save(poll);
        return new ResponseEntity<>(null, HttpStatus.CREATED);
}
Listing 4-17

Implementation to Create New Poll

Although this implementation fulfills the request, the client has no way of knowing the URI of the newly created Poll. For example, if the client wants to share the newly created Poll to a social networking site, the current implementation will not suffice. A best practice is to convey the URI to the newly created resource using the Location HTTP header. Building the URI would require us to inspect the HttpServletRequest object to obtain information such as Root URI and context. Spring makes the URI generation process easy via its ServletUriComponentsBuilder utility class:
URI newPollUri = ServletUriComponentsBuilder
                        .fromCurrentRequest()
                        .path("/{id}")
                        .buildAndExpand(poll.getId())
                        .toUri();
The fromCurrentRequest method prepares the builder by copying information such as host, schema, port, and so on from the HttpServletRequest. The path method appends the passed-in path parameter to the existing path in the builder. In the case of the createPoll method, this would result in http://localhost:8080/polls/{id}. The buildAndExpand method would build a UriComponents instance and replace any path variables ({id} in our case) with passed-in value. Finally, we invoke the toUri method on the UriComponents class to generate the final URI. The complete implementation of the createPoll method is shown in Listing 4-18.
@PostMapping("/polls")
public ResponseEntity<?> createPoll(@RequestBody Poll poll) {
        poll = pollRepository.save(poll);
        // Set the location header for the newly created resource
        HttpHeaders responseHeaders = new HttpHeaders();
        URI newPollUri = ServletUriComponentsBuilder
                                .fromCurrentRequest()
                                .path("/{id}")
                                .buildAndExpand(poll.getId())
                                .toUri();
        responseHeaders.setLocation(newPollUri);
        return new ResponseEntity<>(null, responseHeaders, HttpStatus.CREATED);
}
Listing 4-18

Complete Implementation of Create Poll

To test our newly added functionality, start the QuickPoll application. If you have the application already running, you need to terminate the process and restart it. Enter the information in Postman as shown in Figure 4-8 and hit Send. Make sure that you have added the Content-Type header with value application/json. The JSON used in the body is shown here:
{
    "question": "Who will win SuperBowl this year?",
    "options": [
                 {"value": "New England Patriots"},
                 {"value": "Seattle Seahawks"},
                 {"value": "Green Bay Packers"},
                 {"value": "Denver Broncos"}]
}
../images/332520_2_En_4_Chapter/332520_2_En_4_Fig8_HTML.png
Figure 4-8

Create Poll Postman example

On completion of the request, you will see a Status 201 Created message and headers:
Content-Length ® 0
Date ® Mon, 23 Feb 2015 00:05:11 GMT
Location ® http://localhost:8080/polls/1
Server ® Apache-Coyote/1.1
Now let’s turn our attention to accessing an individual poll. Listing 4-19 gives the necessary code. The value attribute in shortcut annotations (@GetMapping, @PostMapping, etc.) takes a URI template /polls/{pollId}. The placeholder {pollId} along with the @PathVarible annotation allows Spring to examine the request URI path and extract the pollId parameter value. Inside the method, we use the PollRepository’s findById finder method to read the poll and pass it as part of a ResponseEntity.
@GetMapping("/polls/{pollId}")
public ResponseEntity<?> getPoll(@PathVariable Long pollId) {
        Optional<Poll> poll = pollRepository.findById(pollId);
        if(!poll.isPresent()) {
                throw new Exception("Pool not found");
        }
        return new ResponseEntity<>(poll.get(), HttpStatus.OK);
}
Listing 4-19

Retrieving an Individual Poll

In the same fashion, we implement the functionality to update and delete a Poll , as shown in Listing 4-20.
@PutMapping("/polls/{pollId}")
public ResponseEntity<?> updatePoll(@RequestBody Poll poll, @PathVariable Long pollId) {
        // Save the entity
        Poll newPoll = pollRepository.save(poll);
        return new ResponseEntity<>(HttpStatus.OK);
}
@DeleteMapping("/polls/{pollId}")
public ResponseEntity<?> deletePoll(@PathVariable Long pollId) {
        pollRepository.deleteById(pollId);
        return new ResponseEntity<>(HttpStatus.OK);
}
Listing 4-20

Update and Delete a Poll

Once you have this code added to the PollController, restart the QuickPoll application and execute the Postman request as shown in Figure 4-8 to create a new poll. Then input the information in Figure 4-9 to create a new Postman request and update the poll. Notice that the PUT request contains the entire Poll representation along with IDs.
../images/332520_2_En_4_Chapter/332520_2_En_4_Fig9_HTML.png
Figure 4-9

Update poll

This concludes the implementation of the PostController.

VoteController Implementation

Following the principles used to create PollController, we implement the VoteController class . Listing 4-21 gives the code for the VoteController class along with the functionality to create a vote. The VoteController uses an injected instance of VoteRepository to perform CRUD operations on Vote instances.
@RestController
public class VoteController {
        @Inject
        private VoteRepository voteRepository;
        @PostMapping("/polls/{pollId}/votes")
public ResponseEntity<?> createVote(@PathVariable Long pollId, @RequestBody Vote vote) {
                vote = voteRepository.save(vote);
                // Set the headers for the newly created resource
                HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setLocation(ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(vote.getId()).toUri());
                return new ResponseEntity<>(null, responseHeaders, HttpStatus.CREATED);
        }
}
Listing 4-21

VoteController Implementation

To test the voting capabilities, POST a new Vote to the /polls/1/votes endpoint with an option in the request body, as shown in Figure 4-10. On successful request execution, you will see a Location response header with value http://localhost:8080/polls/1/votes/1.
../images/332520_2_En_4_Chapter/332520_2_En_4_Fig10_HTML.png
Figure 4-10

Cast a new vote

Next, we look at implementing the capability to retrieve all votes for a given poll. The findAll method in the VoteRepository returns all votes in the database. Because this would not meet our needs, we need to add this functionality to the VoteRepository as shown in Listing 4-22.
import org.springframework.data.jpa.repository.Query;
public interface VoteRepository extends CrudRepository<Vote, Long> {
@Query(value="select v.* from Option o, Vote v where o.POLL_ID = ?1 and
v.OPTION_ID = o.OPTION_ID", nativeQuery = true)
        public Iterable<Vote> findByPoll(Long pollId);
}
Listing 4-22

Modified VoteRepository Implementation

The custom finder method findVotesByPoll takes the ID of the Poll as its parameter. The @Query annotation on this method takes a native SQL query along with the nativeQuery flag set to true. At runtime, Spring Data JPA replaces the ?1 placeholder with the passed-in pollId parameter value. Next, we implement the /polls/{pollId}/votes endpoint in the VoteController, as shown in Listing 4-23.
@GetMapping("/polls/{pollId}/votes")
public Iterable<Vote> getAllVotes(@PathVariable Long pollId) {
        return voteRepository. findByPoll(pollId);
}
Listing 4-23

GET All Votes Implementation

ComputeResultController Implementation

The final piece remaining for us is the implementation of the ComputeResult resource. Because we don’t have any domain objects that can directly help generate this resource representation, we implement two data transfer objects or DTOs—OptionCount and VoteResult. The OptionCount DTO contains the ID of the option and a count of votes casted for that option. The VoteResult DTO contains the total votes cast and a collection of OptionCount instances. These two DTOs are created under the com.apress.dto package, and their implementation is given in Listing 4-24.
package com.apress.dto;
public class OptionCount {
        private Long optionId;
        private int count;
        // Getters and Setters omitted for brevity
}
package com.apress.dto;
import java.util.Collection;
public class VoteResult {
        private int totalVotes;
        private Collection<OptionCount> results;
        // Getters and Setters omitted for brevity
}
Listing 4-24

DTOs for ComputeResult Resources

Following the principles used in creating the PollController and VoteController, we create a new ComputeResultController class , as shown in Listing 4-25. We inject an instance of VoteRepository into the controller, which is used to retrieve votes for a given poll. The computeResult method takes pollId as its parameter. The @RequestParam annotation instructs Spring to retrieve the pollId value from an HTTP query parameter. The computed results are sent to the client using a newly created instance of ResponseEntity.
package com.apress.controller;
@RestController
public class ComputeResultController {
        @Inject
        private VoteRepository voteRepository;
        @GetMapping("/computeresult")
        public ResponseEntity<?> computeResult(@RequestParam Long pollId) {
                VoteResult voteResult = new VoteResult();
                Iterable<Vote> allVotes = voteRepository.findByPoll(pollId);
                // Algorithm to count votes
                return new ResponseEntity<VoteResult>(voteResult, HttpStatus.OK);
        }
}
Listing 4-25

ComputeResultController implementation

There are several ways to count votes associated with each option. This code provides one such option:
int totalVotes = 0;
Map<Long, OptionCount> tempMap = new HashMap<Long, OptionCount>();
for(Vote v : allVotes) {
        totalVotes ++;
        // Get the OptionCount corresponding to this Option
        OptionCount optionCount = tempMap.get(v.getOption().getId());
        if(optionCount == null) {
                optionCount = new OptionCount();
                optionCount.setOptionId(v.getOption().getId());
                tempMap.put(v.getOption().getId(), optionCount);
        }
        optionCount.setCount(optionCount.getCount()+1);
}
voteResult.setTotalVotes(totalVotes);
voteResult.setResults(tempMap.values());
This concludes the ComputeResult controller implementation. Start/restart the QuickPoll application. Using the earlier Postman requests, create a poll and cast votes on its options. Then create a new Postman request as shown in Figure 4-11 and submit it to test our /computeresult endpoint.
../images/332520_2_En_4_Chapter/332520_2_En_4_Fig11_HTML.png
Figure 4-11

ComputeResult endpoint test

On successful completion, you will see an output similar to this:
{
    "totalVotes": 7,
    "results": [
        {
            "optionId": 1,
            "count": 4
        },
        {
            "optionId": 2,
            "count": 3
        }
    ]
}

Summary

In this chapter, we looked at creating RESTful services for the QuickPoll application. Most of our examples in this chapter assumed a “happy path” in which everything goes as planned. However, this rarely happens in the real world. In the next chapter, we will look at handling errors, validating input data, and communicating meaningful error messages.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.81.43