Skip to content
This repository has been archived by the owner on Jun 2, 2023. It is now read-only.

Modules and Packages

Jason Ginchereau edited this page Aug 6, 2016 · 4 revisions

Overview

OpenT2T leverages the Node.js/NPM module and package system to organize, package, and publish components that can be consumed by applications and services. An OpenT2T module is any Node.js component (code, data, and/or other resources), not part of the core OpenT2T library, that may be distributed or downloaded to support dynamic discovery of and communication with different kinds of things. Examples include interface, translator, and onboarding modules. NPM is the primary distribution mechanism for OpenT2T modules; direct downloads from GitHub are also supported for development scenarios.

This document explains the details of how OpenT2T modules are organized into NPM packages that include metadata required to support OpenT2T index, query, and introspection scenarios.

Requirements

All OpenT2T modules should be publishable as NPM packages. OpenT2T benefits tremendously from fully participating in the Node.js/NPM ecosystem of packages. Furthermore, NPM provides an existing solution for many OpenT2T requirements of packaging, versioning, dependency management, download caching, etc.

GitHub should not be the primary download source for production OpenT2T modules. Any production OS, application, or service should not rely on downloading from GitHub. (Not because it's expected to be unreliable; it's simply not an appropriate use of the service.)

Anyone should be able to develop and publish OpenT2T packages anywhere, including outside the OpenT2T GitHub organization. That’s part of the "open" in OpenT2T. While the OpenT2T organization may curate many packages, 3rd parties should be free to go their own way if they choose.

OpenT2T packages should be able to express versioned dependencies on other packges. Semantic versioning dependencies allows components to evolve freely without destabilizing others that depend on them.

OpenT2T packages should be able to contain more than one module. While it might be feasible, limiting to one module per NPM package would be inconvenient: it would be too granular with too much metadata and process overhead. The opposite, for example all interfaces in only one package, would violate the requirement about anyone being able to independently publish packages.

So instead of either extreme, OpenT2T should allow any number of logically related modules to be grouped together in a package. For example, all of the translators (or at least a common subset) for several classes of device on the same hub might be grouped together, along with the hub translator, in the same package. Because they share a lot of code and may be tightly coupled, it may make sense to develop and release them at the same time in the same place. Another reason for this is that some translators may wish to define specialized interfaces, without having to publish the interface separately.

OpenT2T modules should be able to reference modules from other OpenT2T packages. Interfaces may reference (include/extend) other interfaces from other packages. Translators may implement interfaces defined in other packages.

Basic metadata about modules in a package should be easily indexable and queryable. Apps and services need to be able to identify, index, and search packaged modules without downloading the packages.

Design

The design allows multiple modules of any type to be packaged into an NPM package. OpenT2T leverages standard NPM package dependencies to manage dependencies between packages. Additionally, OpenT2T code can inspect extended package metadata to find out basic information about the OpenT2T modules in the package and their relationships.

Package metadata

The core of the design depends on having metadata about OpenT2T modules in a package, using an extension within the standard NPM package.json. (This replaces the separate manifest.xml file in the old design.) The metadata format is best illustrated using a commented example package.json. This example package happens to include one interface module and one translator module (and zero onboarding modules), but any number of modules (>= 0) of each kind are allowed.

