Using Coaty [koʊti] as a middleware, you can build distributed applications out of decentrally organized application components, so called Coaty agents, which are loosely coupled and communicate with each other in (soft) real-time. The main focus is on IoT prosumer scenarios where smart agents act in an autonomous, collaborative, and ad-hoc fashion.

Coaty removes the need for a central coordination authority by decoupling interaction between agents. You can dynamically and spontaneously add new agents and new features without distracting the existing system in order to adapt to your ever-changing scenarios. All types of agents such as sensors, mobile devices, edge and cloud services can be considered equivalent.

At its core, Coaty offers a standardized set of event-based communication patterns for Coaty agents to talk to each other by one-way/two-way and one-to-many/many-to-many communication flows without the need to know about each other. Subject of communication is domain-specific data based on a simple, extensible typed object model. Thereby, objects in a distributed system can be shared (Advertise, Channel), discovered (Discover-Resolve), queried (Query-Retrieve), as well as modified and persisted (Update-Complete). Moreover, remote operations targeted at multiple executing agents can be requested (Call-Return).

We think collaborative applications often have a natural need for such powerful forms of interaction. Restricting interaction to strongly coupled one-to-one communication (classic request-response) quickly becomes complex, inflexible, and unmaintainable. Restricting interaction to loosely coupled one-way communication (classic publish-subscribe) lacks the power of transmitting information in direct responses. Which is why Coaty combines the best of both worlds.

The Coaty framework is easy to use, straightforward to port to different programming languages, and based on modern software engineering and technology standards.

We believe Coaty is a major step forward in practice that allows developers to create more powerful collaborative applications with less complexity and in less time.

Why use Coaty

To realize decentralized, collaborative applications Coaty leverages the publish-subscribe paradigm by making use of existing pub-sub messaging protocols. The reason is to provide a powerful yet easy to use high-level abstraction of essential collaboration features without coping with the low-level complexities of pub-sub transport layers:

Read on to gain a deeper understanding of Coaty’s underlying technical concepts.

Lightweight and modular framework architecture

Coaty embodies a modern software architecture with a shift in mindset from an imperative to a resource-oriented and declarative programming style: You express where and how things should be, not how things should be done.

To achieve this, Coaty uses an object-oriented framework design with inversion of control (IoC), dependency injection (DI), and distributed lifecycle management (LM).

Coaty provides a modular framework structure where you can choose from a set of core and specialized modules to achieve operational simplicity with minimal dependencies.

Connecting to heterogenous IoT system landscapes is achieved by extensible environment connectors. Connectors serve as gateways to external networks, or bridges to connect to external systems.

Coaty comes with an essential set of database connectors to store and retrieve Coaty objects in SQL and noSQL database systems. Additional connectors are realized as extension packages of the Coaty core framework. For example, an OPC UA connector seamlessly connects Coaty applications with OPC UA servers.

Set up your agent

To set up a Coaty agent, you define, configure, and bootstrap an IoC container with its controllers that encapsulate the agent’s application logic for communication:

// Define container components
const components: Components = {
    controllers: {
        ProductionOrderController,
        SupportTaskController,
    }
};

// Configure container components
const configuration: Configuration = {
    common: {
         // Common options shared by container components
         agentIdentity: { name: "My Coaty agent" },
         ...
    },
    communication: {
          // Options used for communication
          brokerUrl: ... ,
          ...
    },
    controllers: {
        // Controller-specific configuration options
        ProductionOrderController: {
            ...
        },
        SupportTaskController: {
            ...
        },
    },
    ...
};

// Bootstrap container
Container.resolve(components, configuration);

Program your agent’s application logic

Controller classes realize custom application logic of a Coaty agent by:

class SupportTaskController extends Controller {

    /* Lifecycle methods called by framework */

    onInit() {
        // Define application-specific initializations here.
    }

    onCommunicationManagerStarting() {
        // Set up observations for incoming communication events here.
    }

    onCommunicationManagerStopping() {
        // Define application-specific cleanup tasks here.
    }

    /* Business logic methods */
    ...
}

For communication, controllers utilize the communication manager, a component that is an integral part of each IoC container. It provides a set of methods to publish and observe communication events asynchronously.

You can use the communication manager without the need to understand and deal with the complexities of the underlying publish-subscribe messaging protocol, including auto-reconnect, automatic re-subscription upon connection, queued offline publishing, message dispatching, and payload coding.

