Engage 2020 session
"Six Polite Ways to Design a RESTful API for Your Application!"
"With Domino v10 and v11, HCL is delivering on a vision to provide many new ways to architect our applications. One of the key technologies is creating RESTful APIs for our data and processes. APIs are very powerful in allowing us to build integrations between Domino and all other enterprise applications. Regardless of your experience, come to this session to see what options are available to you, what pitfalls you may experience and how to break down the borders between your applications and others. We will present the basic concepts and best practices, allowing you to walk away with tips and tricks on performance, scalability and security."
Serdar Basegmez
Developi Information Systems, London
4. #engageug
Why Should You Care?
• New Front-ends and Enhanced UX
• Faceli`ing, but keeping data and business logic in NSF.
• JS Frameworks (e.g. Angular, React), Mobile Apps, etc.
• Richer experiences with chatbots, AI, etc.
• IntegraGon for third party sites/applicaGons
• IT becomes a huge jungle, we can’t walk alone!
• Financial Systems, AI Systems, CRM, S/M AutomaGon
• CollaboraGve Apps, Office 365
• AutomaGng processes using APIs
• Domino Apps not independent from Business Processes
• AccounGng/Sales/MarkeGng/ERP Processes
User Experience
Business Processes
Integration
26. #engageug
Designing RESTful API
• Contract Design
• URLs (Resources + Verbs)
• Data formats
• Rules, Success and Error Responses
• Tip:
• Start with the end product.
• Write a documentaGon first, before coding.
• No “correct way”, only “best pracGces”
• Follow ConvenGons
* Icons made by Flat Icons from www.flaticon.com
41. #engageug
@Path("/contacts")
public class ContactResource {
private DominoAccessor accessor = new DominoAccessor(ContextInfo.getUserSession());
@GET()
public Response getContactList( @QueryParam("start") int start, @QueryParam("count") int count) {
List<Contact> contactList = accessor.pullContacts(start, count);
String result = ModelUtils.toJson(contactList).toString();
return Response.ok(result, MediaType.APPLICATION_JSON).build();
}
@Path("/{id}")
@GET()
public Response getContact(@PathParam("id") String id) {
Contact contact = accessor.findContact(id);
if(null == contact) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
} else {
String result = ModelUtils.toJson(contact).toString();
return Response.ok(result, MediaType.APPLICATION_JSON).build();
}
}
}
{
"zip": "13202",
"state": "NY",
"lastName": "Abbate",
"middle": "J",
"country": "US",
"emailAddress": "Jessica.J.Abbate@trashymail.com",
"number": "DLEY-ACLH6Y",
"city": "Syracuse",
"firstName": "Jessica"
}
Contact Resource Class
Contact Resource
Short JSON Representation
42. #engageug
@Path("/contacts")
public class ContactResource {
private DominoAccessor accessor = new DominoAccessor(ContextInfo.getUserSession());
@GET()
public Response getContactList( @QueryParam("start") int start, @QueryParam("count") int count) {
List<Contact> contactList = accessor.pullContacts(start, count);
String result = ModelUtils.toJson(contactList).toString();
return Response.ok(result, MediaType.APPLICATION_JSON).build();
}
@Path("/{id}")
@GET()
public Response getContact(@PathParam("id") String id) {
Contact contact = accessor.findContact(id);
if(null == contact) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
} else {
String result = ModelUtils.toJson(contact).toString();
return Response.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/*
43. #engageug
@Path("/contacts")
public class ContactResource {
private DominoAccessor accessor = new DominoAccessor(ContextInfo.getUserSession());
@GET()
public Response getContactList( @QueryParam("start") int start, @QueryParam("count") int count) {
List<Contact> contactList = accessor.pullContacts(start, count);
String result = ModelUtils.toJson(contactList).toString();
return Response.ok(result, MediaType.APPLICATION_JSON).build();
}
@Path("/{id}")
@GET()
public Response getContact(@PathParam("id") String id) {
Contact contact = accessor.findContact(id);
if(null == contact) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
} else {
String result = ModelUtils.toJson(contact).toString();
return Response.ok(result, MediaType.APPLICATION_JSON).build();
}
}
}
This method responds to
GET requests.
No path defined, so this is
the default responder.
44. #engageug
@Path("/contacts")
public class ContactResource {
private DominoAccessor accessor = new DominoAccessor(ContextInfo.getUserSession());
@GET()
public Response getContactList( @QueryParam("start") int start, @QueryParam("count") int count) {
List<Contact> contactList = accessor.pullContacts(start, count);
String result = ModelUtils.toJson(contactList).toString();
return Response.ok(result, MediaType.APPLICATION_JSON).build();
}
@Path("/{id}")
@GET()
public Response getContact(@PathParam("id") String id) {
Contact contact = accessor.findContact(id);
if(null == contact) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
} else {
String result = ModelUtils.toJson(contact).toString();
return Response.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.
45. #engageug
@Path("/contacts")
public class ContactResource {
private DominoAccessor accessor = new DominoAccessor(ContextInfo.getUserSession());
@GET()
public Response getContactList( @QueryParam("start") int start, @QueryParam("count") int count) {
List<Contact> contactList = accessor.pullContacts(start, count);
String result = ModelUtils.toJson(contactList).toString();
return Response.ok(result, MediaType.APPLICATION_JSON).build();
}
@Path("/{id}")
@GET()
public Response getContact(@PathParam("id") String id) {
Contact contact = accessor.findContact(id);
if(null == contact) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
} else {
String result = ModelUtils.toJson(contact).toString();
return Response.ok(result, MediaType.APPLICATION_JSON).build();
}
}
}
Parameters will be injected
into methods.
/contacts?start=X&count=Y
/contacts/someId
JAX-RS servlet will handle
type conversion.
It supports ordinary java
objects, enums, primitives,
etc.
46. #engageug
@Path("/contacts")
public class ContactResource {
private DominoAccessor accessor = new DominoAccessor(ContextInfo.getUserSession());
@GET()
public Response getContactList( @QueryParam("start") int start, @QueryParam("count") int count) {
List<Contact> contactList = accessor.pullContacts(start, count);
String result = ModelUtils.toJson(contactList).toString();
return Response.ok(result, MediaType.APPLICATION_JSON).build();
}
@Path("/{id}")
@GET()
public Response getContact(@PathParam("id") String id) {
Contact contact = accessor.findContact(id);
if(null == contact) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
} else {
String result = ModelUtils.toJson(contact).toString();
return Response.ok(result, MediaType.APPLICATION_JSON).build();
}
}
}
There are lots of options of
returning response.
ResponseBuilders and some
other helpers make it quite
easy.
47. #engageug
@Path("/contacts")
public class ContactResource {
private DominoAccessor accessor = new DominoAccessor(ContextInfo.getUserSession());
@GET()
public Response getContactList( @QueryParam("start") int start, @QueryParam("count") int count) {
List<Contact> contactList = accessor.pullContacts(start, count);
String result = ModelUtils.toJson(contactList).toString();
return Response.ok(result, MediaType.APPLICATION_JSON).build();
}
@Path("/{id}")
@GET()
public Response getContact(@PathParam("id") String id) {
Contact contact = accessor.findContact(id);
if(null == contact) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
} else {
String result = ModelUtils.toJson(contact).toString();
return Response.ok(result, MediaType.APPLICATION_JSON).build();
}
}
}
Error handling is handled by the
JAX-RS engine as well.
You can inject your own errors.
48. #engageug
@Path("/contacts")
public class ContactResource {
…………
@POST()
@Consumes(MediaType.APPLICATION_JSON)
public Response postContactJson(String body) {
Contact contact = ModelUtils.buildContactfromJson(body);
accessor.saveNewContact(contact);
String result = ModelUtils.toJson(contact).toString();
return Response.ok(result, MediaType.APPLICATION_JSON).build();
}
@POST()
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response postContactForm(BufferedInMultiPart formData) {
Contact contact = ModelUtils.buildContactfromMultipart(formData);
accessor.saveNewContact(contact);
String result = ModelUtils.toJson(contact).toString();
return Response.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
Request processor will select
the appropriate method here.
49. #engageug
@Path("/contacts")
public class ContactResource {
…………
@POST()
@Consumes(MediaType.APPLICATION_JSON)
public Response postContactJson(String body) {
Contact contact = ModelUtils.buildContactfromJson(body);
accessor.saveNewContact(contact);
String result = ModelUtils.toJson(contact).toString();
return Response.ok(result, MediaType.APPLICATION_JSON).build();
}
@POST()
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response postContactForm(BufferedInMultiPart formData) {
Contact contact = ModelUtils.buildContactfromMultipart(formData);
accessor.saveNewContact(contact);
String result = ModelUtils.toJson(contact).toString();
return Response.ok(result, MediaType.APPLICATION_JSON).build();
}
}
Wink injects the incoming
data into the method
automatically.
JAX-RS libraries provide
their own implementation
to process different data
formats (Multipart, Atom,
XML, JSON, etc.)
51. #engageug
JAX-RS Methods
• Drawbacks:
• Plugin only
• Difficult if you are not familiar, Takes Gme to learn
• Overkill?
• Not suitable for small projects and simple needs
• Tool selecGon is criGcal.
• Apache Wink is old school
• IntegraGng alternaGves might be difficult
52. #engageug
Node.js
• DQL + Domino AppDev Pack:
• Domino Query Language
• AppDev Pack: Proton Task / domino-db.js
• Countless opportuniGes in the most popular framework
• Different possibiliGes for the architecture
• Wide range of opGons for tooling
https://insights.stackoverflow.com/survey/2019
61. #engageug
Hybrid Approaches
• Wrap services using a suitable front-end
• e.g. Express, Node-RED, etc.
• Add some magic
• Extra processing, computaGon, security, etc.
• Wrap missing parts from exisGng assets
• Combine best of all worlds
• Consistency across the soluGon
• OpGmized ImplementaGon
Internal
Domino Server
Node.js
JAX-RS Service
ExtLib REST
Service
DAS - Data API
Proton
domino-db.js
JavaScript
Services
Wrapped
Services
Data / Application Layer
Non-Domino
Services
API Clients
API ClientsAPI Clients