Skip to content

Chapter REST outline

Aslak Knutsen edited this page Jul 12, 2013 · 10 revisions

Use Case / Requirements

  • Expose stored data via reusable API

As a 3. party Integrator I should be able Add/Change/Delete a Conference

As a 3. party Integrator I should be able Add/Change/Delete a Session
  to Conferences

As a 3. party Integrator I should be able Add/Change/Delete a Attachment
 to Sessions and Conferences

As a 3. party Integrator I should be able Add/Change/Delete a Venue
  (and attch to Conference and Session)

DAP (Domain Application Protocol)

  • /

    • GET → Links

    • Link → /conference

    • Link → /Venue

  • /conference

    • GET → List

    • POST → Add

  • /conference/[c_id] application/vnd.ced+xml;type=conference

    • GET → Single

    • PUT → Update

    • DELETE → Remove

    • Link → /venue/[v_id]

    • Link → /attachment/[a_id]

  • /conference/[c_id]/session application/vnd.ced+xml;type=session

    • GET → List

    • POST → Add

  • /conference/[c_id]/session/[s_id]

    • GET → Single

    • PUT → Update

    • DELETE → Remove

    • Link → /venue/[v_id]/room/[r_id] application/vnd.ced+xml;type=session

    • Link → /attachment/[a_id]

  • /venue application/vnd.ced+xml;type=venue

    • GET → List

    • POST → Add

  • /venue/[v_id]/room application/vnd.ced+xml;type=room

    • GET → List

    • POST → Add

    • Link →/attachment/[a_id]

  • /venue/[v_id]/room/[r_id]

    • GET → Single

    • PUT → Update

    • DELETE → Remove

    • Link →/attachment/[a_id]

  • /attachment application/vnd.ced+xml;type=attachment

    • GET → List ?

    • POST → Add

  • /attachment/[a_id]

    • GET → List

    • POST → Add

Background

  • REST

    • What is REST

      • Why REST over WS-*

        • RPC vs REST (Resource)

      • Levels of REST maturity

        • 0: HTTP Transport

        • 1: Resources

        • 2: HTTP Verbs

        • 3: Hypermedia control, flow

    • JAX-RS

      • Concepts

        • MediaTypes, Links

      • Usage

        • @ Get/Post/Put/Delete Application/Path

      • Implementations

        • ResyEasy, Jersey, ?

