App services
Finsemble is built around the concept of desktop services, or microservices that provide functionality to the desktop. In the previous versions of Finsemble you could create your own custom services. In Finsemble 7.0, these have been replaced by app services, which are easier to implement and are better integrated with FDC3.
What is an app service?
An app service is a special kind of app that works in the background to perform actions on behalf of or to provide services to other apps. This means that app services allow you to easily extend the smart desktop infrastructure. For example, an app service might fetch data from a resource, such as a database or a storage server, own a websocket connection that can be shared between multiple apps, or provide logging services for transactions or trades for multiple other apps.
Because an app service is an app, it is implemented using the same technology as any other app. It uses the same APIs and complies with the same FDC3 requirements. As a result, you don’t need to learn special skills to create an app service.
Resource reuse is the most common reason to have an app service. When the service interacts with a server or owns a connection, you free up other apps from having to do so, thus simplifying their codebases. Another common reason to create an app service is because it is always running and can handle events, notifications, FDC3 intents etc. without having to be started by the user. Instead, it starts automatically on its own, and then waits until it’s needed. Also, it doesn’t normally interact with humans, so it doesn’t need any UI.
If you have a lot of app services, you might care about the order in which they start up. Let’s look at how to specify the order you want. But before we can do that, we need to look at the boot sequence.
The boot sequence
The Finsemble boot sequence consists of stages. If you are familiar with the unix-style boot process, we use similar stages. Finsemble startup always moves sequentially through these startup stages:
microkernel
-- the essential tasks and services start (such as the routerService, configService, loggerService).kernel
-- core services start (for example,windowService
,workspaceService
).pre-authentication
-- optional stage for app services that need to be started before authentication.authentication
-- the user logs in and is authenticated.system-preuser
-- more Finsemble services start. We strongly recommend that you don’t place anything there. If you do, some of our services might not be ready yet and whatever you put here might not work.preuser
-- the default stage for app services.preuser2
-- optional stage for apps that depend on the apps from the preuser stage.preuser3
– optional stage for apps that depend on the apps from thepreuser
and/orpreuser2
stages.preuser4
-- optional stage for apps that depend on the apps from the earlierpreuser
stages.earlyuser
-- the first tier of components start, such as the Finsemble UI components.user
– everything else is started here and the workspace is restored. This is the default for regular apps that havespawnOnStartup: true
set.
All the stages are executed even when empty. That’s why you can see them all in the {system log}(/docs/7.x/troubleshooting/CentralLogger) even if you don’t place anything in these stages.
Waiting for an app service to be ready
The startup process completes each stage, launching every app and service assigned to it, and waiting until each is ready, before the process moves on to the next stage. By default, an app (or app service) is considered ready when its window has been created. But apps might have their own initial requirements such as completing a handshake or connecting to a database or other remote service. Finsemble isn't aware of such requirements, but can be configured not to assume the application is ready if the window has been created. Instead, it waits for the app to let Finsemble know it’s ready via an API call and will not proceed to the next boot stage until it has been received.
To configure Finsemble to wait for an app service's ready signal, set the waitForInitialization
property to true like this:
"appd": {
"appServiceA": {
"appId": "appServiceA",
"name": "appServiceA",
"description": "Test App Service",
"manifest": {
"appService": true,
"waitForInitialization": true,
"window": {
"url": "<your_URL>/appServiceA/appServiceA"
},
},
"version": "1.0.0",
"contactEmail": "info@cosaic.io",
"supportEmail": "support@finsemble.com",
"publisher": "Cosaic"
}
}
Then to signal that your app service is ready:
// Finsemble's startup sequence is paused if this app has `waitForInitialization` set to true
// Perform some initialization tasks, wait for events, etc, then publish
ready FSBL.publishReady();
// Now Finsemble will continue its startup sequence
Controlling the boot sequence
A Finsemble deployment can have multiple app services. If that’s what you need, take a look at how these services work together. Some services might be independent of any others. For these services, the startup order doesn’t matter. In other cases, there might be dependencies amongst them, which need to be handled carefully (that is, the startup order matters).
Finsemble provides 2 mechanisms for controlling the startup order. Both are specified in the bootParams
parameter.
Boot stages
The simplest mechanism to handle dependencies between app services is to specify the stage of the boot process in which each is launched. Here is an example for specifying the latest preuser
stage like this:
`"stage": "preuser4"`
This way, you can ensure that an app service is available to another app or app service started at a later stage that depends on it.
Dependencies
Using boot stages to separate dependencies is the simplest mechanism to handle dependencies between services, and this technique is sufficient in the vast majority of cases, but sometimes it’s not enough. If you have a lot of dependencies, there might not be enough stages to separate the app services into. Because the order in which the app services start within each stage is undefined, to guarantee that your app services will start up in a specific order within a stage, you can use the dependencies parameter.
In this example for AppService-A, we specify 2 dependencies:
"bootParams": {
"stage": "preuser",
"dependencies": ["AppService-B", "AppService-C" ]
}
If you configure a dependency on another app service, Finsemble will create that app service's window first. If you configured Finsemble not to assume that that service is ready immediately, it will wait for a ready signal via the API. The signal must be received before app services that depend on it are started. We have already seen an example of this, FSBL.publishReady()
, in the previous section.
You don't need to, and in fact you can't, configure dependencies on services that were started in previous boot stages.
Startup boot parameters
Here is the complete list of bootParams
parameters that are supported in the startup config for app services:
stage
-- specifies the stage to start. The values for this parameters are the boot sequence stages we’ve seen in the previous section:
microkernel
kernel
pre-authentication
authentication
system-preuser
preuser
preuser2
preuser3
preuser4
earlyuser
user
dependencies
-- specifies one or more startup dependencies to satisfy before starting. Specify only the dependencies within the same stage by listing their appIDs.autoStart
-- specifies whether to automatically start during the system manager’s startup phase (defaults to true). The alternative is to explicitly start programmatically. This way, you can start the service when needed.
If you set autoStart
to false, most of the other bootParams
(stage
, dependency
, stopOnFailure
, timeout
) aren't relevant because the Service Manager is not managing the startup sequence for the app.
stopOnFailure
-- only relevant when there are dependencies, and defaults to true. Set it to false if you want to continue startup on failure. Specifically, set it to false if app service A depends on app service B and if B doesn’t complete, you still want to launch A.customFailureMessage
-- if startup fails, the specified message will be output to the system log.timeout
-- if startup doesn’t complete in this time, it’s marked a failure. The default timeout for all app services is set in the manifest's config underfinsemble.bootConfig.defaults.startComponentTimeout
.
Default config values
If no startup parameters are specified in a component’s config (or service’s config), Finsemble uses these default values:
"bootParams": {
"stage": "preuser", // (or “user” for apps that are not services or tasks)
"dependencies": [],
"stopOnFailure": true,
"autoStart": true,
"customFailureMessage": undefined,
}
Dependency checking
Dependencies are checked only within the stage for which they are specified (such as preuser
). If a dependency is specified on a previous stage, that dependency is automatically marked as satisfied. This frees you from having to know what stage a dependency is scheduled in. If a dependency is specified on a later stage, that dependency is automatically marked as failed.
Startup within each stage proceeds backwards along a dependency graph built from config. This results in a service with no dependencies being started first and a service with the most dependencies being started last.
Each startup stage has its own dependency graph separate from dependency graphs of any other stage. This is essential when considering the configuration data can dynamically change (such as based on authentication updates). For this reason startup will re-read configuration at the end of each stage, enabling dynamic config changes to be used in the next stage.
Error reporting in the System Log of startup errors is detailed enough to help diagnose the problem. Here’s the info you can find:
Early reporting of missing or illegal startup-config definitions.
Reports of any circular dependencies (that is, cycles in the directed dependency graph).
Reports of dependencies on operations scheduled for a later stage (such as a preuser startup operation depending on a user operation)
Startup failures within an individual app service either due to an explicit error or to a timeout error.
Example
Here is an example of an app service that connects to a data feed and listens for a ViewInstrument
intent. When this intent is raised, our app service asks for data from the feed. When the service receives data from the feed, it gets an app channel, or creates one if it doesn't exist. It then broadcasts the context to the channel.
// Connect to the market feed data API
ws.addEventListener('open', () => {
Logger.system.log("Connected to market data feed");
ws.send(JSON.stringify({type: "auth", payload: `${APIKEY}`}));
});
// On message received
ws.addEventListener('message', async (messageObj) => {
Logger.system.log(`Message received: ${JSON.stringify(messageObj)}`);
// Get the symbol from the message
const {symbol} = messageObj;
// Get of create the app channel
const channel = await fdc3.getOrCreateChannel(`price_${symbol}`);
// Create and broadcast the context into the app channel
channel.broadcast(createContext(messageObj))
});
fdc3.addIntentListener("ViewInstrument", (context) => {
// on raiseIntent for fdc3.instrument request the ticker symbol to get price data
ws.send(JSON.stringify({type:"subscribe",payload:`${context.id.ticker}`}));
});
};
/**
* Invoked when FSBL has been injected and is ready to use.
*/
const FSBLReady = async () => {
connectToMarketDataFeed();
// Finsemble's startup sequence is paused if this app has `waitForInitialization` set to true
// Perform some initialization tasks, wait for events, etc, then publish ready
FSBL.publishReady();
// Now Finsemble will continue its startup sequence
};
if (window.FSBL && FSBL.addEventListener) {
FSBL.addEventListener("onReady", FSBLReady);
} else {
window.addEventListener("FSBLReady", FSBLReady);
}
Here is the app service config:
"myAppService": {
"appId": "myAppService",
"name": "myAppService",
"description": "Test App Service",
"manifest": {
"bootParams": {
"stage": "earlyuser",
"dependencies": [],
"stopOnFailure": true,
"autoStart": true,
"customFailureMessage": "My custom app service failure"
},
"appService": true,
"waitForInitialization": true,
"window": {
"url": "$applicationRoot/services/myAppService/myAppService.html"
}
},
"version": "1.0.0",
"contactEmail": "info@cosaic.io",
"supportEmail": "support@finsemble.com",
"publisher": "Cosaic",
"intents": [
{
"name": "ViewChart",
"displayName": "View Chart",
"contexts": ["fdc3.instrument"]
}
]
}