Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Collabsphere 2021: Six Polite Ways to Design a ...

sbasegmez
October 21, 2021

Collabsphere 2021: Six Polite Ways to Design a RESTful API for Your Application!

Dev112 from #CollabSphere2021

"With Domino v12, HCL is transforming its offering 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, attend 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. Along with the updated content, we will also present the basic concepts and best practices, allowing you to walk away with tips and tricks on performance, scalability and security."

sbasegmez

October 21, 2021
Tweet

More Decks by sbasegmez

Other Decks in Programming

Transcript

  1. Six Polite Way s to Design a RESTful AP I

    for your Domino Application! Serdar Basegmez Developi Informa ti on Systems @serdar_basegmez
  2. About… Serdar Basegmez •Developi Information Systems, London •Notes/Domino developer, half-blooded

    admin, errand-boy… •OpenNTF Board, HCL Ambassador, IBM Champion Alumni •Blog: LotusNotus.com / Twitter: @serdar_basegmez •Also tweets/blogs/speaks/podcasts on scientific skepticism 2
  3. Agenda •Why should you care? •RESTful APIs •Practical Implications of

    RESTful Architectures •Designing an API •Ways to Provide REST Services for HCL Domino •Tools •Wrap-up 4
  4. Why Should You Care? •New Front-ends and Enhanced UX –Facelifting,

    but keeping data and business logic in NSF. –JS Frameworks (e.g. Angular, React), Mobile Apps, etc. –Richer experiences with chatbots, AI, etc. •Integration for third party sites/applications –IT becomes a huge jungle, we can’t walk alone! –Financial Systems, AI Systems, CRM, S/M Automation –Collaborative Apps, Office 365 •Automating processes using APIs –Domino Apps not independent from Business Processes –Accounting/Sales/Marketing/ERP Processes 5 User Experience Business Processes Integration
  5. RESTful Web Services Representational state transfer (REST) is a software

    architectural style that was created to guide the design and development of the architecture for the World Wide Web. REST defines a set of constraints for how the architecture of an Internet-scale distributed hypermedia system, such as the Web, should behave. The REST architectural style emphasises the scalability of interactions between components, uniform interfaces, independent deployment of components, and the creation of a layered architecture to facilitate caching components to reduce user-perceived latency, enforce security, and encapsulate legacy systems… 6 Source: https://en.wikipedia.org/wiki/Representational_state_transfer
  6. The simplicity: Data conventions! 7 GET /twink/contacts/DLEY-ACLH6Y HTTP/1.1 Host: homer.developi.info

    Cache-Control: no-cache { "zip": "13202", "state": "NY", "lastName": "Abbate", "middle": "J", "country": "US", "emailAddress": "[email protected]", "number": "DLEY-ACLH6Y", "city": "Syracuse", "firstName": "Jessica" }
  7. URL Conventions: Verb + Resource •GET http://appserver.company.com/api/contacts •GET http://appserver.company.com/api/contacts/UK/London •GET

    http://appserver.company.com/api/contacts/1522 •POST http://appserver.company.com/api/contacts •PUT http://appserver.company.com/api/contacts/1522 •DELETE http://appserver.company.com/api/contacts/1522 •Simple CRUD actions for Contact resource… 9
  8. Stateless / Cacheable / Layered 10 Every request processed independentl

    y Everything cacheabl e Client does not care who cooked the meal in the kitche n ⇣ Scalable, Robust, Resilient
  9. Back-end User Interface Business Logic Datastore Mobile Applications Front-end External

    Apps Microservice Microservice Microservice Integration Architectures Evolving! 11 User Interface Business Logic Datastore Front-end Back-end
  10. RESTful, Everywhere! •Solid Architecture •Well-defined practices •Widespread use •Intuitive Design

    •Easily consumable •Scalable 12 Source: https://openclipart.org/detail/221722/cloud-network
  11. Example: Collaboration Today API •Collaboration Today API –Alternative front-end –Integrate

    to different consumers –Keep the data in NSF –Reuse current implementation •Three Sections: –Presentation (Reading News) –Curation (Adding/Publishing News) –RSS Feeding (DOTS FeedMonster) 14
  12. CollaborationToday API 15 Recent Stories Stories By Author Story fi

    elds needed List Tags/Categories Stories By Tag
  13. Designing RESTful API •Contract Design –URLs (Resources + Verbs) –Data

    formats –Rules, Success and Error Responses •Tip: –Start with the end product. –Write a documentation first, before coding. –No “correct way”, only “best practices” –Follow Conventions 18 * Icons made by Flat Icons from www. fl aticon.com
  14. Designing RESTful API •Security Design –Authentication / Authorization –Administration Lifecycle

    –Application Security •Tips –The scope of the implementation is the key. –Managing security is often overlooked. –Security starts with the design phase. 19 * Icons made by Flat Icons from www. fl aticon.com
  15. Designing RESTful API •Versioning –Enumeration, Lifecycle •Documentation –OpenAPI Specs (a.k.a.

    Swagger) –Generate Server stubs, client libraries –Interactive documentation •Testing –Automated testing 20 * Icons made by Flat Icons from www. fl aticon.com /v1/api/xyz /v2/api/xyz /api1/xyz /api2/xyz
  16. 6 Polite Ways of RESTful Domino 1. HCL Domino Keep

    (a.k.a HCL Domino REST API) 2. HCL Domino AppDev Pack (Node.js) 3. XPages Extension Library Components 4. Hardcoded Services (Web Agents, XAgents, Servlets) 5. JAX-RS (OSGi Plugin, Jakarta, etc.) 6. Hybrid approaches 22
  17. Domino Keep (HCL Domino REST API) •Provides REST API access

    for Domino servers and databases •Broader approach for RESTful access to Domino •Currently on Early Access –GA on Domino 12.0.1 •Makes Domino Access Services obsolete. 24
  18. Domino Keep (HCL Domino REST API) •Access definitions for Domino

    apps –Form/view/agent access –DQL Support –Tweaking what to expose –Additional security •Design access •PIM access •OData access (Salesforce, Excel, SAP, etc.) •Server administration 25
  19. Domino Keep - Capabilities •Security –Authentication with JWT / External

    IdP / Domino login –Opt-in security: Access denied by default •Agent Processing –Run or Queue agents by API –Provide parameters / document context •Extensibility 26
  20. Domino Keep Architecture •Keep task •EventBus to worker verticles •New

    verticles can be added •DominoJNX –Brand new Java API for Domino 27 Source: https://opensource.hcltechsw.com/domino-keep-docs/
  21. Domino Keep: Summary – Exciting Project – Effective replacement for

    Domino Access Services – Go to https://opensource.hcltechsw.com/domino-keep-docs – Start using today and provide feedback! – What not to expect: – Mostly Data, not the business logic – Not production ready yet, getting there 28
  22. HCL Domino AppDev Pack •DQL + Domino AppDev Pack: –Domino

    Query Language –AppDev Pack: Proton Task / domino-db.js / com.hcl.domino.db •Countless opportunities in the most popular framework •Different possibilities for the architecture •Wide range of options for tooling 30 https://insights.stackoverflow.com/survey/2019
  23. Node.js Support for Domino Apps 31 HCL Domino Server Proton

    Node.js Layer domino-db.js GRPC Application ……… Application REST New Add-o n Domino v10 Routers for the RESTful service Might be on the same box or not MyRoutes.js NSF
  24. Node.js Support for Domino Apps 32 Node.js Layer domino-db.js Routers

    for the RESTful service MyRoutes.js https://github.com/sbasegmez/Engage19Demo/
  25. DQL: Domino Query Language •Available since Domino V10 •Optimized query

    access to NSF data •Server extracts and optimizes data for DQL access •Evolving fast! 33 John Curtis, “Factory Tour DQL Performance Explanation”, https://jdcurtis.blog/2019/07/17/factory-tour-dql-performance-explanation/
  26. IAM: Identity and Access Management 34 * IAM Setup documentation

    - https://doc.cwpcollaboration.com/appdevpack/docs/en/iam_setup.html
  27. Bottom line… •Domino Keep vs AppDev Pack •GRPC vs HTTP

    •Middlewares •Language and framework preference •Bringing new developers •Code to market speed 35
  28. REST Components (ExtLib) •Provided and Supported by HCL •Customizable component

    version of the DAS –Computed/Filtered columns, Custom search, etc. –Event model helps building business logic on top of REST model •Custom REST Service –Write your own SSJS or Java bean •Write your CSJS routines for async access –Remote Service / JSON-RPC –Dojo support for single page model 37
  29. REST Components (ExtLib) •Setup REST component(s) on your page. •Minimal

    coding, no administrator needed. 38 Add to your XPage Add a Service Con fi gure Options
  30. REST Components (ExtLib) •Drawbacks: –Easy to slip into a spagetti

    code! –Not optimized for performance and scalability –Difficult to follow RESTful URL Convention 39 https://someserver.domain.com/database.nsf/somepage.xsp/service/…
  31. Hardcoding (Web agents, XAgents, Servlets…) •Old school way; still quite

    useful for some cases. •Great if you have pre-existing code (e.g. Lotusscript libraries, etc.) •Also, useful to prototype quick ideas. •Customizable, flexible and simple way to create any service. 41
  32. Hardcoding (Web agents, XAgents, Servlets…) •Drawbacks: –Hardcode everything… •e.g. Header/parameter

    extraction –Very easy to slip into a spagetti code! •Error handling / proper testing needed –Difficult to follow RESTful URL Convention 42 https://someserver.domain.com/database.nsf/xagent.xsp?… https://someserver.domain.com/database.nsf/someagent?OpenAgent&…
  33. Java (JAX-RS) way to RESTful Services •JAX-RS: ‘Java-ish’ way to

    define RESTful services –Create JAX-RS based REST services on top of Java classes. –Complete Java solution, extensible with custom providers •Options –Apache Wink 1.1.2 (DAS uses!) –Jakarta EE (Official Java) –RestEasy, Apache CXF, Jersey, etc. •Code reusability outside HCL Domino world. 44
  34. JAX-RS Architecture 45 JAX-RS Runtime Application Code Servle t (Customizable)

    HTTP/HTTPS Client Datastore(s) Resource Resource Resource Resource Controller s
  35. JAX-RS Development 46 @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": "[email protected]", "number": "DLEY-ACLH6Y", "city": "Syracuse", "firstName": "Jessica" } Contact Resource Class Contact Resourc e Short JSON Representation
  36. @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(); } } } JAX-RS Development 47 The base URI for the resourc e In the demo, the root path of the plugin is “/twink”. So this class is enabled for requests made to : /twink/contacts/*
  37. @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(); } } } JAX-RS Development 48 This method responds to GET requests . No path de fi ned, so this is the default responder.
  38. @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(); } } } JAX-RS Development 49 This method also responds to GET requests . But it the request path will be elected based on this format.
  39. @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(); } } } JAX-RS Development 50 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.
  40. @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(); } } } JAX-RS Development 51 There are lots of options of returning response . ResponseBuilders and some other helpers make it quite easy.
  41. @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(); } } } JAX-RS Development 52 Error handling is handled by the JAX-RS engine as well . You can inject your own errors.
  42. @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(); } } JAX-RS Development 53 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.
  43. @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(); } } JAX-RS Development 54 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.)
  44. JAX-RS Inside / Outside 55 •Develop an OSGi Plugin for

    Domino –Apache Wink, JakartaEE, etc. –Modern development tools –For large scale implementations •Use Jakarta EE to use JAX-RS inside NSF •Develop J2EE outside of Domino
  45. JAX-RS as OSGi Plugin 56 JAX-RS Runtime Application Code Servle

    t (Customizable) HTTP/HTTPS Client Datastore(s) Resource Resource Resource Resource Controller s
  46. JAX-RS Inside NSF 57 JAX-RS Runtime Application Code Servle t

    (Customizable) HTTP/HTTPS Client Datastore(s) Resource Resource Resource Resource Controller s
  47. JAX-RS inside NSF •Initial work: Martin Pradny •Included in Jakarta

    EE by Jesse Gallagher •JAX-RS 2.1 support (Apache Wink uses 1.1) •No need to deal with OSGi •Directly create annotated classes inside NSF •OpenAPI specs annotations supported! 58 https://www.pradny.com/2019/05/using-jax-rs-inside-nsf-follow-up.html
  48. Alternative: Integrate Domino into Open Liberty •Implementation: Jesse Gallagher –Inspired

    by Sven Hasselbach •Run Java EE server –e.g. Open Liberty •Run Domino HTTP Stack side by side •Deploy Java EE projects connecting to Domino data natively. •HTTP/2, Servlet 4, Websockets, etc. •High performance, scalability 59 https://frostillic.us/blog/posts/2019/1/3/be9501a07279b40d85258377007b0dfd
  49. JAX-RS Methods •Plugin-path might be the longest one… –Difficult, if

    you are not familiar –Overkill? •Not suitable for small projects and simple needs –Tool selection is critical. •Apache Wink is old school •Integrating alternatives might be difficult •Consider alternatives carefully. 60
  50. On Apache Wink… h tt ps://speakerdeck.com/sbasegmez/iconuk-2016-rest-assured... On Jakarta EE… (by

    Jesse Gallagher) h tt ps://github.com/OpenNTF/org.openn tf .xsp.jakartaee For More Detail on JAX-RS… 61
  51. Hybrid Approaches •Wrap services using a suitable middleware –Express, Node-RED,

    Spring etc. •Wrap missing parts from existing assets •Add some magic –Security, caching, misc. services •Combine best of all worlds •Right tools for the right job •Consistency across the solution •Optimised Implementation 63 Domino Server Node.js ExtLib REST Service JAX-RS Service Domino Keep domino-db.js JavaScript Services Wrapped Services Data / Application Layer Non-Domino Services API Clients API Clients API Clients Proton
  52. Tooling - OpenAPI •OpenAPI Specifications / Documentation –Swagger Tools -

    swagger.io –Stoplight Studio - stoplight.io –Apicurio - apicur.io 65
  53. Resources • Serdar Başeğmez: Node.js demo for this session 


    https://github.com/sbasegmez/Engage19Demo • Tom Van Aken: Rest calls and JSON Parsing in LotusScript 
 https://vanakentom.wordpress.com/2019/01/15/rest-calls-and-json-parsing-in-lotusscript/ • Serdar Başeğmez: Demo for IBM Connect 2017 session 
 https://github.com/sbasegmez/IC17RestDemo • Serdar Başeğmez: Apache Wink Template and Demo for Icon UK 2016 
 https://github.com/sbasegmez/RestAssuredDemo • Graham Acres / Serdar Başeğmez: The Journey to Becoming a Social Application Developer (IBM Connect 2014) 
 https://speakerdeck.com/sbasegmez/bp308-the-journey-to-becoming-a-social-application-developer • Stephan H. Wissel: Custom REST service in XPages using a service bean 
 https://wissel.net/blog/d6plinks/SHWL-9Q55QL 69
  54. Resources (cont.) •Eric McCormick: Series on JSON Data with Java

    in XPages 
 https://edm00se.io/json-with-java-in-xpages •Thomas Ladehoff: REST Services with the XPages Extension Library 
 https://www.assono.de/blog/d6plinks/REST-Services-with-the-XPages-Extension-Library •Paul Withers: XPages OSGi Plugins series 
 http://www.intec.co.uk/xpages-osgi-plugins-1-an-introduction/ •John Cooper: Domino OSGI (Part 1) - Configuring Eclipse for XPages OSGI Plugins 
 http://developmentblog.johnmcooper.co.uk/2014/05/configuring-eclipse-for-xpages-osgi-plugins-part1.html •Toby Samples: JAX-RS or THE way to do REST in Domino series 
 https://tobysamples.wordpress.com/2015/04/28/jax-rs-or-the-way-to-do-rest-in-domino-part-1/ •Jesse Gallagher: Eclipse Tutorial for Domino Developers 
 https://github.com/jesse-gallagher/eclipse-tutorial-oct2015/wiki/Java 70
  55. Resources (cont.) •Ulrich Krause: NotesJsonNavigator, NotesJsonElement, NotesJsonArray, NotesJsonObject example 


    https://www.eknori.de/2019-01-01/notesjsonnavigator-notesjsonelement-notesjsonarray-notesjsonobject-example/ •John Dalsgaard: REST Services in IBM Domino/XWork 
 https://www.dalsgaard-data.eu/blog/rest-services-in-ibm-dominoxwork •Oliver Busse: First dive into Domino and Node.js 
 http://oliverbusse.notesx.net/hp.nsf/blogpost.xsp?documentId=2E52 •Oliver Busse: Domino, Proton, IAM, OAuth (Series) 
 http://oliverbusse.notesx.net/hp.nsf/blogpost.xsp?documentId=2FEA •Sven Hasselbach: node.js, domino-db & Docker 
 http://hasselba.ch/blog/?p=2670 •Jesse Gallagher: That Java Thing, Part 1: The Java Problem in the Community 
 https://frostillic.us/blog/thread.xsp?thread=That+Java+Thing 71
  56. Resources (cont.) •Graham Acres / Heiko Voigt: Engage 2019 Demo

    of Domino v10, Proton and Node.js session 
 https://github.com/c3ug/engage2019democode •Troy Reimer: OpenNTF Project: LotusScript implementation of a reader and writer for JSON 
 https://openntf.org/main.nsf/project.xsp?r=project/JSON%20LotusScript%20Classes •Bansilal Haudakari: Effective API Design 
 https://www.slideshare.net/BansilalHaudakari/effective-api-design •Heiko Voigt: How to set up the Domino 10 OAUTH2 Provider - IAM Basics 
 https://www.harbour-light.org/2019/02/18/how-to-set-up-the-domino-10-oauth2-provider-iam-basics/ •Martin Pradny: Using JAX-RS inside NSF follow-up - Swagger UI 
 https://www.pradny.com/2019/05/using-jax-rs-inside-nsf-follow-up.html •Cross Canada Collaboration User Group: John Curtis - Domino Query Language with John Curtis Part 1 
 https://www.youtube.com/watch?v=qm_LJ9-6OTE 72
  57. Resources (cont.) •Cross Canada Collaboration User Group: Heiko Voight -

    Demos from the C3UG AppDevPack Roundtable, March 29, 2021 
 https://www.youtube.com/watch?v=M0Cm7oZakKU&t=950s •Cross Canada Collaboration User Group: Education 
 http://www.c3ug.ca/education •HCL: HCL Domino Keep Documentation 
 https://opensource.hcltechsw.com/domino-keep-docs/ •Stephan Wissel / Paul Withers: July OpenNTF Webinar - HCL Presents Keep, a new API for Domino 
 https://www.youtube.com/watch?v=6CydwW7V5MA 73