Implementation

  • CDI Request Scoped Bean Delegating to Repository EJB

    • JPA Model != REST Model

    • Dynamically append/discover Links/Resources based on modules included in deployment

  1. Explanation of the utility of RepositoryResource

    • Base implementation to expose CRUD operations for a Resource based on a Repository<T>

      • POST /x/

      • GET, PUT, DELETE /x/{id}

    • Converts between REST Representation and Domain object via RepresentationConverter’s

      • RepresentationConverter is responsible for mapping the fields

    • Handles

      • NotFound(404)

      • Created(201) with Hedader: Location On

      • NoContent(204) On DELETE or successfull update

      • BadRequest(400) On PUT on a missing resource

      • Header: Last-Modified

      • Header: Content-Type

  2. And custom @ResourceModel

    • JAX-RS 1 missing Interceptors

      • Used to implement common features, Security, Cross Resource Links, Validation

    • CDI StereoType

      • RequestScoped, Interceptors

  3. Explanation of LinkableRepresenatation

    • Sonme Representation Objects are Linkable

    • Via the @ResourceModel @Linked interceptor a LinkProvider can link a given Resource to some other Resource

    • A way to link Resources that are not described in the Domain Model it self

      • Conference→Session is in the Conference Domain (conference has sessions)

      • Conference→Trackers(User) is linking two Domain models, Conference and User via the Relation domain

      • Domain Model links are handled directly by the Representation/RepresentationConverter

      • Non Domain Model links are handled via the LinkedIntercetptor and the LinkedRepresentation

  4. Explanation of ResourceLink

    • Representation of a link in the model

    • Holds mediatype, href, rel

  5. Create issue to explain EventRepositoryDecorator, and where that should be done

  6. Explain Graph.js and geekseek.js and who is calling it and how

    • (The rest of that bit can be saved for UI chapter)

    • AngularJS is used for the FrontEnd

      • geekseek.js is the AngularJS Module / Controller description

      • graph.js is a Node structure for doing resource calls

    • Normal flow would be:

      • Initiate(OPTIONS) and GET the root resource /api/

      • Initiate links provided by the root

      • Based on the initiation we know what we can do with the different 'top level/root' resources

      • e.g. POST(create) a Conference

      • Map the current link.rel to a angular html template

        • e.g. conference.html

      • Each template has two states; Form and Display

        • Display is a html view representation of the Resource

        • Form is the html create/edit representation of the Resource

  7. Explain RepresentationConverter and its role

    • The underlying Domain Model / JPA model is not the same as the REST model

      • While EE allow you to annotated JPA models with JAX-B bindings etc, you would want to split the two models

    • REST model might;

      • contain less data

      • combine JPA models into one

      • link resources

      • render it self in multiple different representations and formats

    • Some Resources act as Proxy resources and has no representation on their own

      • To allow these Resources to operate in a modular fashion we need a way to describe

        • convert(FROM, TO)

        • e.g. the Relation Resoruce. Links Users to a Conference. The Relation it self knows nothing about the Source/Target, but it knows how to get a T and a Converter that supports converting T to some Representation of T

  8. Explain JSON request > Representation classes via Jackson (runtime) or JAXB as called by JAX-RS

    • Maps HTTP Request to a JAX-RS Resource method

      • MediaType and @Path

    • MessageBodyReader and MessageBodyWriter SPI’s are used to marshal and unmarshal the body of the request / response

      • Any impl of MBR/MBW annotated with @Provider will get picked up by the Runtime

        • Can be filtered more specifically by uing @Consumes/@Produces MediaType expressions

        • Note: User provided @Providers compete with the built in Providers provided by the app server

          • The most specific Prodiver wins

== Requirement Test Scenarios

=== Overview

  • PUT data

  • GET data

  • POST data

  • Link data

=== Setup

  • Arquillian Warp + REST extension

    • CED/pom.xml dependencyManagement arquillian-warp, arquillian-wrp-rest

    • CED/CEW/CER/pom.xml dependency warp-bom, warp-rest

  • ConferenceResource and SessionResource REST services build on the common RepoisitoryResource service.

    • Rely on CDI @Inject of Repository<? extends Identifiable> to avoid binding the Service to the concrete JPA Repository Backend.

    • Allow us to swtich the impls around when we test

=== Domain Conference/ Session Story

  • Use of @InSequence to sequentially execute @Test methods

  • Follows the normal REST client execution flow from A-B

    • GET Root resource

    • Locate conference link

    • POST to create a new Conference

    • GET to read the created Conference

    • Locate session link

    • POST to create a new Session

    • GET to read the created Session

    • PUT to update the Session

    • DELETE to delete the Session

    • PUT to update the Conference

    • DELETE to delete the Conference

  • Pure client side test

  • Requires deployed 'something' that talks the REST APIs.

  • CreateConferenceAndSessionStory defines the Scenario, but not the deployment

@Test @InSequence(0)
public void shouldBeAbleToLocateConferenceRoot() throws Exception { .. }

@Test @InSequence(1)
public void shouldBeAbleToCreateConference() throws Exception { .. }

@Test @InSequence(2)
public void shouldBeAbleToGetConference() throws Exception { .. }

@Test @InSequence(3)
public void shouldBeAbleToUpdateConference() throws Exception { .. }

@Test @InSequence(4)
public void verifyUpdatedConference() throws Exception { .. }

@Test @InSequence(5)
public void shouldBeAbleToCreateSession() throws Exception { .. }

@Test @InSequence(6)
public void shouldBeAbleToGetSession() throws Exception { .. }

@Test @InSequence(7)
public void shouldBeAbleToUpdateSession() throws Exception { .. }

@Test @InSequence(8)
public void verifyUpdatedSession() throws Exception { .. }

@Test @InSequence(8)
public void shouldBeAbleToDeleteSession() throws Exception { .. }

