- Domain objects are the backbone for an application and contain the business logic.
- Create a sub package of
io.zipcoder.tc_spring_poll_application
nameddomain
.
-
Create an
Option
class in thedomain
sub-package. -
Option
class signature is annotated with@Entity
-
Option
has anid
instance variable of typeLong
id
should beannotated
with@Id
- denotes primary key of this entity
@GeneratedValue
- configures the way of increment of the specified
column(field)
- configures the way of increment of the specified
@Column(name = "OPTION_ID")
- specifies mapped column for a persistent property or field
-
Option
has avalue
instance variable of typeString
value
should beannotated
with@Column(name = "OPTION_VALUE")
-
Create a
getter
andsetter
for each of the respective instance variables.
-
Create a
Poll
class in thedomain
sub-package. -
Poll
class signature is annotated with@Entity
-
Poll
has anid
instance variable of typeLong
id
should beannotated
with@Id
@GeneratedValue
Column(name = "POLL_ID")
-
Poll
has aquestion
instance variable of typeString
question
should beannotated
with@Column(name = "QUESTION")
-
Poll
has anoptions
instance variable of typeSet
ofOption
options
should beannotated
with@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "POLL_ID")
@OrderBy
-
Create a
getter
andsetter
for each of the respective instance variables.
-
Create a
Vote
class in thedomain
sub-package. -
Vote
class signature is annotated with@Entity
-
Vote
has anid
instance variable of typeLong
id
should beannotated
with@Id
@GeneratedValue
Column(name = "VOTE_ID")
-
Vote
has aoption
instance variable of typeOption
option
should beannotated
with@ManyToOne
@JoinColumn(name = "OPTION_ID")
-
Create a
getter
andsetter
for each of the respective instance variables.
- Repositories or Data Access Objects (DAO), provide an abstraction for interacting with datastores.
- Typically DAOs include an interface that provides a set of finder methods such as
findById
,findAll
, for retrieving data, and methods to persist and delete data. - It is customary to have one
Repository
perdomain
object. - Create a sub-package of
io.zipcoder.tc_spring_poll_application
namedrepositories
.
- Create an
OptionRepository
interface in therepositories
subpackage. OptionRepository
extendsCrudRepository<Option, Long>
- Create a
PollRepository
interface in therepositories
subpackage. PollRepository
extendsCrudRepository<Poll, Long>
- Create a
VoteRepository
interface in therepositories
subpackage. VoteRepository
extendsCrudRepository<Vote, Long>
- Controllers provides all of the necessary endpoints to access and manipulate respective domain objects.
- REST resources are identified using URI endpoints.
- Create a sub package of
io.zipcoder.tc_spring_poll_application
namedcontroller
.
-
Create a
PollController
class in thecontroller
sub package.PollController
signature should beannotated
with@RestController
-
PollController
has apollRepository
instance variable of typePollRepository
pollRepository
should beannotated
with@Inject
- The method definition below supplies a
GET
request on the/polls
endpoint which provides a collection of all of the polls available in the QuickPolls application. Copy and paste this into yourPollController
class.
@RequestMapping(value="/polls", method= RequestMethod.GET)
public ResponseEntity<Iterable<Poll>> getAllPolls() {
Iterable<Poll> allPolls = pollRepository.findAll();
return new ResponseEntity<>(allPolls, HttpStatus.OK);
}
- The method above begins with reading all of the polls using the
PollRepository
. - We then create an instance of
ResponseEntity
and pass inPoll
data and theHttpStatus.OK
status value. - The
Poll
data becomes part of the response body andOK
(code 200) becomes the response status code.
- Ensure that the
start-class
tag in yourpom.xml
encapsulatesio.zipcoder.springdemo.QuickPollApplication
- Open a command line and navigate to the project's root directory and run this command:
mvn spring-boot:run
- Launch the Postman app in your Chrome browser and enter the URL
http://localhost:8080/polls
and hit Send. - Because we don’t have any polls created yet, this command should result in an empty collection.
- We accomplish the capability to add new polls to the
PollController
by implementing thePOST
verb functionality in acreatePoll
method:
@RequestMapping(value="/polls", method=RequestMethod.POST)
public ResponseEntity<?> createPoll(@RequestBody Poll poll) {
poll = pollRepository.save(poll);
return new ResponseEntity<>(null, HttpStatus.CREATED);
}
- Take note that the method
- has a parameter of type
@RequestBody Poll poll
@RequestBody
tells Spring that the entire request body needs to be converted to an instance of Poll
- delegates the
Poll
persistence toPollRepository
’s save methodpoll = pollRepository.save(poll);
- has a parameter of type
- Best practice is to convey the URI to the newly created resource using the Location HTTP header via Spring's
ServletUriComponentsBuilder
utility class. This will ensure that the client has some way of knowing the URI of the newly created Poll.
URI newPollUri = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(poll.getId())
.toUri();
- Modify the
createPoll
method so that it returns aResponseEntity
which takes an argument of anew HttpHeaders()
whose location has been set to the abovenewPollUri
via thesetLocation
method.
- The code snippet below enables us to access an individual poll.
- The value attribute in the
@RequestMapping
takes a URI template/polls/{pollId}
. - The placeholder
{pollId}
along with@PathVarible
annotation allows Spring to examine the request URI path and extract thepollId
parameter value. - Inside the method, we use the
PollRepository
’sfindOne
finder method to read the poll and pass it as part of aResponseEntity
.
@RequestMapping(value="/polls/{pollId}", method=RequestMethod.GET)
public ResponseEntity<?> getPoll(@PathVariable Long pollId) {
Poll p = pollRepository.findOne(pollId);
return new ResponseEntity<> (p, HttpStatus.OK);
}
- The code snippet below enables us to update a poll.
RequestMapping(value="/polls/{pollId}", method=RequestMethod.PUT)
public ResponseEntity<?> updatePoll(@RequestBody Poll poll, @PathVariable Long pollId) {
// Save the entity
Poll p = pollRepository.save(poll);
return new ResponseEntity<>(HttpStatus.OK);
}
- The code snippet below enables us to delete a poll.
@RequestMapping(value="/polls/{pollId}", method=RequestMethod.DELETE)
public ResponseEntity<?> deletePoll(@PathVariable Long pollId) {
pollRepository.delete(pollId);
return new ResponseEntity<>(HttpStatus.OK);
}
- Restart the QuickPoll application.
- Use Postman to execute a
PUT
tohttp://localhost:8080/polls/1
whose request body is theJSON
object below. - You can modify the request body in Postman by navigating to the
Body
tab, selecting theraw
radio button, and selecting theJSON
option from the text format dropdown.
{
"id": 1,
"question": "What's the best netflix original?",
"options": [
{ "id": 1, "value": "Black Mirror" },
{ "id": 2, "value": "Stranger Things" },
{ "id": 3, "value": "Orange is the New Black"},
{ "id": 4, "value": "The Get Down" }
]
}
- Following the principles used to create
PollController
, we implement theVoteController
class. - Below is the code for the
VoteController
class along with the functionality to create a vote. - The
VoteController
uses an injected instance ofVoteRepository
to performCRUD
operations on Vote instances.
@RestController
public class VoteController {
@Inject
private VoteRepository voteRepository;
@RequestMapping(value = "/polls/{pollId}/votes", method = RequestMethod.POST)
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);
}
}
- To test the voting capabilities,
POST
a new Vote to the/polls/1/votes
endpoint with the option object expressed inJSON
below. - On successful request execution, you will see a Location response header with value http://localhost:8080/polls/1/votes/1.
{
"option": { "id": 1, "value": "Black Mirror" }
}
- The method
findAll
in theVoteRepository
retrieves all votes in a Database rather than a given poll. - To ensure we can get votes for a given poll, we must add the code below to our
VoteRepository
.
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> findVotesByPoll(Long pollId);
}
- The custom finder method
findVotesByPoll
takes theID
of thePoll
as its parameter. - The
@Query
annotation on this method takes a native SQL query along with thenativeQuery
flag set totrue
. - At runtime, Spring Data JPA replaces the
?1
placeholder with the passed-inpollId
parameter value.
- Create a
getAllVotes
method in theVoteController
@RequestMapping(value="/polls/{pollId}/votes", method=RequestMethod.GET)
public Iterable<Vote> getAllVotes(@PathVariable Long pollId) {
return voteRepository. findByPoll(pollId);
}
- 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
- Create a sub package of
java
nameddtos
- The
OptionCount
DTO contains theID
of the option and a count of votes casted for that option.
public class OptionCount {
private Long optionId;
private int count;
public Long getOptionId() {
return optionId;
}
public void setOptionId(Long optionId) {
this.optionId = optionId;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
- The
VoteResult
DTO contains the total votes cast and a collection ofOptionCount
instances.
import java.util.Collection;
public class VoteResult {
private int totalVotes;
private Collection<OptionCount> results;
public int getTotalVotes() {
return totalVotes;
}
public void setTotalVotes(int totalVotes) {
this.totalVotes = totalVotes;
}
public Collection<OptionCount> getResults() {
return results;
}
public void setResults(Collection<OptionCount> results) {
this.results = results;
}
}
- Following the principles used in creating the
PollController
andVoteController
, we create a newComputeResultController
class
@RestController
public class ComputeResultController {
@Inject
private VoteRepository voteRepository;
@RequestMapping(value = "/computeresult", method = RequestMethod.GET)
public ResponseEntity<?> computeResult(@RequestParam Long pollId) {
VoteResult voteResult = new VoteResult();
Iterable<Vote> allVotes = voteRepository.findVotesByPoll(pollId);
// Algorithm to count votes
return new ResponseEntity<VoteResult>(voteResult, HttpStatus.OK);
}
- We inject an instance of
VoteRepository
into the controller, which is used to retrieve votes for a given poll. - The
computeResult
method takespollId
as its parameter. - The
@RequestParam
annotation instructs Spring to retrieve thepollId
value from a HTTP query parameter. - The computed results are sent to the client using a newly created instance of
ResponseEntity
.
- Start/restart the
QuickPoll
application. - Using the earlier Postman requests, create a poll and cast votes on its options.
- Ensure a JSON file with a
status
of200
is returned by executing aGET
request ofhttp://localhost:8080/computeresults?pollId=1
via Postman