Citeck WebAPI

Citeck WebAPI is the primary low-level communication method between microservices within Citeck. Communication follows the Request → Response pattern synchronously.

The API for working with WebAPI is described in ecos-webapp-api (repository ecos-webapp-api).

The API implementation is located in ecos-webapp-lib (repository ecos-webapp-commons).

What Citeck WebAPI Provides

  1. Uniform error handling. WebAPI takes care of collecting information about an error and sending it to the calling party. The WebAPI client user will receive an EcosWebException and either handle it, or it will be passed to the next application in the call hierarchy;

  2. The ability to uniformly send requests of any size (from bytes to gigabytes) without worrying that either side will run out of memory. Stream processing allows requests of any size to pass through WebAPI;

  3. WebAPI eliminates the need to worry about sending structured data together with binary data. Both parts are supported within a single request;

  4. WebAPI abstracts the sender and receiver from the message delivery method. This allows transport capabilities to be improved independently without modifying application code. For example, the following system aspects are related to transport:

    1. Encryption. TLS configuration should not affect the operation of application code in any way;

    2. Transactionality. The transaction identifier and “stickiness” to specific microservice instances is handled at this level;

    3. Locale from the current context is propagated to the receiving side;

    4. Timezone from the current context is propagated to the receiving side;

    5. Client information (IP address) is propagated from the context to the receiving side;

    6. JWT token preparation and sending for authentication is handled at this level;

    7. Tenant information from the current context is propagated to the receiving side;

  5. WebAPI supports message compression, which increases data transfer speed in the presence of network latency;

  6. WebAPI allows libraries to implement client-server communication between applications without adding heavy dependencies such as Spring MVC or various HTTP clients;

  7. API versioning allows you to determine before executing a request whether it is supported on the receiving side. The support information also includes the range of supported versions. If the client can communicate with the /content/upload endpoint using versions 0–3, and the server supports versions 0–2, the client will send the request in the format of the maximum supported version (2). This mechanism allows the product to evolve without breaking API compatibility.

  8. For some requests (transaction commit), maximum speed is required, and the slightest delay can compromise data integrity in the system. In this case, using high-level APIs is not the best option.

  9. Asynchrony at the API level. All requests through Citeck WebAPI return a Promise, which can be used for asynchronous optimizations on the client side.

Example of client-side usage:

Java:

EcosWebClientApi webClient;
int apiVersion = webClient.getApiVersion("emodel", "/custom/request", 2); // 2 - максимально поддерживаемая версия. В ответе вернется версия <= 2
// Здесь должен быть код, который в зависимости от apiVersion подготовит тело запроса
// Если на данный момент предполагается, что есть только нулевая версия эндпоинта, то ничего делать не нужно. Сервер всегда будет получать версию 0 и будет работать с ней.
String result = webClient.newRequest()
    .targetApp("emodel") // целевое приложение
    .path("/custom/request") // целевой путь (эндпоинт) запроса
    .version(apiVersion) // если предполагается поддержка только нулевой версии, то эту строчку можно опустить
    .header("custom-1", "value-1") // произвольные структурированные данные, которые можно
    .header("custom-2", "value-2") // прочитать на принимающей стороне
    .bodyJ((writer) -> { writer.writeText("request-text"); })
    .executeJ((resp) -> { return resp.getBodyReader().readAsText(); })
    .get();

Example of server-side usage:

Java:

class CustomExecutor implements EcosWebExecutor {

    @Override
    public void execute(@NotNull EcosWebExecutorReq req, @NotNull EcosWebExecutorResp resp) {
        int apiVer = req.getApiVersion();
        // тут может быть логика для обработки разных версий API
        String requestText = req.getBodyReader().readAsText();
        resp.setHeader("custom-header", "custom-value"); // хидеры можно использовать для любых данных
        resp.getBodyWriter().writeText(requestText + "-response");
    }
    @NotNull
    @Override
    public Pair<Integer, Integer> getApiVersion() {
        return new Pair<>(0, 2); // поддерживаются версии от 0 до 2
    }
    @NotNull
    @Override
    public String getPath() {
        return "/custom/request";
    }
    @Override
    public boolean isReadOnly() {
        return false;
    }
}
...
EcosWebExecutorsApi executorsApi;
executorsApi.register(new CustomExecutor());

