53
REST Assured, Freeing Your Domino Data Has Never Been That Easy! Serdar Basegmez, Developi Information Systems 16th September 2016

ICONUK 2016: REST Assured, Freeing Your Domino Data Has Never Been That Easy!

Embed Size (px)

Citation preview

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

History

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…

RESTful Servicesfor

IBM Domino Applications

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/*

GET /twink/contacts/DLEY-ACJS7HGET /twink/contacts?start=10000&count=10

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

How to Start

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

Further Details

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

Wrap-up

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