The communication manager also supports distributed lifecycle management of Coaty agents in a decentralized application. A Coaty agent can keep track of other agents or specific Coaty objects by observing agent identities or custom object types which are advertised and made discoverable by joining agents, and deadvertised by leaving agents. Tracking also handles late-joining agents, and abnormal disconnection or termination of an agent, e.g. when its connection is lost temporarily or when its process crashes or is killed.

Reactive Programming for asynchronous event handling

Reactive Programming (RP) is a paradigm for asynchronous programming with observable data streams. It is a combination of the best ideas from the Observer software design pattern, the Iterator pattern, and functional programming.

Coaty adopts RP to handle incoming asynchronous communication events modular and in context. You can process each event where needed according to the structure of your overall business logic, rather than having to implement a (central) event dispatching logic yourself.

RP also raises the level of abstraction of your code, so you can focus on the interdependence of events rather than having to constantly cope with a large amount of implementation details. Code in RP will be more concise and clear by hiding asynchronous issues such as low-level threading, synchronization and concurreny.

Reactive Programming libraries are ubiquitously available for a variety of programming languages and platforms. Coaty JavaScript, our reference framework implementation uses the RxJS library. You can also find examples and explanations on the Learn RxJS website.

Using RxJS observables in a Coaty controller is as simple as that:

/* Observe all incoming Advertise events for Task objects with status 'Done' */

this.communicationManager
    .observeAdvertiseWithCoreType("Task")
    .pipe(
        map(event => event.eventData.object as Task),
        filter(task => task.status === TaskStatus.Done)
    )
    .subscribe(task => {
        console.log("Task done:", task.name);
    });
/* Discover machine information for a given machine ID */

this.communicationManager
    .publishDiscover(DiscoverEvent.withExternalId(machineId))
    .pipe(
        take(1),
        map(event => event.eventData.object)
        timeout(5000)
    )
    .subscribe(
        info => {
            console.log("Machine info:", info);
        },
        error => {
            // No response has been received within the given timeout period
            this.logError(error, "Failed to discover machine");
        });
/* Handle both discovered and advertised tasks in context as they are received */

const discoveredTasks$ = this.communicationManager
    .publishDiscover(DiscoverEvent.withObjectTypes(["com.mycompany.myproject.SupportTask"]))
    .pipe(
        filter(event => event.eventData.object !== undefined),
        map(event => event.eventData.object as SupportTask)
    );

const advertisedTasks$ = this.communicationManager
    .observeAdvertiseWithObjectType("com.mycompany.myproject.SupportTask")
    .pipe(
        map(event => event.eventData.object as SupportTask)
        filter(task => task !== undefined)
    );

return merge(discoveredTasks$, advertisedTasks$)
            .subscribe(task => {
                // Handle both discovered and advertised tasks as they are emitted...
            });

Typed Object Model

You can model your domain data with Coaty’s platform-agnostic typed object model. It provides an opinionated set of core object types to be used or extended by Coaty applications. These Coaty objects are the subject of communication between Coaty agents.

CoatyObject
  |
  |-- Annotation
  |-- Identity
  |-- IoContext
  |-- IoNode
  |-- IoSource
  |-- IoActor
  |-- Location
  |-- Log
  |-- Snapshot
  |-- Task
  |-- User
  |
  |-- Sensor
  |-- Thing
  |-- Observation
  |-- FeatureOfInterest

Coaty objects are JSON (JavaScript Object Notation) compatible to be easily interoperable between languages and platforms. As such, Coaty objects solely represent property - value pairs that model state but no behavior.

Besides core object types, the framework also defines object types to manage a self-discovering network of sensors and to distribute sensor data. For this purpose, Coaty leverages the OGC SensorThings API.

The base CoatyObject interface defines the following generic properties:

interface CoatyObject {

  // Base core type
  coreType: "CoatyObject" | ... ;

  // Concrete type name
  objectType: string;

  // A descriptive name for the object
  name: string;

  // Unique identifier of the object
  objectId: Uuid;

  // Identifier of an object relative to an external system (optional)
  externalId?: string;

  // Object ID of parent object (optional)
  parentObjectId?: Uuid;

  // Object ID of associated Location object (optional)
  locationId?: Uuid;

  // Indicates whether object is no longer in use (optional)
  isDeactivated?: boolean;
}

To enable the distributed system architecture to uniquely identify an object without central coordination, each object has a universally unique object identifier (UUID).

To define a domain-specific object type, simply extend one of the core object types with additional properties and specify a canonical object type name:

enum SupportTaskUrgency {
    Low,
    Medium,
    High,
    Critical
}

interface SupportTask extends Task {
    coreType: "Task",
    objectType: "com.mycompany.myproject.SupportTask";