API Versioning

During initialization, the microservice collects information about all registered executors and writes this information to Zookeeper.

The microservice that wants to send a request retrieves information from Zookeeper and provides the sender with the ability to adjust its behavior based on the support status of a specific endpoint.

Request and Response Description

The request is formed according to the following pattern:

  1. Citeck WebAPI version (integer);

  2. Compression type for all subsequent content (integer);

  3. Size of compressed metadata in bytes including metadata type and compression type (integer);

  4. Metadata type (integer);

  5. Metadata compression type (integer);

  6. Metadata (depends on metadata type (4));

  7. Request body type (integer);

  8. Request body compression type (integer);

  9. Request body (depends on body type (7)).

The response is formed according to the same principles as the request, but without specifying the Citeck WebAPI version (1).

Request metadata structure:

Note

Field names do not matter if the metadata type is an array.

jwt: String // JWT токен с информацией о текущей аутентификации
path: String // Путь для обращения к нужному executor'у
version: Int // Версия запроса для executor'а
headers: DataValue // Заголовки запроса (произвольные структурированные данные)
routes: Map<String, String> // Роуты, которые следует использовать для внешних запросов. Ключ - имя приложения, Значение - идентификатор инстанса приложения
tenant: String // Тенант, в рамках которого выполняется запрос
tzUtcOffset: Duration // Часовое смещение относительно UTC
clientData: ClientData // Информация о клиенте (ip адрес)
txnId: TxnId // Идентификатор транзакции
locales: List<Locale> // Список локалей

Response metadata structure:

Note

Field names do not matter if the metadata type is an array.

headers: DataValue // Заголовки запроса (произвольные структурированные данные)
status: EcosWebRespStatus // Статус выполнения запроса. Поддерживается SUCCESS и ERROR
routes: Map<String, String> // Роуты, которые следует использовать для внешних запросов. Ключ - имя приложения, Значение - идентификатор инстанса приложения
txnData: TxnRespData // Транзакционные данные. Описание ниже
TxnRespData
actions: Map<TxnActionType, List<TxnActionRef>> // Транзакционные действия, которые нужно выполнить перед или после завершения транзакции;
remoteTxnApps: List<String> // Приложения, которые нужно включить в качестве ресурсов в транзакцию. Другими словами - приложения, которые ожидают коммита или ролбэка в рамках текущей транзакции.

Supported compression types:

Compression type ID

Compression type

0

No compression

1

ZSTD (Zstandard)

Supported metadata types:

Metadata type ID

Metadata type

0

CBOR array with data, where the field name is determined by the ordinal index.

Supported body types:

Body type ID

Body type

0

NONE — no body

1

BINARY — binary data

2

CBOR — structured data in CBOR format

3

JSON — structured data in JSON format

4

TEXT_UTF8 — text data

Binary representation of an integer in Citeck WebAPI:

If the number is in the range from 0 to 127 inclusive, it is written as-is in a single byte.

If the number is not in the range from 0 to 127, the first byte is formed as follows (bit by bit):

  • 0–2 — these bits encode the number of bytes in which the number is encoded. For encoding, we take the number of bytes (e.g., 2) and subtract one (getting 1). Thus, 001 means the number is encoded in two bytes.

  • 3–5 — not used

  • 6 — this bit defines the minus sign. If it is 1, the number is negative.

  • 7 — this bit indicates that the number is outside the range from 0 to 127.

Based on bits 0–2, we get the number of bytes to read, read them, and convert them to the original number. If bit 6 is set to 1, the result is multiplied by -1.

Transport

HTTP is currently used as the transport for Citeck WebAPI, but other protocols may be supported in the future.

FAQ

How are Records API and Citeck WebAPI related?

RecordsAPI uses Citeck WebAPI as transport for communication between microservices.