Kotlin/Java Backend

The ecos-records library is provided for working with RecordsAPI on the Kotlin/Java backend - https://github.com/Citeck/ecos-records

Once the library is connected, you can create a RecordsServiceFactory and obtain all services for working with RecordsAPI from it. Service initialization is encapsulated in RecordsServiceFactory and does not require a DI mechanism.

The main service for working with RecordsAPI is ru.citeck.ecos.records3.RecordsService. Usage example:

val serviceFactory = RecordsServiceFactory()
val recordsService = serviceFactory.recordsServiceV1

val value = HashMap<String, String>()
value["someKey"] = "someValue"

val attributeValue = recordsService.getAtt(value, "someKey").asText()
println(attributeValue) // someValue
RecordsServiceFactory serviceFactory = new RecordsServiceFactory();
RecordsService recordsService = serviceFactory.getRecordsServiceV1();

Map<String, String> value = new HashMap<>();
value.put("someKey", "someValue");

String attributeValue = recordsService.getAtt(value, "someKey").asText();
System.out.println(attributeValue); // someValue

Here we create a new map with one value and retrieve from it the attribute named someKey via RecordsService.

There are two main usage scenarios for RecordsService:

  • Working with pre-existing data as in the example above. No requests need to be sent anywhere, and attribute retrieval occurs within the service. In this mode, only attribute retrieval is available and Records DAOs are not involved.

  • Working with references (EntityRef). In this mode, the service interacts with data sources whose functionality is implemented via the following interfaces:

    • RecordsDao is the base interface for all others listed below. It contains only one method — String getId(), which is used when registering RecordsDao in RecordsService;

    • RecordsQueryDao for searching records;

    • RecordsAttsDao (RecordAttsDao) for retrieving attributes by pre-known record identifiers;

    • RecordMutateDao for creating or editing records;

    • RecordsDeleteDao (RecordDeleteDao) for deleting records;

Note

The parentheses show interface variants where the method receives only a single record identifier. These interfaces differ from the multi-record variant only in that you do not need to manually iterate over identifiers. However, if there are optimizations that can be applied when processing records in batch, the interfaces accepting record collections should be implemented.

Records DAO is an implementation of the abstract concept “Data Source”. One Records DAO can represent different data sources.

When working with Records DAO, the following occurs depending on the type of action:

  • Query. We pass a search query to RecordsQueryDao and expect the following output value types (both collections and single instances are supported):

    • EntityRef — entity references. If we receive references, the service calls the corresponding RecordsAttsDao to retrieve attributes;

    • String — a text result means we returned record identifiers for which attributes need to be retrieved via RecordsAttsDao. If no other Records DAO is specified in the string, the same one from which query was called is used;

    • RecsQueryRes — a list of records together with data about their total count;

    • Any — any other value, which is processed using implementations of the AttValueFactory interface;

  • Get attributes. Retrieving attributes by record identifiers. This method is used either with the Query result from the previous item or via a direct call to recordsService.getAtts(...) The method returns any value processed using implementations of the AttValueFactory interface;

  • Mutate. Modifying or creating records via RecordMutateDao.

    In Records API, a record is created by mutating a record with an empty local identifier. That is, if we want to create an entity in the emodel microservice in the types-repo data source, we do the following:

    // здесь следует обратить внимание на строку 'emodel/types-repo@'.
    // Согласно структуре RecordRef'а (ссылка внизу) здесь
    // (AppName - "emodel", SourceId - "types-repo", LocalId - "" (пустая строка))
    let newRecord = Records.get('emodel/types-repo@');
    
    newRecord.att("id", "id-value");
    newRecord.att("name", "Custom name");
    // В resultRecord будет созданная запись.
    // Если мы задаем id вручную (как двумя строчками выше),
    // то в resultRecord будет лежать то же самое что мы получим при выполнении
    // Records.get('emodel/types-repo@id-value');
    // Если мы не указали вручную ID, то он сгенерируется в виде UUID.
    let resultRecord = await newRecord.save();
    
  • Delete. Deleting records via RecordsDeleteDao.

AttValue — an interface that represents a value that RecordsService can work with when retrieving attributes. Interface methods:

Promise<?> init() // инициализация значения перед тем как начать вычисление атрибутов
Object getId() // идентификатор значения. Может быть как строкой, так и EntityRef
Object getDisplayName() // значение для скаляра "?disp"
String asText() // значение для скаляра "?str"
Object getAs(String type) // значение для спец. атрибута "_as"
Double asDouble() // значение для скаляра "?num"
Boolean asBoolean() // значение для скаляра "?bool"
Object asJson() // значение для скаляра "?json"
Object asRaw() // значение для скаляра "?raw"
Object asBin() // значение для скаляра "?bin"
has(String name) // значение для спец. атрибута "_has"
Object getAtt(String name) // получить значение атрибута по его имени
AttEdge getEdge(String name) // получить мета-информацию об атрибуте по его имени
Object getType() // получить ECOS тип значения

