The jump to a new major release of Coaty involves breaking changes to the Coaty JS framework API. This guide describes what they are and how to upgrade your Coaty JS application to a new release.
Coaty 2 incorporates experience and feedback gathered with Coaty 1. It pursues the main goal to streamline and simplify the framework API, to get rid of unused and deprecated functionality, and to prepare for future extensions.
Among other refactorings, Coaty JS 2 carries breaking changes regarding package naming and import declarations, object types, distributed lifecycle management, IO routing, and the communication protocol while keeping the essential set of communication event patterns. Therefore, Coaty 2 applications are no longer backward-compatible and interoperable with Coaty 1 applications.
Coaty JS 1 is still available on npm package coaty@1.x.y
. Coaty JS 2 is
deployed separately on scoped npm package @coaty/core@2.x.y
.
To update to Coaty JS 2, follow the migration steps described next.
To update the build infrastructure of your application, follow these steps:
package-lock.json
and node_modules
folder from your project.package.json
to
comply with Coaty 2:
The easiest way to achieve this is to take over the dependency versions defined in the template project located in the “coaty-examples” repository.
npm install
.Coaty 2 introduces a common npm package scope named @coaty
for current and
future framework projects of the Coaty JS platform. The Coaty 2 framework is
published in the npm package @coaty/core
.
Rewrite all import and require declarations coaty/<modulename>
to use this
scoped package as follows:
coaty/com
, coaty/controller
, coaty/model
, coaty/runtime
, coaty/util
-> @coaty/core
coaty/db
-> @coaty/core/db
coaty/db-adapter-in-memory
-> @coaty/core/db/adapter-in-memory
coaty/db-adapter-postgres
-> @coaty/core/db/adapter-postgres
coaty/db-adapter-sqlite-cordova
-> @coaty/core/db/adapter-sqlite-cordova
coaty/db-adapter-sqlite-node
-> @coaty/core/db/adapter-sqlite-node
coaty/io
-> @coaty/core/io-routing
coaty/runtime-angular
-> @coaty/core/runtime-angular
coaty/runtime-node
-> @coaty/core/runtime-node
coaty/sensor-things
-> @coaty/core/sensor-things
coaty/sensor-things-io
-> @coaty/core/sensor-things/io
coaty/scripts
-> @coaty/core/scripts
coaty/tslint-config
-> @coaty/core/tslint-config
Refactor import declarations of the following type references:
DbConnectionInfo
- import from @coaty/core
HistorianController
- import from @coaty/core/db
All features marked as deprecated in Coaty 1 have been removed:
CommunicationManager.observeAdvertise()
- use either
observeAdvertiseWithCoreType()
or observeAdvertiseWithObjectType()
CommunicationManager.restart()
- use CommunicationManager.start()
CommunicationOptions.brokerOptions
- use
CommunicationOptions.mqttClientOptions
Container.getRuntime()
- use Container.runtime
Container.getCommunicationManager()
- use Container.communicationManager
util
moduleAsync.inSeries()
function no longer returns a boolean promise that
indicates whether an async operation has terminated prematurely. Instead, the
promise resolves the index of the last item that the async operation has been
applied to successfully.runtime-node
moduleRefactor the following definitions:
provideConfiguration()
-> NodeUtils.provideConfiguration()
provideConfigurationAsync()
-> NodeUtils.provideConfigurationAsync()
Configuration
optionsmergeConfigurations
function now accepts partial primary or secondary
configuration parameters.Configuration.common
property is now optional.Configuration.common
into its new extra
property.Configuration.common.associatedUser
as this property has been
removed. If needed for any other purpose than IO routing, define and access
your User object in Configuration.common.extra
. For more details, see
section “Changes in IO routing”.Configuration.common.associatedDevice
as this property has
been removed. For details, see section “Changes in IO routing”.Runtime.options
to Runtime.commonOptions
. Its value is undefined
if the Configuration.common
property is not specified.Container
Container.registerController()
replace the third argument
ControllerConfig
by ControllerOptions
.Component
to Identity
. This name better reflects
its intent to provide the unique identity of a Coaty agent.Config
as this core type has been removed. If needed, define an
equivalent object type in your application code.Device
as this object type has been removed. For details, see
section “Changes in IO routing”.CoatyObject.assigneeUserId
as this property has been removed.Task.workflowId
as this property has been removed. If needed,
define an equivalent property in your custom task type.Task.assigneeObjectId
to reference an object, e.g. a user
or machine, that this task is assigned to.Task.requirements
property to consist of key-value pairs.logLabels
has been added to Log
object type. Useful in
providing multi-dimensional context-specific data along with a log.Log
object type directly. Add them to the
new Log.logLabels
property.LogHost
objects directly. Extend the
LogHost
interface to provide them.In Coaty 2, you no longer need to care about unsubscribing RxJS subscriptions
of Observables returned by CommunicationManager.observe...()
and
CommunicationManager.publish...()
methods when the communication manager is
stopped. These subscriptions are now automatically disposed, in order to
release system resources and to avoid memory leaks.
unsubscribe()
method calls for these subscriptions in the
Controller.onCommunicationManagerStopping()
method, as they are now
unsubscribed automatically.To realize distributed lifecycle management for Coaty agents in Coaty 1, individual identity objects were assigned to the communication manager as well as to each controller, in order to be advertised and to be discoverable by other agents. The identity objects were also used to provide event source IDs for communication events to realize echo suppression on the controller level so that events published by a controller could never be observed by itself.
In Coaty 2, distributed lifecycle management has been simplified and made more efficient:
Upgrade to the new approach as follows:
AdvertiseEvent.withObject()
, remove the first argument eventSource
.CommunicationManager.observeXXX()
remove the first argument eventTarget
.CommunicationOptions.identity
as
this property has been removed. Instead, customize properties of the
container’s identity object in the new CommonOptions.agentIdentity
property.CommunicationManager.identity
as this getter has been removed.
Instead, use Container.identity
to access the container’s identity object,
i.e. from within a controller use this.container.identity
.CommunicationOptions.shouldAdvertiseIdentity
as this property has
been removed.Controller.identity
as this getter has been removed. Use
Container.identity
instead.Controller.initializeIdentity()
as this method has been
removed.ControllerOptions.shouldAdvertiseIdentity
as this property has
been removed.Controller.onContainerResolved()
as this method will no longer be called and used by the framework. Move the
code of this method into Controller.onInit
, accessing the container by
Controller.container
getter.ObjectLifecycleController
or your custom controllers to track
the identity of controllers. Only identity of containers can be observed or
discovered.If echo suppression of communication events is required for your custom controller, place it into its own container and filter out observed events whose event source ID equals the object ID of the container’s identity, like this:
this.communicationManager.observeDiscover()
.pipe(filter(event => event.sourceId !== this.container.identity.objectId))
.subscribe(event => {
// Handle non-echo events only.
});
CommunicationEvent.eventData
getter to CommunicationEvent.data
.CommunicationEvent.eventSourceId
getter to CommunicationEvent.sourceId
.CommunicationEvent.eventSource
as this getter has been removed.
Use CommunicationEvent.sourceId
instead.CommunicationEvent.eventUserId
as this getter has been removed.
For details, see section “Changes in IO routing”.IoStateEvent.eventData
getter to IoStateEvent.data
.OperatingState.Initial
, OperatingState.Starting
and
OperatingState.Stopping
as these enum members have been removed. Use
OperatingState.Started
and OperatingState.Stopped
instead.CommunicationOptions.useReadableTopics
as this property has been
removed. This feature is no longer supported.CommunicationOptions.mqttClientOptions.qos
. The
default and recommended QoS level is 0.CommunicationManager.options
is now read-only, you can specify optional
partial options in CommunicationManager.start(options?)
which override
communication options in the configuration.CommunicationManager.start()
not until
the returned promise resolves.CommunicationOptions.namespace
and
CommunicationOptions.shouldEnableCrossNamespacing
). Communication events are
only routed between agents within a common namespace.We abandon partial Update events in favor of full Update events where you can choose to observe Update events for a specific core type or object type, analogous to Advertise events. This reduces messaging traffic because Update events are no longer submitted to all Update event observers but only to the ones interested in a specific type of object.
UpdateEvent.withPartial()
.
Replace them by full Update events.UpdateEventData.isPartialUpdate()
,
UpdateEventData.isFullUpdate()
, UpdateEventData.objectId
and
UpdateEventData.changedProperties
as these getters have been removed.UpdateEvent.withFull()
to UpdateEvent.withObject()
.CommunicationManager.observeUpdate()
as
this method has been removed. Use either
CommunicationManager.observeUpdateWithCoreType()
or
CommunicationManager.observeUpdateWithObjectType()
.When observing Call events by CommunicationManager.observeCall()
with a
context argument specified, the logic of matching the context filter of an
incoming Call event to the given context object has changed: A Call event is no
longer emitted by the observable if context filter is not supplied and
context object is specified.
CommunicationManager.observeRaw()
no longer emits messages that do not
match the specified subscription topic filter. Remove the RxJS filter
operator in the Observable pipe as filtering is is no longer needed.CommunicationManager.observeRaw()
no longer emits messages for non-raw Coaty
communication event patterns. If you observe raw topics that start with
coaty/
, matching incoming messages will never be emitted.SensorSourceController.findSensor(predicate)
to look up a
registered sensor satisfying a certain predicate.SourceCodeController.registeredSensorsChangeInfo$
are read-only. If you need to modify one, clone the object first (using
clone()
function in @coaty/core
).In Coaty 1, the scope of IO routing is restricted to a single user and its devices that define IO sources and actors. IO value events are only routed between the IO sources and actors of these devices.
Coaty 2 redesigns IO routing by generalizing and broadening the scope of
routing. User
and Device
objects are no longer used for IO routing. They are
replaced by IoContext
and IoNode
objects, respectively:
To migrate to the new concept, read the updated section on IO routing in the Developer Guide and follow these steps:
IoSource.externalTopic
property to IoSource.externalRoute
.IoActor.externalTopic
property to IoActor.externalRoute
.Device
as this functionality has been removed.CommonOptions.associatedUser
and CommonOptions.associatedDevice
as these properties have been removed. For IO routing, use
CommonOptions.ioNodes
and CommunicationManager.getIoNodeByContext()
instead.CommunicationOptions.shouldAdvertiseDevice
as this property has
been removed.externalDevices
for IoRouter
classes as this router option
has been removed.IoRouter.getAssociatedUser()
as this method has been removed.
Instead, use IoRouter.ioContext
and configure the IO context by router
controller option ioContext
.IoRouter.getAssociatedDevices()
as this method has been
removed. Use IoRouter.managedIoNodes
instead.IoRouter.findAssociatedDevice()
as qthis method has been
removed. Use IoRouter.findManagedIoNode()
instead.IoRouter.onDeviceAdvertised()
as this method has been
removed. Use IoRouter.onIoNodeManaged()
instead.IoRouter.onDevicesDeadvertised()
as this method has been
removed. Use IoRouter.onIoNodesUnmanaged()
instead.RuleBasedIoRouter
expect the fifth argument of the
IoRoutingRuleConditonFunc
to be the IoContext
of the router, and the sixth
argument to be the router itself.Copyright (c) 2020 Siemens AG. This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.