In this lab, you will build a service which will expose a
RESTful
API for time entries.
A “time entry” is a record of hours worked for a specific user, on a
particular day — the kind of information that you might record
on a timesheet for project tracking or billing purposes.
The lab will introduce the fundamentals of
Spring MVC
for building web services.
Learning outcomes
After completing the lab, you will be able to:
- Develop RESTful JSON APIs using Spring MVC controllers
- Demonstrate the ability to manually test a JSON API
- Describe when to use the
@Beanannotation
Get started
-
Review the
Web Apps
slides. -
You must have completed (or fast-forwarded to) the
Deployment pipelines lab.
You must have yourpal-trackerapplication associated with the
pipeline-solutioncodebase deployed and running on
Tanzu Application Service. -
In a terminal window,
make sure you start in the~/workspace/pal-tracker
directory. -
Pull in unit and integration tests for this lab:
git cherry-pick mvc-start
The goal is to get the test suite passing by the end of the lab.
If you get stuck
If you get stuck within this lab,
you can either
view the solution,
or you can
fast-forward to the mvc-solution tag.
Time Entries CRUD
The start point of the lab provides you with a time entry data class,
an interface used to define the operations of
a repository for those entities, and an in-memory implementation of
that repository interface.
You will build a service that can do
CRUD (Create, Read, Update,Delete) operations
on time entries.
Data layer
Review the following Java classes:
-
TimeEntry– Java “data class” which functions as a
value object
and a logical repository persisted record.
It will also be used in the controller you will build in this lab as a
data transfer object.git show mvc-solution:src/main/java/io/pivotal/pal/tracker/TimeEntry.java -
TimeEntryRepository– The interface specifying the CRUD repository
operations.
You will use this in another lab for building out a relational
database backed CRUD repository.git show mvc-solution:src/main/java/io/pivotal/pal/tracker/TimeEntryRepository.java -
InMemoryTimeEntryRepository–
implements theTimeEntryRepositoryinterface using aHashMap
based implementation.git show mvc-solution:src/main/java/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.java -
InMemoryTimeEntryRepositoryTest–
the unit tests for theInMemoryTimeEntryRepositorygit show mvc-solution:src/test/java/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.java
Get your code to compile
-
Your code will not compile at this point because you cherry-picked
additional tests for classes which are not implemented. -
Create empty classes and stub methods so that your code compiles.
Use the tests as a guide.
Consider using Java IDE auto-completion features to help you. -
Do not move on until the following command passes (indicating that
your code compiles)../gradlew clean compileTestJava
Wiring your in-memory repository
Declare a
@Bean
method which returns your implementation of the TimeEntryRepository
in the PalTrackerApplication class.
REST controller
In this section of the lab,
you will build out a REST service for time entries using a
Spring MVC Annotated REST Controller.
Implement the controller Java class
-
Review each test case in the
TimeEntryControllerTestclass:-
Notice the pattern of setting up text fixtures,
executing the controller methods under test,
and verifying successful execution of each method.// Test setup ... // Unit under test ... controller.<method> ... // Verification verify(timeEntryRepository) ... assertThat(...) ... -
Notice the use of
mocking
patterns, such as the mock trained stubs in the test fixture
setup:doReturn(expectedResult) .when(timeEntryRepository) ...and use of mock verify in the test verification stage:
verify(timeEntryRepository) ...
-
-
Use the
TimeEntryControllerTestunit test to guide the
implementation of theTimeEntryController,
using a Test First approach:-
Lead with the tests –
Start with the first test casetestCreate() -
Run the test first and watch it fail (red).
Analyze the failure,
and implement the fix to clear the failure.
Repeat this step in the test case until it passes (green). -
Use the
mock, verify, and assertionlogic to drive out the
implementation of the unit (controller method) under test. -
Do the simplest implementation possible to make the current test
pass –
do not anticipate design for future tests. -
Repeat for each test,
from beginning of theTimeEntryControllerTestto end,
until all tests are green.
-
Research how to implement the REST controller with Spring
Spring MVC controllers are Java objects with an annotation that
signals to Spring that they are controllers.
-
Use the following tips to build out your solution:
-
For convenience,
use the@RestControllerannotation.
This is a regular Spring MVC controller,
but it also includes the@ResponseBodyannotation which
automatically serializes objects into JSON when they are
returned from a handler method. -
Controller handler methods are annotated with
@RequestMapping
or one of the associated
custom annotations,
such@GetMapping,@PostMapping,@PutMapping
and@DeleteMapping.
Use the custom annotations in your solution.The annotation takes the URL that the handler will respond to as
an argument. -
Pay close attention to the
handler method argument annotations
such as
@RequestBody
and
@PathVariable,
each has a specific purpose for making sure elements of the
HTTP request are passed to the appropriate handler method
arguments. -
All of your controller methods will return a
ResponseEntity
type.
This allows you to return objects (serialized into JSON) and to
have control of the HTTP status codes.
-
-
For full context, read about
Spring’s annotated controllers here.
Annotate the TimeEntryController
-
Use the
TimeEntryApiTestto guide the implementation of the
annotations you researched in the
previous section.
in theTimeEntryControllercontroller. -
Use a Test First approach:
Iterate through eachTimeEntryApiTesttest case,
and add annotations only for the handler method being tested on
the associated test case. -
After you are done making all the
TimeEntryApiTesttests pass,
make sure all the tests in thepal-trackerproject pass:./gradlew clean build
Exercise endpoints
-
Start your application locally
-
Use the
curlcommands below to verify
that your application behaves as expected.Get all time entries
curl -v localhost:8080/time-entriesCreate a time entry
curl -v -XPOST -H"Content-Type: application/json" localhost:8080/time-entries -d'{"projectId": 1, "userId": 2, "date": "2019-01-01", "hours": 8}'Note: On UNIX-like systems, you can set the shell variable
TIME_ENTRY_IDto the value of the ID that was created by the
previous command.
Assuming the ID value was 42, you would do that like this:TIME_ENTRY_ID=42You can then cut and paste the commands in the following sections
directly.
Otherwise, replace the placeholder${TIME_ENTRY_ID}with the value
of the ID.Get a time entry by ID
curl -v localhost:8080/time-entries/${TIME_ENTRY_ID}Update a time entry by ID
curl -v -XPUT -H"Content-Type: application/json" localhost:8080/time-entries/${TIME_ENTRY_ID} -d'{"projectId": 88, "userId": 99, "date": "2019-01-01", "hours": 8}'Delete a time entry by ID
curl -v -XDELETE -H"Content-Type: application/json" localhost:8080/time-entries/${TIME_ENTRY_ID} -
Move on when you have validated that your application behaves
correctly on your local machine.
Deploy
-
Push your code to GitHub and let the pipeline deploy to your
Tanzu Application Service environment. -
Redo the Exercise endpoints section on
Tanzu Application Service by replacinglocalhost:8080with the
Tanzu Application Service environment route. -
Make sure all the endpoints succeed in the
Tanzu Application Service environment before you move on.
Wrap up
Now that you have completed the lab, you should be able to:
- Develop RESTful JSON APIs using Spring MVC controllers
- Demonstrate the ability to manually test a JSON API
- Describe when to use the
@Beanannotation
Extras
If you have some additional time,
work through the following exercises.
TimeEntry class smells
Review the TimeEntry class,
as well as the all the tests that use it.
Some questions to ponder:
-
How many concerns does the
TimeEntryclass service in the
pal-trackerapplication?
Does the design and usage ofTimeEntrybreak any of the SOLID
principles,
and if so,
name one. -
How could the
pal-trackerapplication design be improved as the
app evolves?
API versioning
Try implementing a new API version alongside the current time entry
controller.
Take a look at the
App Continuum documentation
for ideas on how to implement api versioning.