Skip to content

API Design Principles

Pierre Cauchois edited this page Dec 25, 2016 · 1 revision

API Design Principles

As much as possible the Azure IoT SDK for Node.js follows commonly adopted Node.js coding conventions. If you find yourself disagreeing with something we wrote or find that we don't follow a convention that would be beneficial to us, please let us know in the Issues section of the repository!

Classes and Methods

We've found that simple Data Transfer Object (DTO) classes add little value in Javascript, so we try to limit the classes we have to objects that have methods, that could be called RPC Proxies. Typically, Client, Registry etc, that have methods manipulating DTOs. There are a few exceptions to this rule, mainly present for legacy purposes.

Inheritance, Composition, IoC and Factory Methods

Across the SDK we may have one level of inheritance from native Node.js objects: for example, classes that emit events inherit from EventEmitter. Within the SDK objects we favor composition to maximize code reuse. Typically, transport clients in the azure-iot-common package will be reused in both the device and service transports. We have been trying (without breaking backward compatibility) to implement inversion of control wherever possible but we would rather have good defaults than force the user to know a bunch of internal classes that are not directly usable, which is why we have factory methods for the main clients and encourage users to use those more than constructors.

Async methods and callbacks

Most method calls require sending data to your Azure IoT hub and are therefore asynchronous. Early in the SDK creation we decided to use simple callbacks that take error and result arguments (also sometimes called nodebacks). The error must always be the first argument and can be null, and the result must be the second argument and may be undefined if an error is present.

We decided not to go with promises because they were not widely adopted at the time and required external dependencies to support older versions of node. It's possible however to use our SDK with promises since we follow the nodeback conventions, by using promisification libraries such as bluebird.promisifyAll.

Errors and Exceptions

Custom Error Types

We use custom errors types that derive from the native Error object. Each custom error contains a message and a call stack. The point of using custom errors is to abstract transport-level errors from the client objects user. This allow, as much as possible, to write code that is transport-agnostic and enables a user to change for example from AMQP to MQTT or HTTP, and make use of websockets or not, without changing any code.

Wherever relevant we still include the transport-level error within the custom error type, so that it is still possible for a user to get the nitty-gritty details of the transport error (for debugging purposes for example).

Throw vs Callback

We use two different mechanisms to surface errors:

  • Throw an error object (that becomes an exception): the error is due to a programming error (eg. incorrect use of the API)
  • Pass the error object in the callback of the method: the error happens while executing a method and is not due to programming (eg. throttling)
  • Emit an error event with an Error object parameter: the error isn't a programming error and doesn't happen within the execution of a method (eg. disconnecting the network cable)