{"id":2886,"date":"2015-07-06T18:23:44","date_gmt":"2015-07-06T18:23:44","guid":{"rendered":"http:\/\/13.52.125.237\/?p=2886"},"modified":"2019-02-16T18:40:19","modified_gmt":"2019-02-16T18:40:19","slug":"conventions-for-isomorphism","status":"publish","type":"post","link":"https:\/\/sotaenterprises.com\/?p=2886","title":{"rendered":"JavaScript Functions That Run Beautifully on Client and Server"},"content":{"rendered":"<p>One of the compelling benefits of Meteor\/Node.js is that a single language, JavaScript, can be\u00a0used on client and server. \u00a0Meteor developers use the\u00a0word <em>isomorphic<\/em>\u00a0to describe JavaScript code that has been written to run anywhere. \u00a0To\u00a0make that possible, your JavaScript functions must adhere to certain coding conventions.<\/p>\n<p>After some experimentation and false starts, we have devised helpful programming standards that foster isomorphic code.\u00a0\u00a0I will share these standards with you through concrete examples.<\/p>\n<p>I&#8217;m currently working on an MVP for a startup called Pract.us. \u00a0This system uses a variety of\u00a0Meteor packages, plus some custom-written UI code to\u00a0deal with forms, animations and so on. \u00a0In contrast\u00a0to this &#8220;mechanical&#8221; code, which has nothing to do with the problem domain, the system has\u00a0dozens\u00a0of\u00a0<em>domain functions<\/em> which\u00a0are germane exclusively\u00a0to Pract.us. \u00a0Many\u00a0of these\u00a0domain functions can be classified either as <em>queries<\/em> or <em>transactions<\/em> because they either read from or write to the database.<\/p>\n<p>Domain functions are the heart\u00a0of the system. \u00a0They encapsulate logic that updates the database in response to UI events. \u00a0A large part of the Pract.us development effort goes into\u00a0writing and refining these domain functions.<\/p>\n<p>In Pract.us, a UI event handler will usually\u00a0delegate to one or more domain functions. \u00a0The domain function(s) will perform\u00a0a number of database queries and updates, and some of them are complex, involving updates to several collections.<\/p>\n<p>Pract.us\u00a0domain functions are <em>anchored<\/em> in a global JavaScript object named\u00a0<strong>Practus<\/strong>. \u00a0Because Pract.us is a small system, it is feasible to anchor <em>all<\/em> domain functions in <strong>Practus<\/strong>.<\/p>\n<p>Domain functions are technically\u00a0<em>stateless<\/em> because they have no instance variables. \u00a0They operate exclusively on passed parameter values and data stored in Mini-Mongo and\/or MongoDB.<\/p>\n<p>To facilitate isomorphism, we\u00a0first declare a\u00a0<strong>Practus<\/strong> anchor object\u00a0in a Meteor project folder that is shared by both\u00a0client and server:<\/p>\n<blockquote><p><code>Practus = {};<\/code><\/p><\/blockquote>\n<p>Next, we\u00a0create three JavaScript files named <strong>practus.js<\/strong>: one in a client folder, one in a server folder and one in a shared folder. \u00a0All three files contain the following skeletal code:<\/p>\n<blockquote><p><code>\/*<br \/>\n&nbsp;* Pract.us domain functions.<br \/>\n&nbsp;*\/<br \/>\n\"use strict\"<br \/>\nPractus = _.extend(Practus || {}, {<br \/>\n&nbsp;<strong>\/\/ *** DOMAIN FUNCTIONS GO HERE ***<\/strong><br \/>\n});<\/code><\/p><\/blockquote>\n<p>By using the Underscore <strong>extend<\/strong> function, we&#8217;re appending functions to the <strong>Practus<\/strong> anchor object. \u00a0Since shared functions are visible to <em>both<\/em> client and server, this results in the following arrangement:<\/p>\n<div class='avia-table main_color avia-pricing-table-container  avia_pricing_default  avia-table-1  avia-builder-el-0  avia-builder-el-no-sibling '  itemscope=\"itemscope\" itemtype=\"https:\/\/schema.org\/Table\" ><div class='pricing-table-wrap'><ul class='pricing-table '><li class=''><div class='first-table-item'>Client<\/div><span class='pricing-extra'><\/span><\/li><li class=''>Shared Functions + Client Functions<\/li><\/ul><\/div><div class='pricing-table-wrap'><ul class='pricing-table '><li class=''><div class='first-table-item'>Server<\/div><span class='pricing-extra'><\/span><\/li><li class=''>Shared Functions + Server Functions<\/li><\/ul><\/div><\/div>\n<p>Technically, only the\u00a0shared functions <em>need<\/em> to\u00a0be isomorphic, but\u00a0we apply\u00a0the same programming conventions to all domain functions. This allows us to move functions around without costly refactoring.<\/p>\n<p>To invoke a domain function, we make a call via the <strong>Practus<\/strong> anchor. Example:<\/p>\n<blockquote><p><code>Practus.setCardOpen(cardId, true);<\/code><\/p><\/blockquote>\n<p>Since client, server and shared functions are all invoked via the <strong>Practus<\/strong> anchor, the impact of moving functions is minimized. This is particularly helpful for nested calls (i.e., when one <strong>Practus<\/strong> function calls another <strong>Practus<\/strong> function).<\/p>\n<p>When you write domain functions, you must restrict your code to use only those services that are available on both\u00a0client and server. \u00a0A client-side event handler may\u00a0use jQuery to extract information from the DOM, then pass that\u00a0information as <em>parameters<\/em> to isomorphic domain functions.<\/p>\n<blockquote><p><code>card = this;<br \/>\n$switch = $(event.target);<br \/>\nopen = $switch.bootstrapSwitch(\"state\");<br \/>\nresult = Practus.setCardOpen(card._id, open);<br \/>\n<\/code><\/p><\/blockquote>\n<p>Any jQuery calls must be performed <em>outside<\/em> the domain function, because\u00a0jQuery calls <em>inside<\/em> the domain function would prevent it from being used on the server side.<\/p>\n<p>In Pract.us, domain functions always return a <em>result object<\/em> that contains a success\/failure indicator, an i18n message key, message variables and a message severity level. \u00a0Domain functions never throw errors, but instead catch-and-return them as\u00a0failure-type\u00a0result objects. The UI will render\u00a0the response object to the user via PNotify, resulting in a pop-up message in the lower-right corner of the page:<\/p>\n<p><a href=\"http:\/\/sotaenterprises.com\/wp-content\/uploads\/2015\/07\/pnotify.png\"><img loading=\"lazy\" class=\"aligncenter size-full wp-image-2952\" src=\"http:\/\/sotaenterprises.com\/wp-content\/uploads\/2015\/07\/pnotify.png\" alt=\"pnotify\" width=\"298\" height=\"70\" \/><\/a>PNotify can be used to report results that are returned from both client-side <em>and<\/em> server-side domain functions.<\/p>\n<p>When the UI calls a client-side domain function, that function will return the result object directly:<\/p>\n<blockquote><p><code>var result = Practus.rejectEnrollment(cardId);<br \/>\nUX.createAlertForResult(result);<\/code><\/p><\/blockquote>\n<p>When the UI calls a server-side domain function, that function will return the result object via a callback:<\/p>\n<blockquote><p><code>Meteor.call(\"rejectEnrollment\", cardId, function(err, result) {<br \/>\n&emsp;UX.createAlertForResult(result);<br \/>\n});<\/code><\/p><\/blockquote>\n<blockquote><p><code>Meteor.methods({<br \/>\n&emsp;rejectEnrollement: function(cardId) {<br \/>\n&emsp;&emsp;return Practus.rejectEnrollment(cardId);<br \/>\n&emsp;})<br \/>\n});<\/code><\/p><\/blockquote>\n<p>PNotify is well-suited for reporting results from domain functions because it can work both synchronously and asynchronously. When the UI invokes server-side domain functions, there will be a delay before the results come back. Using PNotify, even if that delay is long, and the user has moved on to a new page, the message will be displayed properly once it arrives; thus, the UI can call  server-side domain functions without locking the UI and forcing the user to wait for the response.<\/p>\n<p>A well-written isomorphic function can be moved from client to\u00a0server or vice-versa with negligible impact on user experience; the only difference will be\u00a0latency.<\/p>\n<p>Mini-Mongo makes isomorphism feasible because it provides\u00a0a client-side\u00a0API that is identical to the server-side\u00a0MongoDB API. \u00a0In a data-centric system, most domain functions either query or update the database. \u00a0Since Meteor provides identical APIs on\u00a0both client and server, most domain functions can be written\u00a0isomorphically.<\/p>\n<p>Although domain functions\u00a0can run equally well on client or server, there can be tantalizing advantages to running them on\u00a0the client.\u00a0 First, there are UI performance advantages. UI events, such as toggling a switch, will trigger an immediate UI update\u00a0as Meteor responds to Mini-Mongo state changes and re-renders the DOM locally without a server round trip (Meteor developers call this\u00a0<em>latency compensation<\/em>). \u00a0Moreover, client-centric coding\u00a0can reduce the server-side workload and costs to a level that would be difficult to achieve\u00a0with a\u00a0traditional page-oriented application.<\/p>\n<p>When you write isomorphic functions, it will make things easier if you use Mini-Mongo and MongoDB to hold <em>all<\/em> of your state. \u00a0You must publish and subscribe collections carefully so that all necessary data is available in Mini-Mongo before attempting to promote\u00a0server-side functions\u00a0to the client. On occasion, I&#8217;ve tried to\u00a0move functions from server to client only to discover that some of the data that those functions needed\u00a0had not been published to the client. Such problems can be remedied by refactoring domain functions or changing the publishing rules. Domain functions may be split so that part of the work is done on the client, while other parts remain on the server.<\/p>\n<p>With\u00a0good programming standards and proper design, you can develop your domain functions isomorphically, allowing you to easily move code from client to server or vice-versa as you see fit. \u00a0The option\u00a0to move functions with impunity\u00a0can improve quality and performance, while simultaneously reducing development and operational costs.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>One of the compelling benefits of Meteor\/Node.js is that a single language, JavaScript, can be\u00a0used on client and server. \u00a0Meteor developers use the\u00a0word isomorphic\u00a0to describe JavaScript code that has been written to run anywhere. \u00a0To\u00a0make that possible, your JavaScript functions must adhere to certain coding conventions. After some experimentation and false starts, we have devised [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_bbp_topic_count":0,"_bbp_reply_count":0,"_bbp_total_topic_count":0,"_bbp_total_reply_count":0,"_bbp_voice_count":0,"_bbp_anonymous_reply_count":0,"_bbp_topic_count_hidden":0,"_bbp_reply_count_hidden":0,"_bbp_forum_subforum_count":0},"categories":[1],"tags":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/sotaenterprises.com\/index.php?rest_route=\/wp\/v2\/posts\/2886"}],"collection":[{"href":"https:\/\/sotaenterprises.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sotaenterprises.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sotaenterprises.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/sotaenterprises.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2886"}],"version-history":[{"count":123,"href":"https:\/\/sotaenterprises.com\/index.php?rest_route=\/wp\/v2\/posts\/2886\/revisions"}],"predecessor-version":[{"id":3324,"href":"https:\/\/sotaenterprises.com\/index.php?rest_route=\/wp\/v2\/posts\/2886\/revisions\/3324"}],"wp:attachment":[{"href":"https:\/\/sotaenterprises.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2886"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sotaenterprises.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2886"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sotaenterprises.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2886"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}