-
Notifications
You must be signed in to change notification settings - Fork 150
Chapter REST outline
-
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)
-
/
-
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
-
-
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, ?
-
-
-
-
CDI Request Scoped Bean Delegating to Repository EJB
-
JPA Model != REST Model
-
Dynamically append/discover Links/Resources based on modules included in deployment
-
-
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
-
-
-
And custom @ResourceModel
-
JAX-RS 1 missing Interceptors
-
Used to implement common features, Security, Cross Resource Links, Validation
-
-
CDI StereoType
-
RequestScoped, Interceptors
-
-
-
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
-
-
-
Explanation of ResourceLink
-
Representation of a link in the model
-
Holds mediatype, href, rel
-
-
Create issue to explain EventRepositoryDecorator, and where that should be done
-
https://github.com/arquillian/continuous-enterprise-development/issues/69
-
CDI Decorator that decorates all Repository<T> instances to add eventing
-
Created / Removed
-
-
Used by Services to hook into the lifecycles of objects created
-
MailService.x(@Observes @Created User) { sendMail() }
-
Services can choose to Observe During or After transcation commit
-
-
-
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
-
-
-
-
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
-
-
-
-
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