{
  // NPM package name; this example is for a scoped package under the "@opent2t" scope.
  // Scoping is supported and recommended, but not required.
  "name": "@opent2t/translator-example",

  // Package version, following the usual semantic versioning guidelines for NPM packages
  "version": "0.1.0",

  // Additional metadata common to any package.json (not limited to properties shown here)
  "description": "Test package B",
  "author": "",
  "license": "MIT",

  // Standard NPM versioned dependencies.
  // Of course these may also include arbitrary non-OpenT2T package dependencies.
  "dependencies": {
    // Dependency on the OpenT2T core library
    "opent2t": "0.1.0",

    // Dependency on interfaces implemented by translators in this package (see below)
    "@opent2t/more-interfaces": "0.2.1"
  }

  // OpenT2T package.json extensions are within this "opent2t" JSON object
  "opent2t": {

    // Mapping from interface module names to metadata about each interface
    "interfaces": {
      // For each key (interface module name) listed here, there must exist a
      // Node module (.js file) in the package with the same name, which exports
      // the interface definition. The interface itself may be defined in
      // another format, that is loaded and parsed/converted by the JS module.
      "ExampleInterface": {
        // Currently description is the only metadata property defined for interfaces.
        "description": "Example interface"
      }
    },

    // Mapping from translator module names to metadata about each translator
    "translators": {
      // For each key (translator module name) listed here, there must exist a
      // Node module (.js file) in the package with the same name, which exports
      // the translator class.
      "ExampleTranslator": {
        "description": "Example translator",

        // References to modules of interfaces implemented by this translator
        "interfaces": [
          // Reference to an interface module from another package
          "@opent2t/more-interfaces/AnotherInterface",
          // Reference to an interface module from this package
          "./ExampleInterface",
        ],

        // Reference to an onboarding module from another package
        "onboarding": "@opent2t/onboarding-example/ExampleOnboarding",
        // Key-value pairs passed to the onboarding module
        "onboardingProperties": {
          "deviceType": "test"
        }
      }
    }
  }
}

Based on that metadata, the example package must also include top-level files named ExampleInterface.js and ExampleTranslator.js. Or instead of one JavaScript file for a module, there may be a directory with many files including an index.js. Once installed, those modules can then be loaded elsewhere by passing the package name and module name to the require() function:

var ExampleInterface = require("@opent2t/onboarding-example/ExampleInterface");
var ExampleTranslator = require("@opent2t/onboarding-example/ExampleTranslator");

The use of separate modules, as opposed to multiple exports from a single package-level index module, allows only specifically required components to be loaded.

Interface modules

An interface module is a Node.js module that exports an OpenT2T DeviceInterface object. The DeviceInterface class is the runtime representation of a grouping of properties and/or methods supported by a translator. A translator may implement multiple interfaces, which may be referenced from multiple packages.

For interoperability with other IoT ecosystems, OpenT2T supports a flexible mechanism for interface definitions. Currently, the AllJoyn XML schema format is the preferred way of defining a device interface. A JavaScript module with 2 lines of code is used to export an AllJoyn schema as an OpenT2T module:

module.exports = require("opent2t/converters/AllJoynConverter")
    .readDeviceInterfacesFromFile(require("path").join(__dirname, "ExampleInterface.xml"))[0];

This code loads the AllJoynConverter module from the OpenT2T library, uses it to parse an AllJoyn XML schema file from the same directory into a DeviceInterface object, then exports that interface from the module.

An alternative design could have placed the XML filename directly in package.json, with some additional metadata about how to parse it. But this module-based approach is better aligned with other use of modules in the rest of the design, and is more flexible because it allows an interface module to use theoretically any file format and (independently versioned) corresponding parser module, or really any other way of creating a DeviceInterface object.

Translator modules

A translator module is a Node.js module that exports a class that implements one or more interfaces. The set of implemented interfaces must match those listed in the package.json metadata for the translator module. (Additional requirements and examples for writing translators are to be documented separately.) Following is code for a very simple translator that implements one interface that has one property.

class ExampleTranslator {  // Using ES2015 class syntax
    constructor(deviceProps) {
        this._exampleProp = "example";
    }

    getExampleProp() {
        return this._exampleProp;
    }
}

module.exports = ExampleTranslator;

The primary point of this example is the last line, which exports the translator class from the module.

Onboarding modules

Onboarding modules are not designed yet, but they are expected to work very similarly to translator modules, perhaps with a limited set of predefined interfaces.

Package information

The OpenT2T library includes a PackageInfo class, which implements a straightforward deserialization of the "opent2t" node from package.json, along with top-level package identity and version. This class enables basic inspection of the OpenT2T modules in a package without scanning any additional files.

Package sources

The PackageSource class in the OpenT2T library can read PackageInfo from several local and remote package sources (filesystem, NPM, GitHub) and download remote ones to a local cache. Package sources may also support basic scanning of available packages, however that is not intended to remove the need for a proper indexing service that supports OpenT2T-specific query abilities, which will be developed later.