title: Defining the order of loopback observers
body: <p>I'm currently writing a REST API in node.js using IBM's loopback. I'm running into an issue where the order of observers is relevant, and they are called in the wrong order.</p>
  
  <p>My <em>Ticket</em> model has an <strong>internal status</strong> field. This status is stored in the database as a UUID. Somewhere in a large "metadata" file, the human name for each status is also defined. The same goes for the <strong>external status</strong> field.</p>
  
  <p>Here are the relevant parts of <code>Ticket.json</code>:</p>
  
  <pre><code>{
    "name": "Ticket",
    "properties": {
      "internalStatusId": {
        "type": "String"
      },
      "externalStatusId": {
        "type": "String"
      }
    }
  }
  </code></pre>
  
  <p>I have written a generic mixin that can convert the human name of such a UUID field to the correct UUID. This mixin is used to allow clients to set a UUID field by its name (via a differently named field).</p>
  
  <p>Here is a simplified version of the mixin:</p>
  
  <pre><code>// This observer is actually a simplification of the actual observer,
  // which is more generic. It handles "name to id" mapping for any number
  // of fields you can configure in the &lt;model&gt;.json file.
  Model.observe('before save', function (ctx, next) {
    let data = ctx.isNewInstance ? ctx.instance : ctx.data;
    if (data.internalStatusName) {
      data.internalStatusId = internalStatusNameToIdMap[data.internalStatusName];
      delete data.internalStatusName;
    }
    next();
  });
  </code></pre>
  
  <p>So if you send a <code>PUT</code> request to <code>/Tickets/1</code> with <code>{ "internalStatusName": "Closed" }</code> as body, this code will convert that to the correct UUID, put that in the <code>internalStatusId</code> field, and remove the <code>internalStatusName</code> field from the arguments.</p>
  
  <p>Now, there is a business rule in the system: if the internal status is set to <code>Closed</code>, the external status also needs to be set to <code>Closed</code>. The code for that is located in <code>Ticket.js</code>, because it's not generic but only relevant for the <em>Ticket</em> model:</p>
  
  <pre><code>// This observer is located in Ticket.json.
  // It makes sure that, when a ticket's internal status is set to Closed,
  // the external status is also set to Closed.
  Ticket.observe('before save', function (ctx, next) {
    let data = ctx.isNewInstance ? ctx.instance : ctx.data;
    if (data.internalStatusId === INTERNAL_STATUS_CLOSED_UUID) {
      data.externalStatusId = EXTERNAL_STATUS_CLOSED_UUID;
    }
    next();
  });
  </code></pre>
  
  <p>The problem that I'm running into is that these two observers don't work well together, because they are invoked in the wrong order.</p>
  
  <p>If I send <code>{ "internalStatusName": "Closed" }</code>:</p>
  
  <ul>
  <li>First the observer from <code>Ticket.js</code> is called. This checks if the <code>internalStatusId</code> field is set, to know if the external status needs to be updated. The <code>internalStatusId</code> is not present in the arguments, so the external status is not set.</li>
  <li>Second, the mixin observer is called. The internal status name is converted to the id.</li>
  </ul>
  
  <p>This order is probably caused by loopback first loading the observers in <code>Ticket.js</code> and after that the mixin observers.</p>
  
  <p>I could, of course, modify the second observer to look for both the <code>internalStatusId</code> <em>and</em> the <code>internalStatusName</code> fields. However, this leads to a significant duplication of code, especially because I have many of such business logic observers and also many UUID fields like this.</p>
  
  <p>I've been looking for a way to tell loopback which order to run these observers in. Even a function as simple as <code>Model.observeFirst()</code> (this does not actually exist!) to put one or more observers at the front of the chain would solve this problem. I have not been able to find if this is possible, and if so, how.</p>
  
  <p>How would you solve this problem?</p>