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
@Bean
annotation
Get started
-
Review the
Web Apps
slides. -
You must have completed (or fast-forwarded to) the
Deployment pipelines lab.
You must have yourpal-tracker
application associated with the
pipeline-solution
codebase 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 theTimeEntryRepository
interface using aHashMap
based implementation.git show mvc-solution:src/main/java/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.java
-
InMemoryTimeEntryRepositoryTest
–
the unit tests for theInMemoryTimeEntryRepository
git 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
TimeEntryControllerTest
class:-
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
TimeEntryControllerTest
unit 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 assertion
logic 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 theTimeEntryControllerTest
to 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@RestController
annotation.
This is a regular Spring MVC controller,
but it also includes the@ResponseBody
annotation 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
TimeEntryApiTest
to guide the implementation of the
annotations you researched in the
previous section.
in theTimeEntryController
controller. -
Use a Test First approach:
Iterate through eachTimeEntryApiTest
test case,
and add annotations only for the handler method being tested on
the associated test case. -
After you are done making all the
TimeEntryApiTest
tests pass,
make sure all the tests in thepal-tracker
project pass:./gradlew clean build
Exercise endpoints
-
Start your application locally
-
Use the
curl
commands below to verify
that your application behaves as expected.Get all time entries
curl -v localhost:8080/time-entries
Create 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_ID
to 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=42
You 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:8080
with 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
@Bean
annotation
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
TimeEntry
class service in the
pal-tracker
application?
Does the design and usage ofTimeEntry
break any of the SOLID
principles,
and if so,
name one. -
How could the
pal-tracker
application 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.