Skip to main content

Custom Services in Ember

This document describes how to use custom factories with Ember components.

Ember configuration is defined by a special configuration file, as described in the Ember Configuration Reference document. Many Ember components read their configurations using the factory pattern described below.

Factory Class

To deploy your custom service, you need to create a factory class for it.

Here's an example:

public class MyCustomServiceFactory implements Factory<Service> {

private String foo;

public void setFoo(String foo) {
this.foo = foo;
}

@Override
public Service create() {
return new MyCustomService(foo);
}
}

This example demonstrates the implementation of a factory class, MyCustomServiceFactory, which is responsible for creating instances of a custom service, MyCustomService. It implements the Factory interface and includes a setFoo method to set a configuration property, foo, and a create method to instantiate the custom service.

You can then add the following stanza to your ember.conf file to deploy your custom service. You can define multiple custom services using this approach:

customServices = [
{
factory = deltix.custom.example.MyCustomServiceFactory
settings {
foo: Hello1
bar: 1
}
},
{
factory = ...
settings {
...
}
}
]

The example above, each custom service object in the customServices array specifies the factory class to be used and its associated settings.

Custom Service

In this section, we'll explore the code that defines the MyCustomService class and its associated methods. This service is designed to adhere to the Service interface and provides methods for retrieving a service name and an encoded ID.

class MyCustomService implements Service {

MyCustomService(String foo) {
}

@Override
public String getName() {
return "TEST";
}

@Override
public long getId() {
return AlphanumericCodec.encode(getName());
}
}

The example above includes the following methods:

  • MyCustomService(String foo) - This is the constructor of the MyCustomService class. It takes a parameter foo, but it does not show any further usage of this parameter within the constructor.

  • public String getName() - This method is part of the Service interface and is overridden in the MyCustomService class. It returns the string "TEST".

  • public long getId() - This method is also part of the Service interface and is overridden in the MyCustomService class. It uses an AlphanumericCodec to encode the name obtained from the getName() method and returns the encoded value as a long.

Lifecycle Events

In your custom service, you have the option to redefine the following events:

  • open() – This event it called on Ember startup.
  • close() – This event is called on Ember shutdown.

For example, these callbacks can be used to create TimeBase cursors during startup and close them at the end to prevent resource leaks.

By redefining these events, you can incorporate custom functionality into your service's lifecycle, ensuring that necessary operations are performed when Ember is initialized and gracefully terminated when it shuts down.

Concurrency

In Ember, the standard approach to concurrency follows the "share nothing" principle. However, custom services can be injected into each other and other custom API components. Injectable services should be thread-safe.

Dependency Injection

Ember provides basic support for dependency injection, a feature frequently used to inject custom services into various components like transformers, algorithms, and connectors.

Let's imagine you want to provide an implementation of the service interface Foo and inject it into an algorithm.

  1. Define a MyCustomService interface with a getGreeting() method.

    interface MyCustomService {
        String greeting();
    }
  2. Implement the MyCustomService interface as a service.

    class MyCustomServiceImpl extends AbstractService implements MyCustomService {

    private final String greeting;

    public MyCustomServiceImpl(String name, String greeting) {
    super(AlphanumericCodec.encode(name), name);
    this.greeting = greeting;
    }

    @Override
    public String getGreeting() {
    return greeting;
    }
    }
  3. Create a MyCustomServiceFactory service factory. This class is responsible for generating instances of MyCustomServiceImpl. The factory registers the custom service in Ember’s Bean Injection Service.

    public class MyCustomServiceFactory implements Factory<MyCustomServiceImpl> {

    @Inject
    private BeanInjectionService injector;

    @Required
    private String greeting;

    public void setGreeting(String greeting) {
    this.greeting = greeting;
    }

    public void setInjector(BeanInjectionService injector) {
    this.injector = injector;
    }

    @Override
    public MyCustomServiceImpl create() {
    MyCustomServiceImpl result = new MyCustomServiceImpl("MYGREET", greeting);
    injector.put(result, MyCustomService.class); // register this custom service in the Injector
    return result;
    }
    }
  4. Inject the MyCustomService into an algorithm factory. The algorithm can then express the desire to use a Foo service.

    public class MyAlgorithmFactory implements AlgorithmFactory {

    /** Here we inject custom service */
    @Inject
    private MyCustomService customService;

    public void setCustomService(MyCustomService customService) {
    this.customService = customService;
    }

    @Override
    public Algorithm create(AlgorithmContext context) {
    return MyAlgorithm(customService);
    }
    }

