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

DNUG 2020: Six Polite Ways to Design a RESTful ...

sbasegmez
October 21, 2020

DNUG 2020: Six Polite Ways to Design a RESTful API for Your Application!

Development 2 Track for #dnug47online

"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, 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. We will present the basic concepts and best practices, allowing you to walk away with tips and tricks on performance, scalability and security."

sbasegmez

October 21, 2020
Tweet

More Decks by sbasegmez

Other Decks in Programming

Transcript

  1. Six Polite Ways to Design a RESTful API for your

    Domino Application! Serdar Basegmez Developi Informa/on Systems @serdar_basegmez
  2. • Developi Information Systems, London • Notes/Domino/XPages/Java Developer, half-blooded admin!

    • Member Director at OpenNTF Board • HCL Ambassador (2020) • IBM Champion Alumni (2011 - 2018) • Blog: LotusNotus.com / Twitter: @serdar_basegmez • Also blogging/speaking/podcasting on scientific skepticism / critical thinking Serdar Basegmez 2
  3. • 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 3 Agenda
  4. • 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 4 Why Should You Care? User Experience Business Processes Integration
  5. 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. 5 Source: https://en.wikipedia.org/wiki/Representational_state_transfer
  6. 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, JavaScript Forms → Old School Web Applications 7
  7. 8 User Interface Business Logic Datastore Front-end Back-end Async web

    apps, Ruby on Rails, Django, JSF, XPages, etc. ← HTML, CSS, JavaScript Forms, AJAX → Web Applications Evolving
  8. 9 User Interface Business Logic Datastore Front-end Back-end Modern Web

    frameworks, Angular.js, React.js, etc. ← HTML, CSS, JavaScript ← REST → Web Applications Evolving
  9. 10 User Interface Business Logic Datastore Mobile Applications Back-end Modern

    Web frameworks, Angular.js, React.js, etc. ← HTML, CSS, JavaScript ← REST → Front-end [Web] Applications Evolving
  10. 11 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 [Web] Applications Evolving
  11. 12 Back-end Modern Web frameworks, Angular.js, React.js, etc. ← HTML,

    CSS, JavaScript ← REST → User Interface Business Logic Datastore Mobile Applications Front-end External Apps Microservice Microservice Microservice Integration [Web] Applications Evolving
  12. The Conversation Makes Sense! 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" }
  13. • 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) 20 Example: Collaboration Today API
  14. • 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 24 Designing RESTful API * Icons made by Flat Icons from www.flaticon.com
  15. • 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. 25 Designing RESTful API * Icons made by Flat Icons from www.flaticon.com
  16. • Versioning – Enumeration, Lifecycle • Documentation – OpenAPI Specs

    (a.k.a. Swagger) – Generate Server stubs, client libraries – Interactive documentation • Testing – Automated testing 26 Designing RESTful API * Icons made by Flat Icons from www.flaticon.com /v1/api/xyz /v2/api/xyz /api1/xyz /api2/xyz
  17. 1. Domino Access Services 2. Using Extension Library Components 3.

    Hardcoding Services (Web Agents, XAgents, Servlets) 4. OSGi Plugins to implement JAX-RS 5. Node.js Applications with App.Dev.Pack 6. Unconventional implementations – SmartNSF, JAX-RS inside NSF, JAX-RS outside Domino – Hybrid Methods 28 6 Polite Ways of RESTful Domino
  18. Domino Access Services 29 HCL Supported Fully Func8onal REST API

    Access to Data / Calendar / Mail* / Freebusy* / Directory** * Mail and Freebusy access is part of the core product since 9.0.1FP10 / ** Directory access is enabled with the ExtLib version of OpenNTF Caching / Na8ve Security (ACL, Reader/Author fields, etc.) Server-level / Database-level / Design-level control Enable on Server Enable For Database Enable For Views
  19. – Drawbacks – Weak control over the data! • No

    checkpoints on CRUD, No coding involved… – No place for business logic! – Exposes internals – Not optimized for some use cases – Trust is the key! 31 Domino Access Services
  20. • 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 32 REST Components (ExtLib)
  21. • Setup REST component(s) on your page. • Minimal coding,

    no administrator needed. 33 REST Components (ExtLib) Add to your XPage Add a Service Configure Options
  22. • Drawbacks: – Easy to slip into a spagetti code!

    – Not optimized for performance and scalability – Difficult to follow RESTful URL Convention 34 REST Components (ExtLib) https://someserver.domain.com/database.nsf/somepage.xsp/service/…
  23. • 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. 35 Hardcoding (Web agents, XAgents, Servlets…)
  24. • 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 36 Hardcoding (Web agents, XAgents, Servlets…) https://someserver.domain.com/database.nsf/xagent.xsp?… https://someserver.domain.com/database.nsf/someagent?OpenAgent&…
  25. • JAX-RS: ‘Java-ish’ way to define RESTful services – Create

    JAX-RS based REST services on top of OSGi plugins. – 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. 37 Java (JAX-RS) with OSGi Plugins
  26. 38 JAX-RS Architecture JAX-RS Runtime Application Code Servlet (Customizable) HTTP/HTTPS

    Client Datastore(s) Resource Resource Resource Resource Controllers Data Accessors Tools/Utilities Request/Response Wrappers Context Helpers /BaseURI/* /BaseURI/Path-Patterns
  27. 39 JAX-RS Development @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 Resource Short JSON Representation
  28. @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(); } } } 40 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/* JAX-RS Development
  29. @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(); } } } 41 This method responds to GET requests. No path defined, so this is the default responder. JAX-RS Development
  30. @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(); } } } 42 This method also responds to GET requests. But it the request path will be elected based on this format. JAX-RS Development
  31. @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(); } } } 43 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. JAX-RS Development
  32. @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(); } } } 44 There are lots of options of returning response. ResponseBuilders and some other helpers make it quite easy. JAX-RS Development
  33. @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(); } } } 45 Error handling is handled by the JAX-RS engine as well. You can inject your own errors. JAX-RS Development
  34. @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(); } } 46 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. JAX-RS Development
  35. @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(); } } 47 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.) JAX-RS Development
  36. 48 On Apache Wink… h.ps://speakerdeck.com/sbasegmez/iconuk-2016-rest-assured... On Jakarta EE… (by Jesse

    Gallagher) h.ps://github.com/OpenNTF/org.opennC.xsp.jakartaee For More Detail on JAX-RS…
  37. • Drawbacks: – Plugin only • Difficult if you are

    not familiar, Takes time to learn – Overkill? • Not suitable for small projects and simple needs – Tool selection is critical. • Apache Wink is old school • Integrating alternatives might be difficult 49 JAX-RS Methods
  38. • DQL + Domino AppDev Pack: – Domino Query Language

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

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

    for the RESTful service MyRoutes.js https://github.com/sbasegmez/Engage19Demo/
  41. • Available since Domino V10 • Optimized query access to

    NSF data • Server extracts and optimizes data for DQL access • Evolving fast! • Watch for next sessions! 53 DQL: Domino Query Language John Curtis, “Factory Tour DQL Performance Explanation”, https://jdcurtis.blog/2019/07/17/factory-tour-dql-performance-explanation/
  42. 54 IAM: Identity and Access Management * IAM Setup documentation

    - https://doc.cwpcollaboration.com/appdevpack/docs/en/iam_setup.html
  43. • Unconventional implementations • Why? – Specific needs – Matching

    skills – Prefer solutions with less effort • Why not? – No support – Version lock-in – Expertise needed 55 Other Implementations
  44. SmartNSF • OpenNTF Plugin – Beta-7 version (2018). • Developers:

    – Christian Güdemann – Martin Jinoch • Define your REST Services from DDE! – Uses Groovy • Auto-generate OpenAPI definitions 56 Looking for Contributors!
  45. 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! 57 https://www.pradny.com/2019/05/using-jax-rs-inside-nsf-follow-up.html
  46. • Implementation: Jesse Gallagher – Inspired by Sven Hasselbach •

    Run Java EE server – e.g. Open Liberty, open source variant of Websphere 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 58 Integrate Domino into Open Liberty https://frostillic.us/blog/posts/2019/1/3/be9501a07279b40d85258377007b0dfd
  47. Hybrid Approaches • Wrap services using a suitable front-end –

    e.g. Express, Node-RED, etc. • Add some magic – Extra processing, computation, security, etc. • Wrap missing parts from existing assets • Combine best of all worlds • Consistency across the solution • Optimized Implementation 59 Internal Network 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 Clients API Clients DMZ
  48. • OpenAPI Specifications / Documentation – Swagger Tools - swagger.io

    – Stoplight Studio - stoplight.io – Apicurio - apicur.io 61 Tooling - OpenAPI
  49. • Testing – Postman - postman.com – Paw - paw.cloud

    [Mac only] – SoapUI - soapui.org 62 Tooling - Testing
  50. • 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 64 Resources
  51. • 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 65 Resources (cont.)
  52. • 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 66 Resources (cont.)
  53. • 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 67 Resources (cont.)
  54. DNUG e.V. Pappelallee 78/79 10437 Berlin Telefon: +49 30 20898805

    0 Telefax: +49 30 20898805 1 E-Mail: [email protected] Web: http://www.dnug.de 68