ICONUK 2016: REST Assured, Freeing Your Domino Data Has Never Been That Easy!
-
Upload
serdar-basegmez -
Category
Software
-
view
23 -
download
1
Transcript of ICONUK 2016: REST Assured, Freeing Your Domino Data Has Never Been That Easy!
REST Assured, Freeing Your Domino Data Has Never Been That Easy!
Serdar Basegmez, Developi Information Systems16th September 2016
• IBM Champion (2011 - 2016)
• Developi Information Systems, Istanbul
• Contributing…
• OpenNTF / LUGTR / LotusNotus.com
• Featured on…
• Engage UG, IBM Connect, ICON UK, NotesIn9…
• Also…
• Blogger and Podcaster on Scientific Skepticism
Serdar Başeğmez
RESTful Web Services
Representational state transfer (REST) is an architectural style used for web development. Systems and sites designed using this style aim for fast performance, reliability and the ability to scale (to grow and easily support extra users). To achieve these goals, developers work with reusable components that can be managed and updated without affecting the system as a whole while it is running.
Source: https://en.wikipedia.org/wiki/Representational_state_transfer
Old School Web Applications
Source: https://speakerdeck.com/jeffschenck/rest-easy-api-security-done-right
User Interface Business Logic Datastore
Front-end Back-end
ASP, PHP, CGI, Web Agents, JSP, etc.
← HTML, CSS, JavaScriptForms →
Web Applications Evolving
User Interface Business Logic Datastore
Front-end Back-end
Async web apps, Ruby on Rails, Django, JSF, XPages, etc.
← HTML, CSS, JavaScriptForms, AJAX →
Web Applications Evolving
User Interface Business Logic Datastore
Front-end Back-end
Modern Web frameworks, Angular.js, React.js, etc.
← HTML, CSS, JavaScript ← REST →
Web Applications Evolving
User Interface Business Logic Datastore
Mobile ApplicationsBack-end
Modern Web frameworks, Angular.js, React.js, etc.
← HTML, CSS, JavaScript ← REST →
Front-end
Web Applications Evolving
User Interface Business Logic Datastore
Mobile Applications Back-end
Modern Web frameworks, Angular.js, React.js, etc.
← HTML, CSS, JavaScript
← REST →
Front-end Microservice Microservice Microservice
RESTful, Everywhere!Solid Architecture
Well-defined practicesWidespread use in modern frameworks
Easily consumable in micro environments
Stateless / Cacheable / LayeredEvery request processed independently
Everything cacheableClient does not care who cooked the meal in the kitchen
⇣Scalable, Robust, Resilient
The Conversation Makes Sense!
Source: http://www.bizcoder.com/a-fresh-coat-of-rest-paint-on-a-soap-stack
The Conversation Makes Sense!GET/twink/contacts/DLEY-ACLH6YHTTP/1.1Host:homer.developi.infoCache-Control:no-cache
{"zip":"13202","state":"NY","lastName":"Abbate","middle":"J","country":"US","emailAddress":"[email protected]","number":"DLEY-ACLH6Y","city":"Syracuse","firstName":"Jessica"}
The Conversation Makes Sense!http://appserver.company.com/apps/contacts.nsf/
GiveMeTheContactWeNeedPleaseAgent?OpenAgent&id=1522
or…
http://appserver.company.com/api/contacts/1522
Conventions on URLs
GET http://appserver.company.com/api/contacts GET http://appserver.company.com/api/contacts/UK/London
POST http://appserver.company.com/api/contacts
Retrieve Contacts / Create a new Contact…
Conventions on URLs
GET http://appserver.company.com/api/contacts/1522 PUT http://appserver.company.com/api/contacts/1522
DELETE http://appserver.company.com/api/contacts/1522
Retrieve/Update/Delete the Contact resource with id=1522…
URI GET PUT POST DELETE
/contacts/ List Contacts Replace Contacts Create New Contact Delete Contacts
/contacts/id Retrieve a Contact Replace a Contact N/A (generally) Delete a Contact
Source: https://en.wikipedia.org/wiki/Representational_state_transfer
Conventions on URLs
Unconventional uses in URLs
GET https://api.twitter.com/1.1/statuses/show.json?id=1234567890
Retrieve the Tweet with id=1234567890…
MotivationPutting stuff into a small device!
Socializing with other developers!Opening to the wild… New animals out there!
Enough! We are moving…All / Some / None of the above
OptionsDomino Access Services (DAS)
Extension Library Components for RESTHardcoding (XAgents, Web agents)
Apache Wink Servlets
RESTful Options on DominoBenefits Challenges Suggested When?
Domino Access Services (DAS)
No Backend CodeZero-setup
Limited ControlNo Business Logic
Exposes the InternalsSimple internal integrations
ExtLib Components for REST
Less Backend CodeMinimal Setup
Partial/Full Customization
Error HandlingSpaghetti Code
URL Conventions
Simple needs for a limited scope
Hardcoding (XAgents, Web agents)
Tailor-madeNo Learning Curve
Hardcoding EverythingSpaghetti Code
URL Conventions
Very specific needs for a limited scope
Apache Wink ServletsTailor-made
Based on JAX-RSOSGi Benefits
Learning CurveBarrier to Entry
Large scope implementations, API
Design
Apache Wink ProjectComplete implementation of JAX-RS v1.1 Specification
Also includes RESTful Client moduleExtension Library comes with Apache Wink 1.1.2
Open SourceSpring integration, WebDAV support
Apache Wink Runtime Application Code
Apache Wink Basic Architecture
Wink Servlet(Customizable)
HTTP/HTTPS Client
Datastore
Resource
Resource
Resource
Resource
ControllersData Accessors
Tools/Utilities
Request Processor
Helpers
/BaseURI/* /BaseURI/Path-Patterns
Resource and Resource Representation
Collection
ResourceResourceResource
SubresourceSubresourceSubresource
Resource• Any addressable object is a resource.
• A resource class is;• Implements RESTful interactions (GET, POST, etc.)• A pure Java object decorated with annotations
• Do not confuse with Model class.
Resource Representation• The content of an object is called as Representation
• JSON, XML, Text, Form data, etc.
@Path("/contacts")publicclassContactResource{
privateDominoAccessoraccessor=newDominoAccessor(ContextInfo.getUserSession()); @GET() publicResponsegetContactList(@QueryParam("start")intstart,@QueryParam("count")intcount){ List<Contact>contactList=accessor.pullContacts(start,count); Stringresult=ModelUtils.toJson(contactList).toString(); returnResponse.ok(result,MediaType.APPLICATION_JSON).build(); } @Path("/{id}") @GET() publicResponsegetContact(@PathParam("id")Stringid){ Contactcontact=accessor.findContact(id);
if(null==contact){ thrownewWebApplicationException(Response.Status.NOT_FOUND); }else{ Stringresult=ModelUtils.toJson(contact).toString(); returnResponse.ok(result,MediaType.APPLICATION_JSON).build(); } }}
{"zip":"13202","state":"NY","lastName":"Abbate","middle":"J","country":"US","emailAddress":"[email protected]","number":"DLEY-ACLH6Y","city":"Syracuse","firstName":"Jessica"}
Contact Resource Class
Contact ResourceShort JSON Representation
Resources@Path("/contacts")publicclassContactResource{
privateDominoAccessoraccessor=newDominoAccessor(ContextInfo.getUserSession()); @GET() publicResponsegetContactList(@QueryParam("start")intstart,@QueryParam("count")intcount){ List<Contact>contactList=accessor.pullContacts(start,count); Stringresult=ModelUtils.toJson(contactList).toString(); returnResponse.ok(result,MediaType.APPLICATION_JSON).build(); } @Path("/{id}") @GET() publicResponsegetContact(@PathParam("id")Stringid){ Contactcontact=accessor.findContact(id);
if(null==contact){ thrownewWebApplicationException(Response.Status.NOT_FOUND); }else{ Stringresult=ModelUtils.toJson(contact).toString(); returnResponse.ok(result,MediaType.APPLICATION_JSON).build(); } }}
The base URI for the resource
In the demo, the root path of the plugin is “/twink”. So this class is enabled for requests made to:
/twink/contacts/*
Resources@Path("/contacts")publicclassContactResource{
privateDominoAccessoraccessor=newDominoAccessor(ContextInfo.getUserSession()); @GET() publicResponsegetContactList(@QueryParam("start")intstart,@QueryParam("count")intcount){ List<Contact>contactList=accessor.pullContacts(start,count); Stringresult=ModelUtils.toJson(contactList).toString(); returnResponse.ok(result,MediaType.APPLICATION_JSON).build(); } @Path("/{id}") @GET() publicResponsegetContact(@PathParam("id")Stringid){ Contactcontact=accessor.findContact(id);
if(null==contact){ thrownewWebApplicationException(Response.Status.NOT_FOUND); }else{ Stringresult=ModelUtils.toJson(contact).toString(); returnResponse.ok(result,MediaType.APPLICATION_JSON).build(); } }}
This method responds to GET requests.
No path defined, so this is the default responder.
Resources@Path("/contacts")publicclassContactResource{
privateDominoAccessoraccessor=newDominoAccessor(ContextInfo.getUserSession()); @GET() publicResponsegetContactList(@QueryParam("start")intstart,@QueryParam("count")intcount){ List<Contact>contactList=accessor.pullContacts(start,count); Stringresult=ModelUtils.toJson(contactList).toString(); returnResponse.ok(result,MediaType.APPLICATION_JSON).build(); } @Path("/{id}") @GET() publicResponsegetContact(@PathParam("id")Stringid){ Contactcontact=accessor.findContact(id);
if(null==contact){ thrownewWebApplicationException(Response.Status.NOT_FOUND); }else{ Stringresult=ModelUtils.toJson(contact).toString(); returnResponse.ok(result,MediaType.APPLICATION_JSON).build(); } }}
This method also responds to GET requests.
But it the request path will be elected based on this format.
Resources@Path("/contacts")publicclassContactResource{
privateDominoAccessoraccessor=newDominoAccessor(ContextInfo.getUserSession()); @GET() publicResponsegetContactList(@QueryParam("start")intstart,@QueryParam("count")intcount){ List<Contact>contactList=accessor.pullContacts(start,count); Stringresult=ModelUtils.toJson(contactList).toString(); returnResponse.ok(result,MediaType.APPLICATION_JSON).build(); } @Path("/{id}") @GET() publicResponsegetContact(@PathParam("id")Stringid){ Contactcontact=accessor.findContact(id);
if(null==contact){ thrownewWebApplicationException(Response.Status.NOT_FOUND); }else{ Stringresult=ModelUtils.toJson(contact).toString(); returnResponse.ok(result,MediaType.APPLICATION_JSON).build(); } }}
Parameters will be injected into methods.
/contacts?start=X&count=Y/contacts/someId
Wink servlet will handle type conversion.
It supports ordinary java objects, enums, primitives, etc.
Resources@Path("/contacts")publicclassContactResource{
privateDominoAccessoraccessor=newDominoAccessor(ContextInfo.getUserSession()); @GET() publicResponsegetContactList(@QueryParam("start")intstart,@QueryParam("count")intcount){ List<Contact>contactList=accessor.pullContacts(start,count); Stringresult=ModelUtils.toJson(contactList).toString(); returnResponse.ok(result,MediaType.APPLICATION_JSON).build(); } @Path("/{id}") @GET() publicResponsegetContact(@PathParam("id")Stringid){ Contactcontact=accessor.findContact(id);
if(null==contact){ thrownewWebApplicationException(Response.Status.NOT_FOUND); }else{ Stringresult=ModelUtils.toJson(contact).toString(); returnResponse.ok(result,MediaType.APPLICATION_JSON).build(); } }}
There are lots of options of returning response.
ResponseBuilders and some other helpers make it quite easy.
Resources@Path("/contacts")publicclassContactResource{
privateDominoAccessoraccessor=newDominoAccessor(ContextInfo.getUserSession()); @GET() publicResponsegetContactList(@QueryParam("start")intstart,@QueryParam("count")intcount){ List<Contact>contactList=accessor.pullContacts(start,count); Stringresult=ModelUtils.toJson(contactList).toString(); returnResponse.ok(result,MediaType.APPLICATION_JSON).build(); } @Path("/{id}") @GET() publicResponsegetContact(@PathParam("id")Stringid){ Contactcontact=accessor.findContact(id);
if(null==contact){ thrownewWebApplicationException(Response.Status.NOT_FOUND); }else{ Stringresult=ModelUtils.toJson(contact).toString(); returnResponse.ok(result,MediaType.APPLICATION_JSON).build(); } }}
Wink handles much of the error handling.
Still you can inject your own errors.
Resources@Path("/contacts")publicclassContactResource{
…………
@POST() @Consumes(MediaType.APPLICATION_JSON) publicResponsepostContactJson(Stringbody){ Contactcontact=ModelUtils.buildContactfromJson(body); accessor.saveNewContact(contact); Stringresult=ModelUtils.toJson(contact).toString(); returnResponse.ok(result,MediaType.APPLICATION_JSON).build(); }
@POST() @Consumes(MediaType.MULTIPART_FORM_DATA) publicResponsepostContactForm(BufferedInMultiPartformData){ Contactcontact=ModelUtils.buildContactfromMultipart(formData); accessor.saveNewContact(contact); Stringresult=ModelUtils.toJson(contact).toString(); returnResponse.ok(result,MediaType.APPLICATION_JSON).build(); }}
This methods respond to POST requests.
This time the selection depends on the incoming data type.
Client marks the request with Content-Type header and Wink will select the appropriate method here.
Resources@Path("/contacts")publicclassContactResource{
…………
@POST() @Consumes(MediaType.APPLICATION_JSON) publicResponsepostContactJson(Stringbody){ Contactcontact=ModelUtils.buildContactfromJson(body); accessor.saveNewContact(contact); Stringresult=ModelUtils.toJson(contact).toString(); returnResponse.ok(result,MediaType.APPLICATION_JSON).build(); }
@POST() @Consumes(MediaType.MULTIPART_FORM_DATA) publicResponsepostContactForm(BufferedInMultiPartformData){ Contactcontact=ModelUtils.buildContactfromMultipart(formData); accessor.saveNewContact(contact); Stringresult=ModelUtils.toJson(contact).toString(); returnResponse.ok(result,MediaType.APPLICATION_JSON).build(); }}
Wink injects the incoming data into the method automatically.
Apache Wink also provides several classes to process different data formats (Multipart, Atom, XML, JSON, etc.)
What is your purpose?Quick and narrow-scoped services
Moving your app to a different web frameworkEnable applications for native mobile access
Create a REST API for your apps
Plan first!Determine resource types and capabilities to be allowed
(Resources, Representations, actions, etc.)
The distribution of tasks(Front-end and Back-end) responsibilities
Collaborate with consumers, if you can
Versioning / Test API
Sketch an architectureKeep your architecture layered
Let your luggage be history
Design as if the consumer will exploit your application, even you!
A sample architecture
RESTful Resources
ResourceResourceResource
SubresourceSubresourceSubresource
Model Classes
Data Objects
Conversion
Resource Representation ←→ Model
Data Access
Model ←→ Documents
Business Logic
Actions (CRUD, etc.) Rules, validations, etc.
Databases
ResourceResourceDocuments
ResourceResourceViews
ResourceResourceetc.
Security Utilities
Getting hands dirty
Test and Development
Local Domino ServerDomino Designer ClientEclipse / XPages SDK / Debug PluginREST testing utility (e.g. Postman)
Plugin Development
Guides / Demos / BlogsConfigure Eclipse
Plugin template for Wink projectAdd Libraries to your project
(See Resources section)
Annotations• @Path
Specifies the relative path for a resource class or method
• @GET, @PUT, @POST, @DELETE, @HEAD Specify the HTTP request type of a resource
• @Produces Specifies the response Internet media types (content negotiation)
• @ConsumesSpecifies the accepted request Internet media types.
Annotations• @PathParam
Binds the method parameter to a path segment
• @QueryParam, @MatrixParam, @FormParamBinds the method parameter to a query/matrix/form parameter
• @HeaderParam, @CookieParamBinds the method parameter to a HTTP header/cookie parameter
• @ContextReturns the entire context of the object@ContextHttpServletRequestrequest
• @DefaultValue Specifies a default value for the above bindings when the key is not found. @Default(“1”)@QueryParam(“start”)intstart
Annotations• @Provider
Providers are used for transformation between entities and representations. Wink comes with several providers and more can be developed for special purposes.
• @AssetMore advanced implementation of providers. Especially suitable for automatic transformation between data objects and representations.
• @ParentDefines a parent resource that has a base URI. (See Versioning)
• @Scope By default, every resource class instantiated per request. Scope can define longer life cycles for resource instances (e.g. singletons).
JSON Handling• Wink and IBM Commons provide JSON Object helpers• A library for JSON processing strongly suggested
• Hardcoding JSON data structure becomes more and more difficult.• Automatic Serialization / Deserialization is life saving
• Tip: Look into Jackson and GSON libraries
Versioning
@Path("/v1")publicclasscom.developi.wink.demo.api.v1.VersionRoot{}
@Parent(com.developi.wink.demo.api.v1.VersionRoot.class)@Path("/ping")publicclasscom.developi.wink.demo.api.v1.PingResource{ @GETpublicResponseping(){ returnResponse.ok("<h1>HelloWorldVersion1!</h1>",MediaType.TEXT_HTML).build(); }}
@Parent(com.developi.wink.demo.api.v2.VersionRoot.class)@Path("/ping")publicclasscom.developi.wink.demo.api.v2.PingResource{ @GETpublicResponseping(){ returnResponse.ok("<h1>HelloWorldVersion2!</h1>",MediaType.TEXT_HTML).build(); }}
@Path("/v2")publicclasscom.developi.wink.demo.api.v2.VersionRoot{}
Responds to “/root/v2/ping”
Responds to “/root/v1/ping”
Notes Session• NotesSession related to the authenticated user:
• ContextInfo.getUserSession()
• At the servlet level,• No SessionAsSigner• No SessionAsSignerWithFullAccess• No CurrentDatabase
• Elevated level of access is a bit tricky.• Refer to DominoRunner XSnippet
OpenNTF Domino API• OpenNTF Domino API is compatible with Apache Wink
• One trick: You need to customize the servlet
• Refer to the blog post by Paul Withers
• Advantages
• No recycle!
• Modern Java practices (Maps, generics, etc.)
• Much better development experience
• Ability to use elevated session
• Refer to the OpenNTF Domino API Project page for more
SummaryRESTful Services Architecture
Designing RESTful services for Domino ApplicationsBasic Concepts around RESTful Services
Architecture ExamplesAnnotations used by Apache WinkSome tricks for Domino developers
TakeawayDownload and play with the template and demo plugins
Experiment JAX-RS annotationsGet yourself familiar with Plugin development
Download Extension Library source code and look its designStudy on RESTful design practices and JAX-RS concepts
Resources• Serdar Başeğmez: Demo Plugin and Apache Wink Template
https://github.com/sbasegmez/RestAssuredDemo
• Apache Wink Projecthttps://wink.apache.org/
• Paul Withers: From XPages Hero To OSGi Guru: Taking The Scary Out Of Building Extension Librarieshttp://www.slideshare.net/paulswithers1/ibm-connected-2015-mas103-xpages-performance-and-scalability
• Paul Withers: XPages OSGi Plugins serieshttp://www.intec.co.uk/xpages-osgi-plugins-1-an-introduction/
• John Cooper: Domino OSGI (Part 1) - Configuring Eclipse for XPages OSGI Pluginshttp://developmentblog.johnmcooper.co.uk/2014/05/configuring-eclipse-for-xpages-osgi-plugins-part1.html
• John Dalsgaard: Wrap An Existing Jar File Into A Plug-in https://www.dalsgaard-data.eu/blog/wrap-an-existing-jar-file-into-a-plug-in/
• Toby Samples: JAX-RS or THE way to do REST in Domino serieshttps://tobysamples.wordpress.com/2015/04/28/jax-rs-or-the-way-to-do-rest-in-domino-part-1/
• Jesse Gallagher: Eclipse Tutorial for Domino Developershttps://github.com/jesse-gallagher/eclipse-tutorial-oct2015/wiki/Java