@Test @InSequence(9)
public void verifyNotFoundForDeletedSession() throws Exception { .. }

@Test @InSequence(10)
public void shouldBeAbleToDeleteConference() throws Exception { .. }

@Test @InSequence(11)
public void verifyNotFoundForDeletedConference() throws Exception { .. }
  • The TestClass defines the whole 'scenario' and not the Test Method.

  • CreateConferenceAndSessionStoryTestCase extends CreateConferenceAndSessionStory defines the 'module level' @Deployment and is ran as part of the test phase in the rest-conference module.

    @Deployment(testable = false)
    public static WebArchive deploy() {
        return ConferenceRestDeployments.conference()
                .addAsWebInfResource(new File("src/main/resources/META-INF/beans.xml"));
    }
  • Creates a deployment with TestDouble/Fakes for the Repository / JPA layer.

    • TestConferenceRepository and SessionConferenceRepository simulate the JPA layer for Testing.

@ApplicationScoped
public abstract class TestRepository<T extends Identifiable> implements Repository<T> { .. }

public class TestConferenceRepository extends TestRepository<Conference> { .. }

@ApplicationScoped
public class TestSessionRepository implements Repository<Session> { .. }
  • Splitting the Test Scenario code and Deployment code allow for reuse of the Scenario code

    • CreateConferenceAndSessionStory is reused and executed against the final deployment as well

      • See "Assembly and Deployment" chapter

  • the Story Test tests the crud, flow and linking of resources

  • Uses RESTAssured library for fluent REST Client test calls

import static com.jayway.restassured.RestAssured.given;

uri_conferenceInstance =
      given().
          contentType(CONFERENCE_MEDIA_TYPE).
          body(conf).
      then().
          statusCode(Status.CREATED.getStatusCode()).
      when().
          post(uri_conference).
      header("Location");


uri_session =
      given().
      then().
          contentType(CONFERENCE_MEDIA_TYPE).
          statusCode(Status.OK.getStatusCode()).
          root("conference").
              body("link.find {it.@rel == 'bookmark'}.size()", equalTo(1)).
              body("link.find {it.@rel == 'self'}.size()", equalTo(1)).
      when().
          get(uri_conferenceInstance).
      body().
          path("conference.link.find {it.@rel == 'session'}.@href");
  • Does not share REST Representation objects with server side.

    • Client != Server

    • Test should verify a behavior. Sharing of Objects might be easier to code/update, but could also sneak in unexpected client changes which should have been caught by the tests.

=== Domain Conference Details

  • ConferenceResourceTestCase tests details of the REST service behavior.

  • Uses Arquillian Warp to allow easy control over permutations of data

    • Reuses same Test Double/ Fakes Repositories

  • Can create Conference domain objects on Client side and transfere them to container side

    • Controls which data to fetch trough the REST layer

    • No need to setup backend data store with all permutations

final Conference conference = new Conference()
    .setName("Name")
    .setTagLine("TagName")
    .setDuration(new Duration(new Date(), new Date()));

Warp.initiate(new Activity() {
    @Override
    public void perform() {
        given().
        then().
            contentType(CONFERENCE_MEDIA_TYPE).
            root("conference").
                body("name", equalTo(conference.getName())).
                body("tagLine", equalTo(conference.getTagLine())).
                body("start", equalToXmlDate(conference.getDuration().getStart())).
                body("end", equalToXmlDate(conference.getDuration().getEnd())).
        when().
            get(baseURL + "api/conference/{id}", conference.getId()).
        body();
    }
}).inspect(new SetupConference(conference));
  • @BeforeServlet called in-container before REST service is hit

  • We can produce the data we want the REST layer to receive

public static class SetupConference extends Inspection {
    private static final long serialVersionUID = 1L;

    private Conference conference;

    public SetupConference(Conference conference) {
        this.conference = conference;
    }

    @BeforeServlet
    public void store(Repository<Conference> repository) {
        repository.store(conference);
    }
}

=== Domain User

  • Not explained, only code

  • See Conference

=== Domain Venue

  • Not explained, only code.

  • See Conference