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
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.
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
- 1.
Resource identification
- 2.
Resource representation
- 3.
Endpoint identification
- 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.
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.
The JavaScript Object Notation, or JSON, is a lightweight format for exchanging information. Information in JSON is organized around two structures: objects and arrays.
Poll Representation
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.
List of Polls Representation
Vote Representation
List of Votes Representation
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
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 |
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 |
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 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.
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
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.
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
Option Class
Poll Class
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.
CrudRepository API
OptionRepository Interface
PollRepository Interface
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.
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
PollController Class
Inject Dependency in POM File
GET Verb Implementation for /polls
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.
Implementation to Create New Poll
Complete Implementation of Create Poll
Retrieving an Individual Poll
Update and Delete a Poll
This concludes the implementation of the PostController.
VoteController Implementation
VoteController Implementation
Modified VoteRepository Implementation
GET All Votes Implementation
ComputeResultController Implementation
DTOs for ComputeResult Resources
ComputeResultController implementation
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.