    // Level of urgency of a support task
    urgency: SupportTaskUrgency;
}

Communication foundation

Coaty reaches distributed system flexibility by decoupling communication endpoints, but maintaining all types of communication flows.

Communication foundation

Coaty uses event-based communication flows with one-way/two-way and one-to-many/many-to-many event patterns to realize decentralized prosumer scenarios. Thereby, Coaty combines the characteristics of both classic request-response and publish-subscribe communication. In contrast to classic client-server systems, all Coaty participants are equal in that they can act both as producers/requesters and consumers/responders.

One of the unique features of Coaty communication is the fact that a single request in principle can yield multiple responses over time, even from the same responder. The use case specific logic implemented by the requester determines how to handle such responses. For example, the requester can decide to

Using this “open channel” communication, you can realize advanced interaction scenarios beyond classic request-response in a very simple way. For example, by introducing live queries, a Query event responder could monitor the database for changes and publish a Retrieve event with the latest query result every time the concerned data is updated. In this way, inefficient manual database polling is replaced by an efficient automatic push-based approach that helps your application feel quick and responsive.

To give you another example, it may be desirable for some remote operation calls to have a progressive series of call results, e.g. to return partial data in chunks or progress status of long running operations, even simultaneously by different callees.

Communication event patterns

Coaty provides a set of event-based communication patterns to discover, query, share, and update data on demand in a distributed system, and to request execution of context-filtered remote operations.

Communication event patterns

Coaty’s standardized communication event patterns are build on top of interchangeable open-standard publish-subscribe (pub/sub) messaging protocols which are either broker-based, such as MQTT or WAMP, or brokerless like DDS or peer-to-peer approaches. Providing communication bindings, you can choose a specific messaging transport for your Coaty application while keeping the set of communication event patterns and your application code completely unaffected. By choosing a WebSocket-aware communication binding, Coaty agents can also natively run in mobile and web browsers.

With the help of Reactive Programming, all Coaty event patterns are programmed in a simple, uniform way. You can find some examples in the section on Reactive Programming. For details and advanced use cases, take a look at the Coaty JS Developer Guide.

Query anwhere - retrieve anywhere - persist anywhere

Coaty communication provides a Query-Retrieve event pattern to seamlessly query distributed domain-specific data across decentralized application components, and retrieve them from a single or multiple storage systems, no matter if storage is persistent in a database or in memory. Query-Retrieve enables declarative, seamless and transparent retrieval of Coaty objects across Coaty agents independent of storage implementations. The Query event’s object filter which specifies selection and ordering criteria can be directly passed to Coaty’s Unified Storage API for object retrieval.

Using the Unified Storage API, Coaty objects can be persisted schemalessly in arbitary data sources. The database-agnostic API provides a common interface to store and retrieve data from NoSQL and relational SQL data stores using the notion of adapters to connect to specific databases.

The Coaty framework provides ready-to-use built-in adapters for NoSQL/SQL-based storage in e.g. PostgreSQL and SQLite databases. In addition, it provides an in-memory storage. You can also write your own custom adapter to connect to a specific database not yet supported by the framework.

Smart routing of IoT data

Coaty provides what we call Smart Routing of IoT (sensor) data, or also called IO Routing, where data is dynamically routed from sources to actors, i.e. consumers based on context. Backpressure strategies that enable data transfer rate controlled publishing of data are negotiated between sources and actors.

Smart routing ensures that IoT data events published by sources are dispatched only to the currently relevant actors, depending on application-specific context logic which can be defined by a set of rules. Coaty’s rule engine evaluates these rules and updates associations between sources and actors accordingly.

For details, take a look at the Coaty JS Developer Guide.

Works across languages and platforms

Coaty is designed with interoperability in mind. It can be easily ported to a variety of programming languages and platforms because of its layered and modular architecture and its lightweight technology stack:

Coaty JS is the reference implementation of the Coaty framework. It works across platforms and is targeted at JavaScript/TypeScript, running as mobile and web apps in the browser, or as Node.js services. Coaty JS provides a complete set of features as described in the Developer Guide.

Coaty Swift is a subset implementation of the Coaty framework for the Swift programming language targeted at iOS, iPadOS, and macOS applications.

Coaty JS should be used as reference for porting Coaty to new programming languages and platforms. We intend to make additional framework implementations available as open source.

Getting started

Get started with Coaty by using its best practice code examples as application skeleton. In addition, Coaty comes with all you need to know: a developer guide, coding style guide, and complete framework API documentation.

Details can be found on the Documentation page.