- Connecting to the Avid Platform using the Avid Connector API
- Threading Model
- Using the Avid Connector API as a Client
- Using the Avid Connector API to Host Services
- Using the Avid Connector API for Channels
- Accessing Constants
- Exposing Operations as REST Requests
- Migrating to IOperationContext, IChannelContext and IChannelSubscriber interfaces
- Migrating to the Asynchronous Methods of IBusClient
Connecting to the Avid Platform using the Avid Connector API
The Avid Connector API connects to the Avid Platform over the Avid Secure Gateway. To connect your service to the Avid Platform, the Avid Secure Gateway must be running on a known host and port. The default port is 9900.
Default Connection Settings
If no special settings are passed in, and no special environment variables defined, the following defaults are used:
- Query Timeout (ms): 10,000
- Gateway Port: 9900
- Gateway Host: 127.0.0.1
- Bus Initial Connection Attempts: -1 (try forever)
- Bus Initial Reconnection Attempts: -1 (try forever)
- Bus exponential back-off reconnection duration for unlicensed connection (ms): 600000 (10 minutes)
- Bus exponential back-off reconnection jitter for unlicensed connection: true (apply jitter to the reconnection delays)
To connect to the Avid Platform, create a BusAccess
object.
When connecting to the Avid Platform, an authentication provider may be specified using a client id
and client secret
(default authentication is based on IP address).
1 | // Create a new BusAccess, with default connection settings and default authentication provider, and connect to the bus immediately. |
Non-default settings can be set by passing a ConnectionInfo
object to the BusAccess
constructor.
1 | // Create a new BusAccess, with non-default connection settings and default authentication provider, and connect to the bus immediately. |
Constructing a BusAccess object and connecting to the bus can be accomplished in two separate operations. All of the BusAccess constructors accept a boolean connectNow
parameter. Passing false
for this will delay connecting to the bus until the Connect
method is called.
The Connect
method will not return until it establishes a connection or until the supplied CancellationToken
has been canceled (via CancellationTokenSource.Cancel
):
1 | CancellationTokenSource cancel = new CancellationTokenSource(); |
Overriding Default Settings with Environment Variables
You can override the default connection settings by setting pre-defined environment variables. The table below shows some of the variables/keys you can set to affect the way the bus connects:
Environment Variable | Description |
---|---|
ACS_BUS_QUERY_TIMEOUT | The default timeout (in ms) for Avid Platform queries. Default is 10000 ms |
ACS_GATEWAY_HOST | Secure Gateway connection host. Default is 127.0.0.1 |
ACS_GATEWAY_PORT | Secure Gateway connection port. Default is 9900 |
ACS_GATEWAY_UNSECURE_PORT | Port for not secured connection. Default is 9966 |
ACS_GATEWAY_PROTOCOL_SEQUENCE | Sequence of protocols (or just one protocol) separated by coma ‘,’ in which Avid Connector API will try to establish connection to Secure Gateway. Default is ‘wss’. Allowed protocols are ‘wss’ and ‘ws’. Possible combinations are ‘wss,ws’, ‘ws’, ‘wss’ or ‘ws,wss’. |
ACS_PLATFORM_IDENTIFIER | Unique node identifier, where service is running. Must be provided by target platform, i.e.AWS, OpenStack, etc. (Default ‘unknown’) |
ACS_SERVICE_BUILD_NUMBER | RPM version or any other version of the service (Default ‘unknown’) |
ACS_ENVIRONMENT_IDENTIFIER | Environment identifier is basically chef generalized identifier for any collection of nodes (Default ‘unknown’) |
ACS_BUS_INITIAL_CONNECTION_ATTEMPTS | The # of times to attempt the initial connection before failing (-1 means to try forever) |
ACS_GATEWAY_CONNECTION_LOST_THRESHOLD | Amount of time, after which connection to Gateway considered as broken if we didn’t get ping from gateway. (Default 5000 ms) |
ACS_BUS_RECONNECTION_ATTEMPTS | The # of times to attempt reconnecting a broken connection (-1 means to try forever) |
ACS_BUS_RECONNECTION_DELAY | Delay in ms between connection attempts to Secure Gateway. This delay same both for initial connection attempts and reconnections attempts. Default is 1000 ms. |
ACS_BUS_MAX_BACK_OFF_DURATION | Maximum duration of back-off re-connection attempts for unlicensed connection. (Default 600000 ms) |
ACS_BUS_BACK_OFF_JITTER | Back-off reconnection jitter for unlicensed connection. (Default true) |
ACS_SECURITY_TRUST_SELF_SIGNED | Whether to trust (true) or not trust (false) to self signed certificates. Default is true. |
Debugging the Secure Gateway Connection
The Avid Connector API and the Secure Gateway have an internal failover logic to validate the connection between them. If you are debugging your service in an IDE or on the command line, a breakpoint can block your service from receiving the information it needs to know that it is still connected to the Secure Gateway. To prevent the service from thinking it has lost its connection to the Secure Gateway, and thus failing over into reconnection mode, you should set the variable ACS_GATEWAY_CONNECTION_LOST_THRESHOLD=600000 (10 minutes). This should prevent the service from thinking it has lost connection while giving you enough time to inspect the information you need at the breakpoint.
1 | static void Main() |
Connection Handler
If your application needs to react when a connection to the Avid Platform established/lost, pass in an implementation of an IConnectionSubscriber
interface.
1 | namespace Avid.Platform.Bus |
Threading Model
The Avid Connector API for .NET uses the .NET Core managed thread pool for all message processing e.g. service operations/requests, responses to query operations and channel messages.
There is only one thread pool per process.
Using the Avid Connector API as a Client
The Connector API can be used to send requests to services, and to subscribe and publish to channels. Requests and responses use the Message
interface.
Providing Message Options
With each message operation (e.g. query, send, broadcast) you may provide message options, via a MessageOptions
object. The following options are available:
- Timeout - Specifies a message timeout in ms for query operations. Default is 10000ms.
- Durable - Sets whether message is durable. Default is false. NOTE: This option is currently not implemented, and will be revised in the future releases.
- AnyCompatibleVersion - Sets whether the message is delivered to any compatible version of the service or to an exact version of the service. Default is true.
Querying Services
All queries to services should be performed asynchronously. To execute an asynchronous query, pass an implementation of IAsyncQueryResult
. The API contains a helper class that implements IAsyncQueryResult
via delegates.
1 | var msg = new Message("avid.acs.calculator", "global.test", 3, "echo"); |
The onError
delegate is only called when there are OperationError
-type errors. The Message
passed to onSuccess()
will contain any errors that occurred while executing the service operation.
Sending to Services
Sending to a service is a one-way communication from the client to the service. There is no response. In comparison to a broadcast (described below), the client is also guaranteed that no more than one service instance will process the message.
1 | // ... |
Broadcasting to Services
Broadcasting to a service is a one-way communication from the client to all instances of a given service. There is no response. As opposed to a send, the message is processed by every available instance of the service.
1 | // ... |
Remote Zone and Multi-Zone Communications
The default behavior of the Avid Connector API is to communicate within its own local zone. All the examples provided above use this default behavior. If the local zone has been initialized in a multi-zone environment, however, it is possible communicate with services in other zones.
Zone-Specific Communications
To communicate with a specific remote zone, use the bus.Zone(String zoneID)
object. The following are examples of communications with other zones:
1 | // ... |
In all of the above cases, only services in the remote zone with an ID of 5b2123f1-3f8e-4fcb-9263-f7b98bbdab0c
are invoked. In addition, only service instances registered with a scope of multi-zone
are considered.
Multi-Zone Communications
To communicate across multiple zones, use the bus.MultiZone
object. The following are examples of multi-zone communications:
1 | var request = new Message("example.service", "global", 1, "doSomething"); |
Note that the last parameter passed to the Service
attribute declares that the service with the given version can handle requests targeted to other compatible versions. In this case, version 1 of the avid.platform.bus.examples.service.calculator
service can also handle requests targeted to version 0.
The following code snippet illustrates how to create a connection to the Avid Platform, and register an instance of the service on it:
1 | static void Main() |
The ServiceOptions
class also has a StartSuspended
property, which will register the service in ServiceStatus.Suspended
mode (a Suspended
status indicates that the service is visible on the platform but will not receive any requests). It is expected that after registering in suspended mode, the service will eventually transition to an Ok
status (typically during the processing of ServiceBase.OnServiceConfiguration
or ServiceBase.OnRegistered
).
Since StandaloneService
derives from ServiceBase
, note the following StandaloneService
useful properties:
1 | namespace Avid.Platform.Bus.Service |
Structured Errors
A service must declare its complete list of possible error codes via the Error
attribute. The Error
attribute constructor accepts many parameters.
1 | public ErrorAttribute(string code, int status, ErrorSeverity severity, string messageTemplate, string description); |
code
is part of the service’s public API and therefore the format of thecode
should be a short sentence in uppercase with an underscore symbol used as a separator, e.g. BAD_REQUEST, BULK_DUPLICATION, MISSING_ARGUMENT, QUOTA_EXCEEDED.status
parameter is the appropriate corresponding HTTP error code.messageTemplate
is text in en_US locale, that may include%{identifier}
placeholders for error message parameters. ThemessageTemplate
(and any parameters) should convey meaningful information e.g. “Quota on %{resourceName} exceeded for %{projectName}.”
An error response to an operation is done by passing an Error
object or an IErrorSet
to the IOperationContext::Error
method.
1 | [0 } )] ] { |
The description
parameter may contain an elaborate description of an issue for internal use only that will only appear in error logs.
Exceptions can also be used to send an error response. The API will automatically transform a BusServiceException
or any exception that contains API specific key/value pairs in the Exception.Data
property (keyed using the strings listed in the static ExceptionKeys
class) into an Error
object and call IOperationContext.Error
. For example:
1 | using Avid.Platform.Bus.Exceptions; |
Multi-Zone Services
The default behavior of the Avid Connector API is to register services in the local zone scope. This means that by default services only receive requests from clients within the same zone. If the local zone is initialized in a multi-zone environment, however, it is possible to register a service in the multi-zone scope. This allows the service to be invoked by clients in any connected zone.
To register a service in the multi-zone scope, use the bus.MultiZone
object:
1 | bus.MultiZone.RegisterService(new CalculatorService(), new AnonAsyncOpResult() { |
Local Zone Scope
Registering a service using the bus.LocalZone
object is functionally equivalent to registering it using the base bus
object. The service is only accessible within the local zone.
Attributes
This section presents the attributes you can associate with a service’s class implementation.
Service Attributes
Attribute Type | Description |
---|---|
Service | Indicates the service type, version, and description. |
Examples | Specifies example arguments for operations. You can provide examples by creating a new embedded resource for your project and specifying the parameter set for each operation. |
Error | Specifies any error the service may produce. |
Operation Attributes
Attribute Type | Description |
---|---|
Operation | Exposes service operations. |
RestRequest | Annotates methods to expose a specific name as a REST request. |
Return Value Attributes
Use the Result
attribute to annotate a method’s return value, to expose a specific name as part of a service API.
Receiving or Returning the Entire Message Object in the Operation
Most of the time service operations accept and return simple types or objects, but a service method may need to analyze the entire Message
object, or need access to a parameter that is not easily de-serialized into a .NET type, or simply wants to return an entire Message
.
1 | public class Person |
The above code snippet could be used to yield result set similar to the following:
"resultSet": {
"person": {
"serviceType": "echoer,
"serviceRealm": "global",
"serviceVersion": 4,
"op": "echoPersonInMessage",
"resultSet": {
"person": {
"FirstName": "Tom",
"LastName": "Brady"
}
}
}
}
Note that eliminating the [return: Result("person")]
attribute, yields a very different result set:
"resultSet": {
"person": {
"FirstName": "Tom",
"LastName": "Brady"
}
}
Avid Platform Service Events
Your service can indicate an interest in OnRegistered
/OnUnregistered
notifications, by re-implementing the corresponding methods in ServiceBase
. In addition, there are IChannelMessageSubscriber
and IConnectionSubscriber
for channel and connection-related “events”.
Service Configuration Changes
In order to receive OnServiceConfiguration
notifications, a value of true
must be set in the ServiceOptions.RequestServiceConfiguration
property that is passed to the RegisterService
method.
When overriding ServiceBase.OnServiceConfiguration
it is a best practice to always set the status of the service based on whether the processing of the configuration was successful.
1 | public override void OnServiceConfiguration(ServiceConfigurationEventArg configArg) |
When a service’s configuration changes, the OnServiceConfiguration
method is called. The current configuration is passed as an IDataSet
.
Providing Service Status
When a service is registered, it starts sending heartbeats to report its current status (in the background). The reported status contains a status code.
By default, as long as the process is still running, the Avid Connector API reports an “OK” status. While this may be acceptable for simple services (like our calculator example), more complex services can explicitly supply their own status information. The .NET Connector API supports 5 status statues (OK, WARNING, ERROR, SUSPENDED, OFFLINE). Each state has its own behavior which define whether the service is still visible on the platform, and if it is able to recieve messages. See the table below to see the behavior defined by each status.
Status | Visible on Platform | Receives Requests | Example Use Case |
---|---|---|---|
OK | Yes | Yes | Service is fully functional (default state) |
WARNING | Yes | Yes | Service is still fully functioning, but want to warn about some resource (ie. high memory useage, large db latency, many timeouts to another service) |
ERROR | Yes | No | Service is not functioning properly, needs some intervention to fix (ie. ran out of memory, but wish to keep the process alive for debugging) |
SUSPENDED | Yes | No | Service is ok, but cannot function properly due to an external resource (ie. DB connection lost and service cannot proceed without persisting data) |
OFFLINE | No | No | Service process should stay alive, but should not be visible or routed to (ie. Keep service process alive during DB migration) |
To set the status for a service, use the SetStatus
method of ServiceBase
.
1 | transcodeService.SetStatus(ServiceStatus.Offline, new AnonAsyncOpResult() { |
Providing Custom Service Health Information
One of the core operations provided by the Avid Connector API is serviceHealth
. By default if service receives this operation request it replies back with such message in the resultSet
:
1 | { |
Service developers may override the serviceHealth
operation by providing a specific healthStatus
or additional customHealthInfo
. In this case serviceHealth
response may look like:
1 | { |
A service may choose to override the implementation of the serviceHealth
operation by annotating a method with the HealthCheck
attribute that accepts either a IHealthCheckReporter
or IHealthCheckReporter<T>
parameter:
1 | [ ] |
In this case the serviceHealth
response would look like:
1 | "resultSet": { |
Providing Compatible Version
Services on the Avid platform provide the concept of ‘Compatible Versions’ to provide a way to remain backwards compatibility with older versions of your service. When you declare that a newer version of your service is compatible with previous versions, it gains the ability to receive messages which were sent to an older version. This way clients who are sending messages to an older version of your service, will get a valid response, even if there are no instances of the old service running. For example, if we declare a new version of our service to be version == 3, but we also declare that it is compatible with versions 2 & 1, then any message which is sent to version 1, 2, or 3 of your service will be routed to version 3 if there is no running instance of your service in the specified version. To declare your service as compatible with other versions we add a comaptibleVersions array as the last parameter to the ServiceAttribute.
1 | [2, 1 })] ] { |
Using the Avid Connector API for Channels
Channels are analogous to Java Message Service (JMS) API Topics. When a message is posted to a channel, it is broadcast as a one-way communication to all the subscribers listening to that channel. It is important to note that channel messages are not persisted, and if you post to a channel that has no subscribers, it will not result in an error.
Channels are identified by their name. Subscribers interested in listening on a channel must either receive the channel name from the service that owns the channel, or use a predefined or well-known channel name.
Note: The names of channels should be namespaced and end with a past-tense verb. The reason for using past tense is, generally speaking, channel messages convey “facts” - i.e. notifications of something that has already happened. For example, avid.protools.project.status.changed.
Posting to a Channel
Channel messages are very similar to regular Avid Platform messages, but rather than have Parameters
or Results
, they simply have Data
. The IBusAccess
API provides a method for posting to a channel:
1 | var channelMessage = new ChannelMessage(channelName, subject); |
NOTE: Once a channel is registered, anyone knowing the channel name can post to it. Although the poster is often the creator of the channel, this is not enforced in any way. This treatment is subject to change in a future edition of the API.
Subscribing to a Channel
Objects that need to subscribe to channels can do so by subscribing through the IBusAccess
interface. First, a channel subscriber must implement the IChannelMessageSubscriber
interface:
1 | public interface IChannelMessageSubscriber |
A ChannelSubscriber
can be subscribed to a channel using SubscribeToChannel()
in IBusAccess
. Note that a single subscriber may subscribe to multiple channels at once. There is enough data in each of the subscriber methods for the subscriber to identify the channel to which the message pertains.
Shared Channels
Clients can also subscribe to a shared channel by shared name, channel name and bindings. In this case, when multiple instances are subscribed to the same channel with the same shared name, only one instance will receive each message.
1 | bus.SubscribeToChannel(channelName, mySubscriber, new ChannelOptions("shared.name"), new AnonAsyncOpResult() { |
Using Bindings
Bindings can be used to filter which messages are sent to subscribers. When subscribing to a channel, pass along a list of bindings to specify which messages are relevant to the subscriber:
1 | var bindings = new List<String>(); |
The binding string will filter messages based on their dot-separated subject. The ‘*‘ character can be used as a wildcard to match entire words. For example, “com.*.info” matches “com.MapService.info” but “com.Map*.info” does not.
Unsubscribing from Bindings
To unsubscribe your subscriber from specific bindings:
1 | var bindings = new List<String>(); |
Unsubscribing from a Channel
To unsubscribe your subscriber from specific channel:
1 | bus.UnsubscribeFromChannel(loggingChannelName, mySubscriber, new AnonAsyncOpResult() { |
To unsubscribe your subscriber from all channels:
1 | bus.UnsubscribeFromChannels(mySubscriber, new AnonAsyncOpResult() { |
Remote Zone and Multi-Zone Channel Communications
The default behavior of the Avid Connector API is to scope channel communications within the local zone. All the examples given above use this default behavior. If the local zone has been initialized in a multi-zone environment, however, it is possible to communicate using channels across multiple zones.
Interacting with Channel Subscribers in a Specific Remote Zone
To communicate with channel subscribers in a specific remote zone, the bus.Zone(String zoneID)
object should be used. The following examples send channel events and messages to subscribers in a specific zone only:
1 | bus.Zone("5b2123f1-3f8e-4fcb-9263-f7b98bbdab0c").PostToChannel(channelMessage, new AnonAsyncOpResult() { |
In the above case, only multi-zone subscribers to the channel in the remote zone with ID 5b2123f1-3f8e-4fcb-9263-f7b98bbdab0c
receive these channel events and messages. If there are local scope subscribers listening on the same channel in that zone, they do not receive the messages (since they only receive messages sourced from their local zone).
Interacting with Channel Subscribers in All Zones
To communicate with channel subscribers in all connected zones, use the bus.MultiZone
object.
1 | bus.MultiZone.PostToChannel(channelMessage, new AnonAsyncOpResult() { |
In the above example, all multi-zone subscribers to the channel across all connected zones receive the channel events and messages. The local scope subscribers in the poster’s zone also receive the messages. The local scope subscribers in remote zones, however, do not receive the messages.
Subscribing to Multi-Zone Channels
If the local zone has been initialized in a multi-zone environment, channel subscribers can listen using the multi-zone scope. This means that they can receive channel messages from posters in remote zones.
To subscribe to a channel in the multi-zone scope, use the bus.MultiZone
object:
1 | bus.MultiZone.SubscribeToChannel(channelName, mySubscriber, new AnonAsyncOpResult() { |
Local Zone Communications
Note that there are also channel methods in the bus.LocalZone
object. Invoking channel methods on this object is functionally equivalent to invoking the same methods on the base bus
object.
Accessing Constants
The Avid Connector API provides constants, which represent core Avid Platform service types (e.g. registry, federation and attributes). To access these constants:
1 | using Avid.Platform.Bus.Messaging.CoreServices; |
Exposing Operations as REST Requests
To declare that an operation supports a REST request, apply the RestRequest attribute to corresponding operation:
1 | [ ] |
The RestRequest annotation has the following parameters:
- path - Required;
- method - Not required, default GET;
- queryParams - Not required, default *;
- bodyParam - Not required, default *.
For more detailed information about how REST requests are mapped and delivered to your service,
please view the upstream HTTP docs.
Migrating to the Asynchronous Methods of IBusClient
In version 2.3 of the .NET Connector API, asynchronous versions of the Query
, Send
, Broadcast
, and PostToChannel
methods were added to the IBusClient
interface.
Overloaded versions of the Query
method were added that accept an additional IAsyncQueryResult
parameter. A method of IAsyncQueryResult
will eventually get called whenever the Query
method completes:
1 | public interface IAsyncQueryResult : IAsyncOpResult { |
Overloaded versions of the Send
, Broadcast
, and PostToChannel
methods each accept an additional IAsyncOpResult
parameter.
Migrating to these asynchronous methods is easly done by implementing the desired delegates of the AnonAsyncOpResult
, AnonAsyncOpResult<T>
or AnonAsyncQueryResult
classes.
1 | public class AnonAsyncOpResult : IAsyncOpResult { |
For example:
1 | // ... |
And:
1 | // ... |
Migrating to IOperationContext, IChannelContext and IChannelSubscriber interfaces
In version 2.5 of the .NET Connector API, the IResponder
and IResponder<T>
interfaces have been replaced by the IOperationContext and IOperationContextIOperationContext.BusAccess
property. This instance must be used when making any subsequent calls to the bus during the processing of a service operation.
Migrating code from the IResponder
interface to IOperationContext
involves a simple “Search and Replace” text edit (IResponder -> IOperationContext) and replacing all calls to any private or global instance of IBusAccess
with a call to the IOperationContext.BusAccess
property. For example:
1 | public void SubmitJob(int jobId, int execTime, IOperationContext<NetServiceResponse> response) |
The IChannelSubscriber
interface has also been replaced with a context aware IChannelMessageSubscriber. The IChannelMessageSubscriber.OnChannelMessage
method is passed an instance of IChannelContext
, which has properties to access the incoming ChannelMessage
and the context aware instance of IBusAccess
. This instance must be used when making any subsequent calls to the bus during the processing of the channel message.