AttValueFactory — an interface for converting arbitrary data types into an AttValue implementation.

// Проинициализировать фабрику. В основном используется для получения конвертеров для других типов.
// Например: attValuesConverter.getFactory(DataValueAttFactory.class)
void init(attValuesConverter: AttValuesConverter)

// Получить реализацию AttValue для значения
AttValue getValue(T value)

// Получить список доступных типов значений, которые может обрабатывать данная фабрика
List<Class<*>> getValueTypes()

// Получить приоритет фабрики. Чем выше приоритет, тем важнее фабрика в случае если для одного и того же типа нашлось две фабрики.
int getPriority()

To register custom AttValueFactory instances, create the following file in the library or microservice:

resources/META-INF/services/ru.citeck.ecos.records3.record.atts.value.factory.AttValueFactory

This file must contain the fully qualified class name (including the package) of your AttValueFactory interface implementation.

Example: https://github.com/Citeck/ecos-records/blob/master/ecos-records/src/test/resources/META-INF/services/ru.citeck.ecos.records3.record.atts.value.factory.AttValueFactory

If no suitable AttValueFactory is found for a value, the default BeanValueFactory is used. This factory treats the value as a bean, looking for getters for attributes.

For example, if we have the following bean:

static class TestDto {
  private String field;
  void setField(String value) {
    this.field = value;
  }
  String getField() {
    return field;
  }
}

From the perspective of BeanValueFactory, this bean has a value with one attribute “field”. Usage example:

RecordsServiceFactory serviceFactory = new RecordsServiceFactory();
RecordsService recordsService = serviceFactory.getRecordsServiceV1();

TestDto value = new TestDto();
value.setField("field-value");

String attributeValue = recordsService.getAtt(value, "someKey").asText();
System.out.println(attributeValue); // field-value

If we want to change the attribute name without renaming the methods, we can use the AttName annotation:

static class TestDto {
  private String field;
  void setField(String value) {
    this.field = value;
  }
  @AttName("otherName")
  String getField() {
    return field;
  }
}
...
TestDto value = new TestDto();
value.setField("field-value-2");

String attributeValue = recordsService.getAtt(value, "otherName").asText();
System.out.println(attributeValue); // field-value-2

The @AttName annotation allows setting an arbitrary attribute name. It can be used:

  • On a getter, to assign an arbitrary name to an attribute;

  • On a setter for converting DTO -> attribute schema for a query; (see methods recordsService.getAtts(Any record, Class<?> atts));

  • An annotation on a field works for both the setter and the getter if they exist;

The @AttName annotation can accept "..." as an argument. This notation means that all attributes from the field with this annotation will also be accessible in our value. Example:

static class ParentDto {
  @AttName("...")
  private ChildDto child = new ChildDto(); // опустим сеттер, чтобы не усложнять пример
  public ChildDto getChild() {
    return child;
  }
}
static class ChildDto {
   public String getValue(): String {
     return "abc"; // геттер не обязательно должен отдавать значение поля. Его поведение может быть произвольным
   }
}
...
ParentDto value = new ParentDto();

// Если бы аннотация AttName отсутствовала, то до значения 'abc' мы бы могли добраться так:
// recordsService.getAtt(value, "child.value").asText();
// Но с аннотацией @AttValue("...") можно обращаться к вложенному атрибуту так:

String attributeValue = recordsService.getAtt(value, "value").asText();
System.out.println(attributeValue); // abc

Also of special significance are AttName annotations where one of the question-mark scalars is specified as the argument. For example: @AttName("?str"). Such getters are called when loading scalars.

BeanValueFactory also searches the bean for a number of special methods by their name and arguments (the return type does not matter):

Object getId() // значение для скаляра ?id
Object getAsStr() // значение для скаляра "?str"
Object getAsNum() // значение для скаляра "?num"
Object getAsBool() // значение для скаляра "?bool"
Object getAsJson() // значение для скаляра "?json"
Object getAsRaw() // значение для скаляра "?raw"
Object getAsBin() // значение для скаляра "?bin"
Object getEcosType() // значение для атрибута "_type"
Object getAs(String name) // значение для спец. атрибута "_as"
Object has(String name) // значение для спец. атрибута "_has"
Object getEdge(String name) // значение для спец. атрибута "_edge"
Object getAtt(String name) // Значение атрибута по имени если не получилось найти геттер для него

For the display name of our bean, BeanValueFactory searches for the following methods in descending priority order (the first found is used):

Object getDisplayName()
Object getLabel()
Object getTitle()
Object getName()

Data Types in Kotlin/Java

ObjectData set/add Methods

set — sets a value by key. Equivalent to the put method in Map.

add — adds an element to an array. ObjectData itself is an equivalent of Map, and add has little direct meaning for it, but add can accept a path to a nested array as its first argument.

For example:

ObjectData data = ObjectData.create();
data.set("arr", new LinkedList<Object>());
data.add("$.arr", "new-element");

The $ format is the JsonPath standard.