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.
After completing the lab, you will be able to:
@Bean
annotationReview the Web Apps slides.
You must have completed (or fast-forwarded to) the
Deployment pipelines lab.
You must have your pal-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 within this lab,
you can either
view the solution,
or you can
fast-forward to the mvc-solution
tag.
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.
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 the TimeEntryRepository
interface using a HashMap
based implementation.
git show mvc-solution:src/main/java/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.java
InMemoryTimeEntryRepositoryTest
-
the unit tests for the InMemoryTimeEntryRepository
git show mvc-solution:src/test/java/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.java
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
Declare a
@Bean
method which returns your implementation of the TimeEntryRepository
in the PalTrackerApplication
class.
In this section of the lab, you will build out a REST service for time entries using a Spring MVC Annotated REST Controller.
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 the TimeEntryController
,
using a Test First approach:
Lead with the tests -
Start with the first test case testCreate()
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 the TimeEntryControllerTest
to end,
until all tests are green.
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.
TimeEntryController
Use the TimeEntryApiTest
to guide the implementation of the
annotations you researched in the
previous section.
in the TimeEntryController
controller.
Use a Test First approach:
Iterate through each TimeEntryApiTest
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 the pal-tracker
project pass:
./gradlew clean build
Start your application locally
Use the curl
commands below to verify
that your application behaves as expected.
__Get all time entries__
```bash
curl -v localhost:8080/time-entries
```
__Create a time entry__
```bash
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:
```bash
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__
```bash
curl -v localhost:8080/time-entries/${TIME_ENTRY_ID}
```
__Update a time entry by ID__
```bash
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__
```bash
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.
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 replacing localhost:8080
with the
Tanzu Application Service environment route.
Make sure all the endpoints succeed in the Tanzu Application Service environment before you move on.
Now that you have completed the lab, you should be able to:
@Bean
annotationIf you have some additional time, work through the following exercises.
TimeEntry
class smellsReview 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 of TimeEntry
break any of the SOLID
principles,
and if so,
name one.
How could the pal-tracker
application design be improved as the
app evolves?
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.