Uncategorized

Spring MVC with REST Endpoints

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

  1. Review the
    Web Apps
    slides.

  2. 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.

  3. In a terminal window,
    make sure you start in the ~/workspace/pal-tracker
    directory.

  4. 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 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
    

Get your code to compile

  1. Your code will not compile at this point because you cherry-picked
    additional tests for classes which are not implemented.

  2. 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.

  3. 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

  1. 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)
      ...
      
  2. 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.

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.

  1. 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.

  2. For full context, read about
    Spring’s annotated controllers here.

Annotate the TimeEntryController

  1. Use the TimeEntryApiTest to guide the implementation of the
    annotations you researched in the
    previous section.
    in the TimeEntryController controller.

  2. 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.

  3. After you are done making all the TimeEntryApiTest tests pass,
    make sure all the tests in the pal-tracker project pass:

    ./gradlew clean build
    

Exercise endpoints

  1. Start your application locally

  2. 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}
    
  3. Move on when you have validated that your application behaves
    correctly on your local machine.

Deploy

  1. Push your code to GitHub and let the pipeline deploy to your
    Tanzu Application Service environment.

  2. Redo the Exercise endpoints section on
    Tanzu Application Service by replacing localhost:8080 with the
    Tanzu Application Service environment route.

  3. 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:

  1. 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.

  2. 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.