Predefined Injectable Services

  • com.typesafe.config.Config
  • deltix.ember.app.ThreadFactories
  • deltix.ember.service.price.api.PricingService
  • deltix.calendar.providers.TradingCalendarProvider
  • deltix.qsrv.hf.tickdb.pub.TickDB
  • deltix.qsrv.hf.tickdb.pub.WritableTickDB
  • deltix.qsrv.hf.tickdb.pub.DXTickDB
  • deltix.ember.service.InstrumentSnapshot
  • deltix.ember.service.BeanInjectionService

Limitations

  • The current version does not support sending journaled messages to or from custom Services.

Appendix: Example of a Custom Service

This section demonstrates how to customize the connection to TimeBase in Ember and explains how to apply similar customizations to other components.

Ember’s default configuration includes the following stanza that defines the TimeBase connection:

timebase {
factory = "deltix.ember.app.RemoteTimebaseFactory"
settings {
url = null
username = null
password = null
}
}

Notice that the configuration consists of two parts:

  1. The factory class name.
  2. The factory settings to be passed to the factory class.

Ember users typically modify the settings as follows:

timebase.settings.url = "dxtick://timebase.svc:8011"  

To customize the TimeBase connection creation, you can define your own custom factory. The custom factory must implement the deltix.anvil.util.Factory interface:

package deltix.anvil.util;  

public interface Factory<T> {   
T create();
}

Additionally, the factory must follow the JavaBeans (POJO) naming convention and define setters for all required parameters. Below is the source code of the default TimeBase factory:

public class RemoteTimebaseFactory implements Factory<DXTickDB> {

private @Optional String url;
private @Optional String username;

@Hashed
@Optional
private String password;

public String getUrl() {
return url;
}

public void setUrl(final String url) {
this.url = url;
}

public String getUsername() {
return username;
}

public void setUsername(final String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(final String password) {
this.password = password;
}

@Override
public DXTickDB create() {
if (CharSequenceUtil.isEmptyOrNull(url))
return null;

if (username == null || password == null) {
return TickDBFactory.createFromUrl(url);
}

return TickDBFactory.createFromUrl(url, username, password);
}
}

A few clarifications about the source code:

  • As you can see, the factory must implement a create method that returns the required service type.
  • The factory properties (settings) can be annotated with @Optional to mark them as optional. Otherwise, Ember will complain about missing factory settings parameters.
  • Password-like fields should be annotated with @Hashed. This indicates that rather than plain text, a config file will have a hashed value. The special utility ember/bin/mangle can be used to hash raw passwords. In addition, Ember supports integration with HashiCorp Vault and AWS Secrets Manager for these values, in which case the value stores secret store path.

Custom Factory Example

Continuing with the previous example, let’s create a custom TimeBase factory and define the configuration to use it.

Our custom implementation will add support for the Entitlement ID (used to manage Data Access Control for providers like Bloomberg and Reuters):

package deltix.ember.sample;

import deltix.anvil.util.annotation.Required;
import deltix.qsrv.hf.tickdb.pub.DXTickDB;
import deltix.ember.app.RemoteTimebaseFactory;

public class CustomTimebaseFactory extends RemoteTimebaseFactory {
@Required
public String entitlementId;

public String getEntitlementId() {
return entitlementId;
}

public void setEntitlementId(String entitlementId) {
this.entitlementId = entitlementId;
}

@Nullable
@Override
public DXTickDB create() {
DXTickDB result = super.create();
EntitlementService.initializeTimeBaseClient(result); // do something custom
return result;
}
}

To register this factory, you need to specify the custom factory class name and define all required properties:

timebase {
factory = "deltix.ember.sample.CustomTimebaseFactory"
settings {
url = "dxtick://localhost:8011"
entitlementId = "TOKEN-E802CCAF-999F-4655-8C06-967B5999058F"
}
}